diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..53150142 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,7 @@ +# https://containers.dev/guide/dockerfile#dockerfile +# https://github.com/devcontainers/templates/tree/main/src/go +# https://github.com/devcontainers/images/tree/main/src/go + +FROM mcr.microsoft.com/devcontainers/go:latest + +# Add additional commands \ No newline at end of file diff --git a/.devcontainer/Dockerfile-dev b/.devcontainer/Dockerfile-dev deleted file mode 100644 index 9819d558..00000000 --- a/.devcontainer/Dockerfile-dev +++ /dev/null @@ -1,30 +0,0 @@ -ARG VARIANT="jammy" -FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} - -# Install Docker CE CLI -RUN apt-get update \ - && apt-get install -y apt-transport-https ca-certificates curl gnupg2 lsb-release \ - && curl -fsSL https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/gpg | apt-key add - 2>/dev/null \ - && echolog "deb [arch=amd64] https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]') $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list \ - && apt-get update \ - && apt-get install -y docker-ce-cli - -# Install Docker Compose -RUN LATEST_COMPOSE_VERSION=$(curl -sSL "https://api.github.com/repos/docker/compose/releases/latest" | grep -o -P '(?<="tag_name": ").+(?=")') \ - && mkdir -p ~/.docker/cli-plugins/ \ - && curl -sSL "https://github.com/docker/compose/releases/download/${LATEST_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o ~/.docker/cli-plugins/docker-compose \ - && chmod +x ~/.docker/cli-plugins/docker-compose - -# Install Buf -RUN BIN="/usr/local/bin" \ - && VERSION="1.8.0" \ - && curl -sSL \ - "https://github.com/bufbuild/buf/releases/download/v${VERSION}/buf-$(uname -s)-$(uname -m)" \ - -o "${BIN}/buf" \ - && chmod +x "${BIN}/buf" - -# Install migrate CLI -RUN BIN="/usr/local/bin" \ - && curl -L "https://github.com/golang-migrate/migrate/releases/download/v4.15.2/migrate.linux-amd64.tar.gz" | tar xvz \ - && mv migrate "${BIN}/migrate" \ - && chmod +x "${BIN}/migrate" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7957c08e..51125f20 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,108 +1,121 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet +// https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/setting-up-your-dotnet-project-for-codespaces +// https://github.com/microsoft/vscode-remote-try-dotnet +// https://dev.to/this-is-learning/set-up-github-codespaces-for-a-net-8-application-5999 +// https://audacioustux.notion.site/Getting-Started-with-Devcontainer-c727dbf9d56f4d6b9b0ef87b3111693f { - "name": "store-microservices", - "build": { - "dockerfile": "./Dockerfile-dev", - "args": { - "VARIANT": "ubuntu-22.04" - } + "name": "Go Food Delivery Microservices", + // use existing dev container templates. More info: https://containers.dev/guide/dockerfile, https://containers.dev/templates + //"image": "mcr.microsoft.com/devcontainers/dotnet:1-7.0", + // use a Dockerfile file. More info: https://containers.dev/guide/dockerfile#dockerfile + // "build": { + // // Path is relative to the devcontainer.json file. + // "dockerfile": "Dockerfile" + // }, + // using a Dockerfile with Docker Compose, https://containers.dev/guide/dockerfile#docker-compose-image + "dockerComposeFile": "docker-compose.yaml", + "service": "devcontainer", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + // https://github.com/devcontainers/features/tree/main/src/go + "ghcr.io/devcontainers/features/go:1": { + "version": "1.21" + }, + // https://github.com/devcontainers/features/tree/main/src/github-cli + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "2" }, - "runArgs": [ - "-u", - "vscode", - "-v", - "-v", - "--cap-add=SYS_PTRACE", - "--security-opt", - "seccomp=unconfined" - ], - // Use 'settings' to set *default* container specific settings.json values on container create. - // You can edit these settings after create using File > Preferences > Settings > Remote. - "settings": { - "go.gopath": "/go", - // https://github.com/golang/tools/blob/master/gopls/doc/vscode.md#vscode - "go.useLanguageServer": true, - "[go]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true - }, - // Optional: Disable snippets, as they conflict with completion ranking. - "editor.snippetSuggestions": "none" - }, - "[go.mod]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true - } - }, - "[sql]": { - "editor.formatOnSave": true - }, - // There are handly utility scripts within /scripts that we invoke via go run. - // These scripts (and its dependencies) should never be consumed by the actual server directly - // Thus they are flagged to require the "scripts" build.sh tag. - // We only inform gopls and the vscode go compiler here, that it has to set this build.sh tag if it sees such a file. - "go.buildTags": "scripts,migrate", + // https://github.com/devcontainers/features/tree/main/src/powershell + "ghcr.io/devcontainers/features/powershell:1": { + "version": "latest" + }, + // https://github.com/devcontainers/features/tree/main/src/node + "ghcr.io/devcontainers/features/node:1": {}, + // // https://github.com/devcontainers/features/tree/main/src/kubectl-helm-minikube + // "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {}, + // // https://github.com/devcontainers/features/tree/main/src/terraform + // "ghcr.io/devcontainers/features/terraform:1": {}, + // https://github.com/devcontainers/features/tree/main/src/docker-in-docker + // https://devopscube.com/run-docker-in-docker/ + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "dockerDashComposeVersion": "v2" + }, + "ghcr.io/devcontainers/features/git:1": {}, + // https://github.com/devcontainers/features/tree/main/src/common-utils + "ghcr.io/devcontainers/features/common-utils:2": { + "configureZshAsDefaultShell": true + } + }, + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + "settings": { + "git.autofetch": true, + "files.autoSave": "onFocusChange", + "editor.formatOnSave": true, + "editor.suggest.snippetsPreventQuickSuggestions": false, + "explorer.autoReveal": true, + "resmon.show.cpufreq": false, + "dotnet.server.startTimeout": 60000, + "omnisharp.projectLoadTimeout": 60, + "workbench.colorTheme": "Visual Studio Light", + "workbench.iconTheme": "material-icon-theme", + "editor.minimap.enabled": false, + "editor.fontFamily": "'MesloLGM Nerd Font', 'Droid Sans Mono', 'monospace', 'Droid Sans Fallback', 'Consolas'", + "editor.fontSize": 14, + "explorer.confirmDelete": false, + "terminal.integrated.defaultProfile.windows": "PowerShell", + "terminal.integrated.defaultProfile.linux": "zsh", + "powershell.cwd": "~", + "terminal.external.windowsExec": "%LOCALAPPDATA%\\Microsoft\\WindowsApps\\pwsh.exe", "gopls": { - // Add parameter placeholders when completing a function. - "usePlaceholders": true, - // If true, enable additional analyses with staticcheck. - // Warning: This will significantly increase memory usage. - // DISABLED, done via - "staticcheck": false + "ui.semanticTokens": true }, - "gopls.env": { - "GOFLAGS": "-tags=scripts,migrate" - }, - // https://code.visualstudio.com/docs/languages/go#_intellisense - "go.autocompleteUnimportedPackages": true, - // https://github.com/golangci/golangci-lint#editor-integration "go.lintTool": "golangci-lint", - "go.lintFlags": [ - "--fast", - "--timeout", - "5m" - ], - // disable test caching, race and show coverage (in sync with makefile) - "go.testFlags": [ - "-cover", - "-race", - "-count=1", - "-v" - ], - "go.coverMode": "atomic", // atomic is required when utilizing -race - "go.delveConfig": { - "dlvLoadConfig": { - // increase max length of strings displayed in debugger - "maxStringLen": 2048 - }, - "apiVersion": 2 - } - }, - // Add the IDs of extensions you want installed when the container is created in the array below. - "extensions": [ - "golang.go", - // optional: - "ms-azuretools.vscode-docker", - "ms-kubernetes-tools.vscode-kubernetes-tools", + "go.lintFlags": ["--fast"], + "go.lintOnSave": "package", + "go.testFlags": ["-failfast", "-v"], + "go.toolsManagement.autoUpdate": true, + "go.useLanguageServer": true, + "go.formatTool": "gofumpt" + }, + "extensions": [ + "golang.Go", + "esbenp.prettier-vscode", + "streetsidesoftware.code-spell-checker", + "ms-dotnettools.csdevkit", "mutantdino.resourcemonitor", "humao.rest-client", - "42crunch.vscode-openapi", - "heaths.vscode-guid", - "bungcip.better-toml", - "eamodio.gitlens", - "casualjim.gotemplate", - "davidanson.vscode-markdownlint", - "cweijan.vscode-mysql-client2", - "bierner.markdown-mermaid" - ], - "postCreateCommand": "go version", - "features": { - "ghcr.io/devcontainers/features/go:1": { - "version": "1.21" - }, - "ghcr.io/devcontainers/features/docker-from-docker:1": { - "version": "latest" - } + "dzhavat.bracket-pair-toggler", + "ms-azuretools.vscode-docker", + "vivaxy.vscode-conventional-commits", + "emmanuelbeziat.vscode-great-icons", + "ms-vscode.vs-keybindings", + "GitHub.vscode-github-actions", + "PKief.material-icon-theme", + "EditorConfig.EditorConfig", + "DavidAnson.vscode-markdownlint" + ] } + }, + "hostRequirements": { + "cpus": 2, + "memory": "8gb", + "storage": "32gb" + }, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [5000, 5001], + // "portsAttributes": { + // "5001": { + // "protocol": "https" + // } + // } + // https://containers.dev/implementors/json_reference/#lifecycle-scripts + "updateContentCommand": "chmod +x .devcontainer/scripts/update.sh", + "postCreateCommand": "chmod +x .devcontainer/scripts/post-create.sh" + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" } diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml new file mode 100644 index 00000000..8d8adc20 --- /dev/null +++ b/.devcontainer/docker-compose.yaml @@ -0,0 +1,26 @@ +# https://containers.dev/guide/dockerfile#docker-compose-image +# https://containers.dev/guide/dockerfile#docker-compose-dockerfile +services: + devcontainer: + build: + context: . + volumes: + - ../..:/workspaces:rw,cached + init: true + command: sleep infinity + +# services: +# devcontainer: +# image: mcr.microsoft.com/devcontainers/dotnet:3.1.0 +# volumes: +# - ../..:/workspaces:cached +# network_mode: service:db +# command: sleep infinity + +# db: +# image: postgres:latest +# restart: unless-stopped +# environment: +# POSTGRES_PASSWORD: postgres +# POSTGRES_USER: postgres +# POSTGRES_DB: postgres diff --git a/.devcontainer/scripts/post-create.sh b/.devcontainer/scripts/post-create.sh new file mode 100644 index 00000000..ea4950e9 --- /dev/null +++ b/.devcontainer/scripts/post-create.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -eax + +# Run the setup-fonts.sh script +echo "Installing Nerd Fonts..." +./setup-fonts.sh + +echo "Installing tools for Go development..." +./../../scripts/install-tools.sh diff --git a/.devcontainer/scripts/setup-fonts.sh b/.devcontainer/scripts/setup-fonts.sh new file mode 100644 index 00000000..309333bb --- /dev/null +++ b/.devcontainer/scripts/setup-fonts.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Download MesloLGM Nerd Font +wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/Meslo.zip -O MesloLGM.zip + +# Extract the font files +unzip MesloLGM.zip -d MesloLGM + +# Create the fonts directory if it doesn't exist +mkdir -p ~/.local/share/fonts + +# Move the font files to the fonts directory +mv MesloLGM/*.ttf ~/.local/share/fonts/ + +# Update the font cache +fc-cache -fv + +# Clean up +rm -rf MesloLGM.zip MesloLGM + +# Verify installation +fc-list | grep "MesloLGM" + +echo "Font setup completed." \ No newline at end of file diff --git a/.devcontainer/scripts/update.sh b/.devcontainer/scripts/update.sh new file mode 100644 index 00000000..99a8bb9c --- /dev/null +++ b/.devcontainer/scripts/update.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +set -eax \ No newline at end of file diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml index b9d49908..e855b068 100644 --- a/.github/workflows/first-interaction.yml +++ b/.github/workflows/first-interaction.yml @@ -22,5 +22,5 @@ jobs: - uses: actions/first-interaction@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: "Welcome to go-ecommerce-microservices. Thank you ${{ github.event.pull_request.user.login }} for reporting your first issue. Please check out our [contributor guide](https://github.com/mehdihadeli/go-ecommerce-microservices/blob/main/CONTRIBUTION.md)." - pr-message: "Thank you ${{ github.event.pull_request.user.login }} for your first pull request to go-ecommerce-microservices repository. Please check out our [contributors guide](https://github.com/mehdihadeli/go-ecommerce-microservices/blob/main/CONTRIBUTION.md)." + issue-message: "Welcome to go-food-delivery-microservices. Thank you ${{ github.event.pull_request.user.login }} for reporting your first issue. Please check out our [contributor guide](https://github.com/mehdihadeli/go-food-delivery-microservices/blob/main/CONTRIBUTION.md)." + pr-message: "Thank you ${{ github.event.pull_request.user.login }} for your first pull request to go-food-delivery-microservices repository. Please check out our [contributors guide](https://github.com/mehdihadeli/go-food-delivery-microservices/blob/main/CONTRIBUTION.md)." diff --git a/.gitignore b/.gitignore index 2a5970e3..8220ca71 100644 --- a/.gitignore +++ b/.gitignore @@ -186,3 +186,7 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +.cycle/ + +.env diff --git a/.vscode/settings.json b/.vscode/settings.json index a3104cd8..09ed86c1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,36 @@ { - "gopls": { + "git.autofetch": true, + "files.autoSave": "onFocusChange", + "editor.formatOnSave": true, + "editor.suggest.snippetsPreventQuickSuggestions": false, + "explorer.autoReveal": true, + "resmon.show.cpufreq": false, + "dotnet.server.startTimeout": 60000, + "omnisharp.projectLoadTimeout": 60, + "gopls": { "ui.semanticTokens": true - }, - "go.lintTool":"golangci-lint", - "go.lintFlags": ["--fast"], - "go.lintOnSave": "package", - "go.testFlags": ["-failfast","-v"], - "go.toolsManagement.autoUpdate": true, - "go.useLanguageServer": true -} \ No newline at end of file + }, + "go.lintTool": "golangci-lint", + "go.lintFlags": ["--fast"], + "go.lintOnSave": "package", + "go.testFlags": ["-failfast", "-v"], + "go.toolsManagement.autoUpdate": true, + "go.useLanguageServer": true, + "go.formatTool": "gofumpt", + "workbench.colorTheme": "Visual Studio Light", + "workbench.iconTheme": "material-icon-theme", + "editor.minimap.enabled": false, + "terminal.integrated.fontFamily": "MesloLGM Nerd Font", + "terminal.integrated.defaultProfile.windows": "PowerShell", + "terminal.integrated.defaultProfile.linux": "zsh", + "powershell.cwd": "~", + "terminal.external.windowsExec": "%LOCALAPPDATA%\\Microsoft\\WindowsApps\\pwsh.exe", + "explorer.confirmDelete": false, + "remote.autoForwardPortsSource": "hybrid", + "go.inlayHints.assignVariableTypes": true, + "go.inlayHints.compositeLiteralFields": true, + "go.inlayHints.compositeLiteralTypes": true, + "go.inlayHints.constantValues": true, + "go.inlayHints.functionTypeParameters": true, + "go.inlayHints.rangeVariableTypes": true +} diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..1fc1b99a --- /dev/null +++ b/Makefile @@ -0,0 +1,142 @@ +.PHONY: install-tools +install-tools: + @./scripts/install-tools.sh + +.PHONY: run-catalogs-write-service +run-catalogs-write-service: + @./scripts/run.sh catalogwriteservice + +.PHONY: run-catalog-read-service +run-catalog-read-service: + @./scripts/run.sh catalogreadservice + +.PHONY: run-order-service +run-order-service: + @./scripts/run.sh orderservice + +.PHONY: build +build: + @./scripts/build.sh pkg + @./scripts/build.sh catalogwriteservice + @./scripts/build.sh catalogreadservice + @./scripts/build.sh orderservice + +.PHONY: update-dependencies +update-dependencies: + @./scripts/update-dependencies.sh pkg + @./scripts/update-dependencies.sh catalogwriteservice + @./scripts/update-dependencies.sh catalogreadservice + @./scripts/update-dependencies.sh orderservice + +.PHONY: install-dependencies +install-dependencies: + @./scripts/install-dependencies.sh pkg + @./scripts/install-dependencies.sh catalogwriteservice + @./scripts/install-dependencies.sh catalogreadservice + @./scripts/install-dependencies.sh orderservice + +.PHONY: docker-compose-infra-up +docker-compose-infra-up: + @docker-compose -f deployments/docker-compose/docker-compose.infrastructure.yaml up --build -d + +docker-compose-infra-down: + @docker-compose -f deployments/docker-compose/docker-compose.infrastructure.yaml down + +.PHONY: openapi +openapi: + @./scripts/openapi.sh catalogwriteservice + @./scripts/openapi.sh catalogreadservice + @./scripts/openapi.sh orderservice + +# https://stackoverflow.com/questions/13616033/install-protocol-buffers-on-windows +.PHONY: proto +proto: + @./scripts/proto.sh catalogwriteservice + @./scripts/proto.sh orderservice + +.PHONY: unit-test +unit-test: + @./scripts/test.sh catalogreadservice unit + @./scripts/test.sh catalogwriteservice unit + @./scripts/test.sh orderservice unit + +.PHONY: integration-test +integration-test: + @./scripts/test.sh catalogreadservice integration + @./scripts/test.sh catalogwriteservice integration + @./scripts/test.sh orderservice integration + +.PHONY: e2e-test +e2e-test: + @./scripts/test.sh catalogreadservice e2e + @./scripts/test.sh catalogwriteservice e2e + @./scripts/test.sh orderservice e2e + +#.PHONY: load-test +#load-test: +# @./scripts/test.sh catalogs_write load-test +# @./scripts/test.sh catalogs_read load-test +# @./scripts/test.sh orders load-test + +.PHONY: format +format: + @./scripts/format.sh catalogwriteservice + @./scripts/format.sh catalogreadservice + @./scripts/format.sh orderservice + @./scripts/format.sh pkg + +.PHONY: lint +lint: + @./scripts/lint.sh catalogwriteservice + @./scripts/lint.sh catalogreadservice + @./scripts/lint.sh orderservice + @./scripts/lint.sh pkg + +# https://github.com/golang-migrate/migrate/blob/856ea12df9d230b0145e23d951b7dbd6b86621cb/database/postgres/TUTORIAL.md +# https://github.com/golang-migrate/migrate/blob/856ea12df9d230b0145e23d951b7dbd6b86621cb/GETTING_STARTED.md +# https://github.com/golang-migrate/migrate/blob/856ea12df9d230b0145e23d951b7dbd6b86621cb/MIGRATIONS.md +# https://github.com/golang-migrate/migrate/tree/856ea12df9d230b0145e23d951b7dbd6b86621cb/cmd/migrate#usage +.PHONY: go-migrate +go-migrate: + @./scripts/go-migrate.sh -p ./internal/services/catalogwriteservice/db/migrations/go-migrate -c create -n create_product_table + @./scripts/go-migrate.sh -p ./internal/services/catalogwriteservice/db/migrations/go-migrate -c up -o postgres://postgres:postgres@localhost:5432/catalogs_service?sslmode=disable + @./scripts/go-migrate.sh -p ./internal/services/catalogwriteservice/db/migrations/go-migrate -c down -o postgres://postgres:postgres@localhost:5432/catalogs_service?sslmode=disable + +# https://github.com/pressly/goose#usage +.PHONY: goose-migrate +goose-migrate: + @./scripts/goose-migrate.sh -p ./internal/services/catalogwriteservice/db/migrations/goose-migrate -c create -n create_product_table + @./scripts/goose-migrate.sh -p ./internal/services/catalogwriteservice/db/migrations/goose-migrate -c up -o "user=postgres password=postgres dbname=catalogs_service sslmode=disable" + @./scripts/goose-migrate.sh -p ./internal/services/catalogwriteservice/db/migrations/goose-migrate -c down -o "user=postgres password=postgres dbname=catalogs_service sslmode=disable" + +# https://atlasgo.io/guides/orms/gorm +.PHONY: atlas +atlas: + @./scripts/atlas-migrate.sh -c gorm-sync -p "./internal/services/catalogwriteservice" + @./scripts/atlas-migrate.sh -c apply -p "./internal/services/catalogwriteservice" -o "postgres://postgres:postgres@localhost:5432/catalogs_service?sslmode=disable" + +.PHONY: cycle-check +cycle-check: + cd internal/pkg && goimportcycle -dot imports.dot dot -Tpng -o cycle/pkg.png imports.dot + cd internal/services/catalogwriteservice && goimportcycle -dot imports.dot dot -Tpng -o cycle/catalogwriteservice.png imports.dot + cd internal/services/catalogwriteservice && goimportcycle -dot imports.dot dot -Tpng -o cycle/catalogwriteservice.png imports.dot + cd internal/services/orderservice && goimportcycle -dot imports.dot dot -Tpng -o cycle/orderservice.png imports.dot + +#.PHONY: c4 +#c4: +# cd tools/c4 && go mod tidy && sh generate.sh + +# https://medium.com/yemeksepeti-teknoloji/mocking-an-interface-using-mockery-in-go-afbcb83cc773 +# https://vektra.github.io/mockery/latest/running/ +# https://amitshekhar.me/blog/test-with-testify-and-mockery-in-go +.PHONY: pkg-mocks +pkg-mocks: + cd internal/pkg/es && mockery --output mocks --all + cd internal/pkg/core/serializer && mockery --output mocks --all + cd internal/pkg/core/messaging && mockery --output mocks --all + +.PHONY: services-mocks +services-mocks: + cd internal/services/catalogwriteservice && mockery --output mocks --all + cd internal/services/catalogreadservice && mockery --output mocks --all + cd internal/services/orderservice && mockery --output mocks --all diff --git a/api/openapi/catalogreadservice/docs.go b/api/openapi/catalogreadservice/docs.go new file mode 100644 index 00000000..466a708c --- /dev/null +++ b/api/openapi/catalogreadservice/docs.go @@ -0,0 +1,252 @@ +// Code generated by swaggo/swag. DO NOT EDIT. + +package catalogreadservice + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/products": { + "get": { + "description": "Get all products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get all product", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/search": { + "get": { + "description": "Search products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Search products", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "description": "Get product by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get product", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto" + } + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "productId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto": { + "type": "object", + "properties": { + "product": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListQuery": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FilterModel" + } + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Catalogs Read-Service Api", + Description: "Catalogs Read-Service Api.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/api/openapi/catalogreadservice/swagger.json b/api/openapi/catalogreadservice/swagger.json new file mode 100644 index 00000000..f6ed6251 --- /dev/null +++ b/api/openapi/catalogreadservice/swagger.json @@ -0,0 +1,225 @@ +{ + "swagger": "2.0", + "info": { + "description": "Catalogs Read-Service Api.", + "title": "Catalogs Read-Service Api", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "1.0" + }, + "paths": { + "/api/v1/products": { + "get": { + "description": "Get all products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get all product", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/search": { + "get": { + "description": "Search products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Search products", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "description": "Get product by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get product", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto" + } + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "productId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto": { + "type": "object", + "properties": { + "product": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListQuery": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FilterModel" + } + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/api/openapi/catalogreadservice/swagger.yaml b/api/openapi/catalogreadservice/swagger.yaml new file mode 100644 index 00000000..0007d662 --- /dev/null +++ b/api/openapi/catalogreadservice/swagger.yaml @@ -0,0 +1,144 @@ +definitions: + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto: + properties: + createdAt: + type: string + description: + type: string + id: + type: string + name: + type: string + price: + type: number + productId: + type: string + updatedAt: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto + : properties: + product: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto + : properties: + products: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto + : properties: + products: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto' + type: object + utils.FilterModel: + properties: + comparison: + type: string + field: + type: string + value: + type: string + type: object + utils.ListQuery: + properties: + filters: + items: + $ref: '#/definitions/utils.FilterModel' + type: array + orderBy: + type: string + page: + type: integer + size: + type: integer + type: object + ? utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto + : properties: + items: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto' + type: array + page: + type: integer + size: + type: integer + totalItems: + type: integer + totalPage: + type: integer + type: object +info: + contact: + name: Mehdi Hadeli + url: https://github.com/mehdihadeli + description: Catalogs Read-Service Api. + title: Catalogs Read-Service Api + version: "1.0" +paths: + /api/v1/products: + get: + consumes: + - application/json + description: Get all products + parameters: + - in: query + name: orderBy + type: string + - in: query + name: page + type: integer + - in: query + name: size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto' + summary: Get all product + tags: + - Products + /api/v1/products/{id}: + get: + consumes: + - application/json + description: Get product by id + parameters: + - description: Product ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto' + summary: Get product + tags: + - Products + /api/v1/products/search: + get: + consumes: + - application/json + description: Search products + parameters: + - in: query + name: search + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto' + summary: Search products + tags: + - Products +swagger: "2.0" diff --git a/api/openapi/catalogwriteservice/docs.go b/api/openapi/catalogwriteservice/docs.go new file mode 100644 index 00000000..e92fab5a --- /dev/null +++ b/api/openapi/catalogwriteservice/docs.go @@ -0,0 +1,380 @@ +// Code generated by swaggo/swag. DO NOT EDIT. + +package catalogwriteservice + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/products": { + "get": { + "description": "Get all products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get all product", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto" + } + } + } + }, + "post": { + "description": "Create new product item", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Create product", + "parameters": [ + { + "description": "Product data", + "name": "CreateProductRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto" + } + } + } + } + }, + "/api/v1/products/search": { + "get": { + "description": "Search products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Search products", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "description": "Get product by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get product by id", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto" + } + } + } + }, + "put": { + "description": "Update existing product", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Update product", + "parameters": [ + { + "description": "Product data", + "name": "UpdateProductRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto" + } + }, + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "description": "Delete existing product", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Delete product", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "productId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto": { + "type": "object", + "properties": { + "productId": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto": { + "type": "object", + "properties": { + "product": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListQuery": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FilterModel" + } + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Catalogs Write-Service Api", + Description: "Catalogs Write-Service Api.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/api/openapi/catalogwriteservice/swagger.json b/api/openapi/catalogwriteservice/swagger.json new file mode 100644 index 00000000..96d7f8e0 --- /dev/null +++ b/api/openapi/catalogwriteservice/swagger.json @@ -0,0 +1,353 @@ +{ + "swagger": "2.0", + "info": { + "description": "Catalogs Write-Service Api.", + "title": "Catalogs Write-Service Api", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "1.0" + }, + "paths": { + "/api/v1/products": { + "get": { + "description": "Get all products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get all product", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto" + } + } + } + }, + "post": { + "description": "Create new product item", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Create product", + "parameters": [ + { + "description": "Product data", + "name": "CreateProductRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto" + } + } + } + } + }, + "/api/v1/products/search": { + "get": { + "description": "Search products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Search products", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "description": "Get product by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get product by id", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto" + } + } + } + }, + "put": { + "description": "Update existing product", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Update product", + "parameters": [ + { + "description": "Product data", + "name": "UpdateProductRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto" + } + }, + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "description": "Delete existing product", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Delete product", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "productId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto": { + "type": "object", + "properties": { + "productId": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto": { + "type": "object", + "properties": { + "product": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListQuery": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FilterModel" + } + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/api/openapi/catalogwriteservice/swagger.yaml b/api/openapi/catalogwriteservice/swagger.yaml new file mode 100644 index 00000000..14a05ec0 --- /dev/null +++ b/api/openapi/catalogwriteservice/swagger.yaml @@ -0,0 +1,228 @@ +definitions: + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto: + properties: + createdAt: + type: string + description: + type: string + name: + type: string + price: + type: number + productId: + type: string + updatedAt: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto + : properties: + description: + type: string + name: + type: string + price: + type: number + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto + : properties: + productId: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto + : properties: + product: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto + : properties: + products: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto + : properties: + products: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto + : properties: + description: + type: string + name: + type: string + price: + type: number + type: object + utils.FilterModel: + properties: + comparison: + type: string + field: + type: string + value: + type: string + type: object + utils.ListQuery: + properties: + filters: + items: + $ref: '#/definitions/utils.FilterModel' + type: array + orderBy: + type: string + page: + type: integer + size: + type: integer + type: object + ? utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto + : properties: + items: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto' + type: array + page: + type: integer + size: + type: integer + totalItems: + type: integer + totalPage: + type: integer + type: object +info: + contact: + name: Mehdi Hadeli + url: https://github.com/mehdihadeli + description: Catalogs Write-Service Api. + title: Catalogs Write-Service Api + version: "1.0" +paths: + /api/v1/products: + get: + consumes: + - application/json + description: Get all products + parameters: + - in: query + name: orderBy + type: string + - in: query + name: page + type: integer + - in: query + name: size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto' + summary: Get all product + tags: + - Products + post: + consumes: + - application/json + description: Create new product item + parameters: + - description: Product data + in: body + name: CreateProductRequestDto + required: true + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto' + summary: Create product + tags: + - Products + /api/v1/products/{id}: + delete: + consumes: + - application/json + description: Delete existing product + parameters: + - description: Product ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + summary: Delete product + tags: + - Products + get: + consumes: + - application/json + description: Get product by id + parameters: + - description: Product ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto' + summary: Get product by id + tags: + - Products + put: + consumes: + - application/json + description: Update existing product + parameters: + - description: Product data + in: body + name: UpdateProductRequestDto + required: true + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto' + - description: Product ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + summary: Update product + tags: + - Products + /api/v1/products/search: + get: + consumes: + - application/json + description: Search products + parameters: + - in: query + name: search + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto' + summary: Search products + tags: + - Products +swagger: "2.0" diff --git a/api/openapi/orderservice/docs.go b/api/openapi/orderservice/docs.go new file mode 100644 index 00000000..837aa349 --- /dev/null +++ b/api/openapi/orderservice/docs.go @@ -0,0 +1,315 @@ +// Code generated by swaggo/swag. DO NOT EDIT. + +package orderservice + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/orders": { + "get": { + "description": "Get all orders", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Get all orders", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto" + } + } + } + }, + "post": { + "description": "Create new order", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Create order", + "parameters": [ + { + "description": "Order data", + "name": "CreateOrderRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto" + } + } + } + } + }, + "/api/v1/orders/{id}": { + "get": { + "description": "Get order by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Get order by id", + "parameters": [ + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto" + } + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto": { + "type": "object", + "properties": { + "accountEmail": { + "type": "string" + }, + "cancelReason": { + "type": "string" + }, + "canceled": { + "type": "boolean" + }, + "completed": { + "type": "boolean" + }, + "createdAt": { + "type": "string" + }, + "deliveredTime": { + "type": "string" + }, + "deliveryAddress": { + "type": "string" + }, + "id": { + "type": "string" + }, + "orderId": { + "type": "string" + }, + "paid": { + "type": "boolean" + }, + "paymentId": { + "type": "string" + }, + "shopItems": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto" + } + }, + "submitted": { + "type": "boolean" + }, + "totalPrice": { + "type": "number" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto": { + "type": "object", + "properties": { + "accountEmail": { + "type": "string" + }, + "deliveryAddress": { + "type": "string" + }, + "deliveryTime": { + "type": "string" + }, + "shopItems": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto" + } + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto": { + "type": "object", + "properties": { + "Id": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto": { + "type": "object", + "properties": { + "order": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto": { + "type": "object", + "properties": { + "orders": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Orders Service Api", + Description: "Orders Service Api", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/api/openapi/orderservice/swagger.json b/api/openapi/orderservice/swagger.json new file mode 100644 index 00000000..c2cab1dd --- /dev/null +++ b/api/openapi/orderservice/swagger.json @@ -0,0 +1,288 @@ +{ + "swagger": "2.0", + "info": { + "description": "Orders Service Api", + "title": "Orders Service Api", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "1.0" + }, + "paths": { + "/api/v1/orders": { + "get": { + "description": "Get all orders", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Get all orders", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto" + } + } + } + }, + "post": { + "description": "Create new order", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Create order", + "parameters": [ + { + "description": "Order data", + "name": "CreateOrderRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto" + } + } + } + } + }, + "/api/v1/orders/{id}": { + "get": { + "description": "Get order by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Get order by id", + "parameters": [ + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto" + } + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto": { + "type": "object", + "properties": { + "accountEmail": { + "type": "string" + }, + "cancelReason": { + "type": "string" + }, + "canceled": { + "type": "boolean" + }, + "completed": { + "type": "boolean" + }, + "createdAt": { + "type": "string" + }, + "deliveredTime": { + "type": "string" + }, + "deliveryAddress": { + "type": "string" + }, + "id": { + "type": "string" + }, + "orderId": { + "type": "string" + }, + "paid": { + "type": "boolean" + }, + "paymentId": { + "type": "string" + }, + "shopItems": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto" + } + }, + "submitted": { + "type": "boolean" + }, + "totalPrice": { + "type": "number" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto": { + "type": "object", + "properties": { + "accountEmail": { + "type": "string" + }, + "deliveryAddress": { + "type": "string" + }, + "deliveryTime": { + "type": "string" + }, + "shopItems": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto" + } + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto": { + "type": "object", + "properties": { + "Id": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto": { + "type": "object", + "properties": { + "order": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto": { + "type": "object", + "properties": { + "orders": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/api/openapi/orderservice/swagger.yaml b/api/openapi/orderservice/swagger.yaml new file mode 100644 index 00000000..56bf1aa7 --- /dev/null +++ b/api/openapi/orderservice/swagger.yaml @@ -0,0 +1,186 @@ +definitions: + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto: + properties: + accountEmail: + type: string + cancelReason: + type: string + canceled: + type: boolean + completed: + type: boolean + createdAt: + type: string + deliveredTime: + type: string + deliveryAddress: + type: string + id: + type: string + orderId: + type: string + paid: + type: boolean + paymentId: + type: string + shopItems: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto' + type: array + submitted: + type: boolean + totalPrice: + type: number + updatedAt: + type: string + type: object + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto: + properties: + description: + type: string + price: + type: number + quantity: + type: integer + title: + type: string + type: object + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto: + properties: + description: + type: string + price: + type: number + quantity: + type: integer + title: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto + : properties: + accountEmail: + type: string + deliveryAddress: + type: string + deliveryTime: + type: string + shopItems: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto' + type: array + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto + : properties: + Id: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto + : properties: + order: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto + : properties: + orders: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto' + type: object + utils.FilterModel: + properties: + comparison: + type: string + field: + type: string + value: + type: string + type: object + ? utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto + : properties: + items: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto' + type: array + page: + type: integer + size: + type: integer + totalItems: + type: integer + totalPage: + type: integer + type: object +info: + contact: + name: Mehdi Hadeli + url: https://github.com/mehdihadeli + description: Orders Service Api + title: Orders Service Api + version: "1.0" +paths: + /api/v1/orders: + get: + consumes: + - application/json + description: Get all orders + parameters: + - in: query + name: orderBy + type: string + - in: query + name: page + type: integer + - in: query + name: size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto' + summary: Get all orders + tags: + - Orders + post: + consumes: + - application/json + description: Create new order + parameters: + - description: Order data + in: body + name: CreateOrderRequestDto + required: true + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto' + summary: Create order + tags: + - Orders + /api/v1/orders/{id}: + get: + consumes: + - application/json + description: Get order by id + parameters: + - description: Order ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto' + summary: Get order by id + tags: + - Orders +swagger: "2.0" diff --git a/api/protobuf/catalogwriteservice/products.proto b/api/protobuf/catalogwriteservice/products.proto new file mode 100644 index 00000000..4e01c0af --- /dev/null +++ b/api/protobuf/catalogwriteservice/products.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +package products_service; + +option go_package = "./;products_service"; + +service ProductsService { + rpc CreateProduct(CreateProductReq) returns (CreateProductRes); + rpc UpdateProduct(UpdateProductReq) returns (UpdateProductRes); + rpc GetProductById(GetProductByIdReq) returns (GetProductByIdRes); +} + +message Product { + string ProductId = 1; + string Name = 2; + string Description = 3; + double Price = 4; + google.protobuf.Timestamp CreatedAt = 6; + google.protobuf.Timestamp UpdatedAt = 7; +} + +message CreateProductReq { + string Name = 1; + string Description = 2; + double Price = 3; +} + +message CreateProductRes { + string ProductId = 1; +} + +message UpdateProductReq { + string ProductId = 1; + string Name = 2; + string Description = 3; + double Price = 4; +} + +message UpdateProductRes {} + +message GetProductByIdReq { + string ProductId = 1; +} + +message GetProductByIdRes { + Product Product = 1; +} \ No newline at end of file diff --git a/api/protobuf/common.proto b/api/protobuf/common.proto new file mode 100644 index 00000000..410bfdea --- /dev/null +++ b/api/protobuf/common.proto @@ -0,0 +1 @@ +syntax = "proto3"; diff --git a/api/protobuf/orderservice/orders.proto b/api/protobuf/orderservice/orders.proto new file mode 100644 index 00000000..904c2c4a --- /dev/null +++ b/api/protobuf/orderservice/orders.proto @@ -0,0 +1,118 @@ +syntax = "proto3"; + +package orders_service; + +option go_package = "./;orders_service"; + +import "google/protobuf/timestamp.proto"; + + +message ShopItem { + string Title = 1; + string Description = 2; + uint64 Quantity = 3; + double Price = 4; +} + +message Order { + string OrderId = 1; + repeated ShopItem ShopItems = 2; + bool Paid = 3; + bool Submitted = 4; + bool Completed = 5; + bool Canceled = 6; + double TotalPrice = 7; + string AccountEmail = 8; + string CancelReason = 9; + string DeliveryAddress = 10; + google.protobuf.Timestamp DeliveredTime = 11; + google.protobuf.Timestamp CreatedAt = 12; + google.protobuf.Timestamp UpdatedAt = 13; + string PaymentId = 14; +} + +message OrderReadModel { + string Id = 1; + string OrderId = 2; + repeated ShopItemReadModel ShopItems = 3; + bool Paid = 4; + bool Submitted = 5; + bool Completed = 6; + bool Canceled = 7; + double TotalPrice = 8; + string AccountEmail = 9; + string CancelReason = 10; + string DeliveryAddress = 11; + google.protobuf.Timestamp DeliveredTime = 12; + google.protobuf.Timestamp CreatedAt = 13; + google.protobuf.Timestamp UpdatedAt = 14; + string PaymentId = 15; +} + +message ShopItemReadModel { + string Title = 1; + string Description = 2; + uint64 Quantity = 3; + double Price = 4; +} + +message CreateOrderReq { + string AccountEmail = 1; + repeated ShopItem ShopItems = 2; + string DeliveryAddress = 3; + google.protobuf.Timestamp DeliveryTime = 4; +} + +message CreateOrderRes { + string OrderId = 1; +} + +message SubmitOrderReq { + string OrderId = 1; +} + +message SubmitOrderRes { + string OrderId = 1; +} + +message GetOrderByIDReq { + string Id = 1; +} + +message GetOrderByIDRes { + OrderReadModel Order = 1; +} + +message UpdateShoppingCartReq { + string OrderId = 1; + repeated ShopItem ShopItems = 2; +} + +message UpdateShoppingCartRes {} + +message GetOrdersReq { + string SearchText = 1; + int32 Page = 2; + int32 Size = 3; +} + +message GetOrdersRes { + Pagination Pagination = 1; + repeated OrderReadModel Orders = 2; +} + +message Pagination { + int64 TotalItems = 1; + int32 TotalPages = 2; + int32 Page = 3; + int32 Size = 4; + bool HasMore = 5; +} + +service OrdersService { + rpc CreateOrder(CreateOrderReq) returns (CreateOrderRes); + rpc SubmitOrder(SubmitOrderReq) returns (SubmitOrderRes); + rpc UpdateShoppingCart(UpdateShoppingCartReq) returns (UpdateShoppingCartRes); + rpc GetOrderByID(GetOrderByIDReq) returns (GetOrderByIDRes); + rpc GetOrders(GetOrdersReq) returns (GetOrdersRes); +} diff --git a/assets/catalogs-service.png b/assets/catalogs-service.png new file mode 100644 index 00000000..9bcb71f2 Binary files /dev/null and b/assets/catalogs-service.png differ diff --git a/assets/high-level.png b/assets/high-level.png new file mode 100644 index 00000000..1c93f3c7 Binary files /dev/null and b/assets/high-level.png differ diff --git a/assets/system-architecture-raw.excalidraw b/assets/system-architecture-raw.excalidraw new file mode 100644 index 00000000..fca2936a --- /dev/null +++ b/assets/system-architecture-raw.excalidraw @@ -0,0 +1,6525 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "type": "arrow", + "version": 737, + "versionNonce": 1620537161, + "isDeleted": false, + "id": "m3zezDVBTqKuPqyHbYsf6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 641.0433407464122, + "y": 451.9204131016077, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 64.87804991428078, + "height": 398.0795868983923, + "seed": 1040908097, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "07RNxXH8sCPnJGTmwMgQi" + } + ], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": { + "elementId": "xsYLZ_oJEXz46VEKvxb6P", + "focus": 0.3939596375981451, + "gap": 2.9476235043467796 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -64.87804991428078, + 171.57958689839228 + ], + [ + -30.025794502239762, + 398.0795868983923 + ] + ] + }, + { + "id": "07RNxXH8sCPnJGTmwMgQi", + "type": "text", + "x": 490.6553573604517, + "y": 611, + "width": 171.01986694335938, + "height": 25, + "angle": 0, + "strokeColor": "#364fc7", + "backgroundColor": "#5c940d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 2039820711, + "version": 11, + "versionNonce": 709936583, + "isDeleted": false, + "boundElements": null, + "updated": 1692970003171, + "link": null, + "locked": false, + "text": "Save Write Model", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "m3zezDVBTqKuPqyHbYsf6", + "originalText": "Save Write Model", + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "version": 988, + "versionNonce": 1952226857, + "isDeleted": false, + "id": "xsYLZ_oJEXz46VEKvxb6P", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 623.0116712575987, + "y": 352.9032025709222, + "strokeColor": "#1280d9", + "backgroundColor": "#1280d9", + "width": 118.63942581686875, + "height": 110.73013076241084, + "seed": 1379790145, + "groupIds": [ + "1blh0pdFtosucu9bti_GL", + "mkUlC9Ni4aY40KW279Uwk" + ], + "roundness": null, + "boundElements": [ + { + "id": "7mWVcUYEIq6l4O9wPbigM", + "type": "arrow" + }, + { + "id": "m3zezDVBTqKuPqyHbYsf6", + "type": "arrow" + } + ], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1196, + "versionNonce": 65172711, + "isDeleted": false, + "id": "3fFqGGoReBza5bi_BKfBc", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 722.8665213201303, + "y": 371.6877783252596, + "strokeColor": "#6cadf3", + "backgroundColor": "#6cadf3", + "width": 81.0702743081936, + "height": 73.16097925373579, + "seed": 1792230223, + "groupIds": [ + "1blh0pdFtosucu9bti_GL", + "mkUlC9Ni4aY40KW279Uwk" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -53.747255029157124, + 25.293266476235246 + ], + [ + -81.0702743081936, + 73.16097925373579 + ] + ] + }, + { + "type": "line", + "version": 1070, + "versionNonce": 2035872009, + "isDeleted": false, + "id": "5PT2U6abBzW5IVTlTh8_X", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 665.7272692570981, + "y": 356.8578500981514, + "strokeColor": "#6cadf3", + "backgroundColor": "#6cadf3", + "width": 68.01453276291157, + "height": 70.19499360831405, + "seed": 1366334753, + "groupIds": [ + "1blh0pdFtosucu9bti_GL", + "mkUlC9Ni4aY40KW279Uwk" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 16.221639874367487, + 44.4750285338361 + ], + [ + 68.01453276291157, + 70.19499360831405 + ] + ] + }, + { + "type": "ellipse", + "version": 1139, + "versionNonce": 431046663, + "isDeleted": false, + "id": "IBhySbFYsDPPrlXXWZ_qL", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 702.1046218021804, + "y": 411.23425359755004, + "strokeColor": "#ffff", + "backgroundColor": "#ffff", + "width": 17.795913872530317, + "height": 17.795913872530317, + "seed": 827064687, + "groupIds": [ + "1blh0pdFtosucu9bti_GL", + "mkUlC9Ni4aY40KW279Uwk" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1270, + "versionNonce": 84404201, + "isDeleted": false, + "id": "YUCu0Xr99_MWYIcLCQhtt", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 6.1036791789047005, + "x": 653.0641927052299, + "y": 364.44952289001327, + "strokeColor": "#6cadf3", + "backgroundColor": "#6cadf3", + "width": 59.56036004152681, + "height": 89.05870919955755, + "seed": 1518489857, + "groupIds": [ + "1blh0pdFtosucu9bti_GL", + "mkUlC9Ni4aY40KW279Uwk" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.10816435157013607, + 63.498515694252916 + ], + [ + 59.45219568995668, + 89.05870919955755 + ] + ] + }, + { + "type": "ellipse", + "version": 1058, + "versionNonce": 498706215, + "isDeleted": false, + "id": "GEiL7LKPi-TKDi1XCLJW2", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 636.8529376029036, + "y": 395.4156634886342, + "strokeColor": "#ffff", + "backgroundColor": "#ffff", + "width": 34.60316586325345, + "height": 34.60316586325345, + "seed": 1112013711, + "groupIds": [ + "1blh0pdFtosucu9bti_GL", + "mkUlC9Ni4aY40KW279Uwk" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1109, + "versionNonce": 1903493833, + "isDeleted": false, + "id": "b6YPfDMQKgcw1e2af8v7K", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 682.3313841660306, + "y": 435.95080064272963, + "strokeColor": "#ffff", + "backgroundColor": "#ffff", + "width": 17.795913872530317, + "height": 17.795913872530317, + "seed": 1660061921, + "groupIds": [ + "1blh0pdFtosucu9bti_GL", + "mkUlC9Ni4aY40KW279Uwk" + ], + "roundness": null, + "boundElements": [ + { + "id": "7mWVcUYEIq6l4O9wPbigM", + "type": "arrow" + } + ], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1232, + "versionNonce": 1315449415, + "isDeleted": false, + "id": "sCl2KsO6JoyjdHAFBh8qm", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 526.5216090425529, + "y": 297.63333333333327, + "strokeColor": "#000000", + "backgroundColor": "#6cadf3", + "width": 310.60882568359375, + "height": 36.14694148936174, + "seed": 1644147119, + "groupIds": [ + "mkUlC9Ni4aY40KW279Uwk" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "fontSize": 28.91755319148939, + "fontFamily": 1, + "text": "Catalog Write Service", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Catalog Write Service", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "ellipse", + "version": 987, + "versionNonce": 1262207401, + "isDeleted": false, + "id": "MXTPt8G3YcNeaAySVUvSo", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1226.5077887537993, + "y": 364.2721631205677, + "strokeColor": "#1280d9", + "backgroundColor": "#1280d9", + "width": 114.35125379939159, + "height": 106.72783687943213, + "seed": 421501679, + "groupIds": [ + "lkhFcKG2jUyeqevFORepX", + "T7QayivgTe3uuILx5V1Bm" + ], + "roundness": null, + "boundElements": [ + { + "id": "URzPYGpulIEYGYUDzshtn", + "type": "arrow" + }, + { + "id": "8Jf5TQ1IGPxJiYzt4xTyd", + "type": "arrow" + }, + { + "id": "o2h5sdhKIQh3pUi9m9rCD", + "type": "arrow" + } + ], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1196, + "versionNonce": 1754876263, + "isDeleted": false, + "id": "IPfCZBy8QDyCojfoceyU3", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1322.7534273682877, + "y": 382.3777783054712, + "strokeColor": "#6cadf3", + "backgroundColor": "#6cadf3", + "width": 78.1400234295842, + "height": 70.51660650962486, + "seed": 1047465857, + "groupIds": [ + "lkhFcKG2jUyeqevFORepX", + "T7QayivgTe3uuILx5V1Bm" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -51.80458316063338, + 24.37905202528699 + ], + [ + -78.1400234295842, + 70.51660650962486 + ] + ] + }, + { + "type": "line", + "version": 1070, + "versionNonce": 2139595913, + "isDeleted": false, + "id": "3-Iw3_h7q-xYLvXyj0zWl", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1267.6794494762082, + "y": 368.08387158054757, + "strokeColor": "#6cadf3", + "backgroundColor": "#6cadf3", + "width": 65.55617615702322, + "height": 67.65782516464004, + "seed": 1869374735, + "groupIds": [ + "lkhFcKG2jUyeqevFORepX", + "T7QayivgTe3uuILx5V1Bm" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 15.635315541559024, + 42.867497382010704 + ], + [ + 65.55617615702322, + 67.65782516464004 + ] + ] + }, + { + "type": "ellipse", + "version": 1139, + "versionNonce": 1503233159, + "isDeleted": false, + "id": "iNtfjrvYU2tKUBMeeFRfS", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1302.741957953396, + "y": 420.4948629052692, + "strokeColor": "#ffff", + "backgroundColor": "#ffff", + "width": 17.15268806990874, + "height": 17.15268806990874, + "seed": 1472427873, + "groupIds": [ + "lkhFcKG2jUyeqevFORepX", + "T7QayivgTe3uuILx5V1Bm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1270, + "versionNonce": 1242835817, + "isDeleted": false, + "id": "1SkKsyhM4FHNJFql_35E0", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 6.1036791789047005, + "x": 1255.474074486456, + "y": 375.4011465606556, + "strokeColor": "#6cadf3", + "backgroundColor": "#6cadf3", + "width": 57.40757594364031, + "height": 85.83971971041694, + "seed": 299559727, + "groupIds": [ + "lkhFcKG2jUyeqevFORepX", + "T7QayivgTe3uuILx5V1Bm" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.10425479669410705, + 61.20338862096668 + ], + [ + 57.303321146946196, + 85.83971971041694 + ] + ] + }, + { + "type": "ellipse", + "version": 1054, + "versionNonce": 824531879, + "isDeleted": false, + "id": "ymTNdpVgdILOOm6ZK3h96", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1239.8487683637318, + "y": 405.24802906535035, + "strokeColor": "#ffff", + "backgroundColor": "#ffff", + "width": 33.35244902482261, + "height": 33.35244902482261, + "seed": 1040180033, + "groupIds": [ + "lkhFcKG2jUyeqevFORepX", + "T7QayivgTe3uuILx5V1Bm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1108, + "versionNonce": 1663882825, + "isDeleted": false, + "id": "j5U-YvSjNGkc8CY5JvVC9", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1283.6834156534928, + "y": 444.3180407801413, + "strokeColor": "#ffff", + "backgroundColor": "#ffff", + "width": 17.15268806990874, + "height": 17.15268806990874, + "seed": 215199055, + "groupIds": [ + "lkhFcKG2jUyeqevFORepX", + "T7QayivgTe3uuILx5V1Bm" + ], + "roundness": null, + "boundElements": [ + { + "id": "8Jf5TQ1IGPxJiYzt4xTyd", + "type": "arrow" + }, + { + "id": "o2h5sdhKIQh3pUi9m9rCD", + "type": "arrow" + } + ], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1240, + "versionNonce": 1399410375, + "isDeleted": false, + "id": "wnwbcDPYUe2UYXEZzVpQm", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1134.755319148936, + "y": 311.00000000000017, + "strokeColor": "#000000", + "backgroundColor": "#6cadf3", + "width": 298.15313720703125, + "height": 35, + "seed": 495178529, + "groupIds": [ + "T7QayivgTe3uuILx5V1Bm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "fontSize": 27.872340425531945, + "fontFamily": 1, + "text": "Catalog Read Service", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Catalog Read Service", + "lineHeight": 1.2557251908396934, + "baseline": 25 + }, + { + "type": "ellipse", + "version": 1050, + "versionNonce": 1840063785, + "isDeleted": false, + "id": "itFHGRSGVl50S5i425n1o", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1603.7327471899796, + "y": 364.97720976724304, + "strokeColor": "#1280d9", + "backgroundColor": "#1280d9", + "width": 117.88156096366805, + "height": 110.02279023275683, + "seed": 339398529, + "groupIds": [ + "YavOqo5_HznJ7Lg2AgOwl", + "xycg2aY-3kHNqBDXSEu9w" + ], + "roundness": null, + "boundElements": [ + { + "id": "mK60H2Z1O4qzU5bYMtFay", + "type": "arrow" + }, + { + "id": "CqH0rJZl_giwlh370YPWA", + "type": "arrow" + } + ], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1255, + "versionNonce": 1795284455, + "isDeleted": false, + "id": "jjijjBIdMQecm-APFl8sS", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1702.9497276677341, + "y": 383.64179025315696, + "strokeColor": "#6cadf3", + "backgroundColor": "#6cadf3", + "width": 80.55239999183978, + "height": 72.69362926092869, + "seed": 2101126415, + "groupIds": [ + "YavOqo5_HznJ7Lg2AgOwl", + "xycg2aY-3kHNqBDXSEu9w" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -53.403919285055586, + 25.13169389989315 + ], + [ + -80.55239999183978, + 72.69362926092869 + ] + ] + }, + { + "type": "line", + "version": 1129, + "versionNonce": 620795913, + "isDeleted": false, + "id": "AiL2HeS86z1BsSmUdn9NS", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1646.175479317242, + "y": 368.90659513269884, + "strokeColor": "#6cadf3", + "backgroundColor": "#6cadf3", + "width": 67.58005810549504, + "height": 69.74659023683697, + "seed": 2115450721, + "groupIds": [ + "YavOqo5_HznJ7Lg2AgOwl", + "xycg2aY-3kHNqBDXSEu9w" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 16.11801655827829, + 44.190923475683505 + ], + [ + 67.58005810549504, + 69.74659023683697 + ] + ] + }, + { + "type": "ellipse", + "version": 1202, + "versionNonce": 452692231, + "isDeleted": false, + "id": "HtpOliI0zZdwiRs3EywCk", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1682.3204544990945, + "y": 422.93564390771377, + "strokeColor": "#ffff", + "backgroundColor": "#ffff", + "width": 17.68223414455021, + "height": 17.68223414455021, + "seed": 2074290991, + "groupIds": [ + "YavOqo5_HznJ7Lg2AgOwl", + "xycg2aY-3kHNqBDXSEu9w" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1329, + "versionNonce": 1260564201, + "isDeleted": false, + "id": "Zek-hVYkN-RxDdzurfo07", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 6.1036791789047005, + "x": 1633.5932940929065, + "y": 376.4497725618571, + "strokeColor": "#6cadf3", + "backgroundColor": "#6cadf3", + "width": 59.17989036874596, + "height": 88.489805016913, + "seed": 1675338561, + "groupIds": [ + "YavOqo5_HznJ7Lg2AgOwl", + "xycg2aY-3kHNqBDXSEu9w" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -0.10747340115580428, + 63.092889209264946 + ], + [ + 59.07241696759016, + 88.489805016913 + ] + ] + }, + { + "type": "ellipse", + "version": 1113, + "versionNonce": 1892864039, + "isDeleted": false, + "id": "iIZDhacl2qCeA0ZKNpIZB", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1617.4855959690783, + "y": 407.2181024458914, + "strokeColor": "#ffff", + "backgroundColor": "#ffff", + "width": 34.38212194773658, + "height": 34.38212194773658, + "seed": 328461647, + "groupIds": [ + "YavOqo5_HznJ7Lg2AgOwl", + "xycg2aY-3kHNqBDXSEu9w" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 1168, + "versionNonce": 1453255113, + "isDeleted": false, + "id": "OL8YSkmzw1pHU0EYG5Ntz", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1662.6735276718111, + "y": 447.49430244181013, + "strokeColor": "#ffff", + "backgroundColor": "#ffff", + "width": 17.68223414455021, + "height": 17.68223414455021, + "seed": 300389153, + "groupIds": [ + "YavOqo5_HznJ7Lg2AgOwl", + "xycg2aY-3kHNqBDXSEu9w" + ], + "roundness": null, + "boundElements": [ + { + "id": "mK60H2Z1O4qzU5bYMtFay", + "type": "arrow" + } + ], + "updated": 1692970003171, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1338, + "versionNonce": 376714055, + "isDeleted": false, + "id": "TKO_P4ph0usYPoTTvO8qp", + "fillStyle": "solid", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1570.9999999999998, + "y": 311.3489932885907, + "strokeColor": "#000000", + "backgroundColor": "#6cadf3", + "width": 188.98587036132812, + "height": 36.08053691275168, + "seed": 275213167, + "groupIds": [ + "xycg2aY-3kHNqBDXSEu9w" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003171, + "link": null, + "locked": false, + "fontSize": 28.73282878766245, + "fontFamily": 1, + "text": "Order Service", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Order Service", + "lineHeight": 1.255725190839694, + "baseline": 25 + }, + { + "type": "line", + "version": 4060, + "versionNonce": 1620101289, + "isDeleted": false, + "id": "q5EpO5Hja_Quak-jb9Q8k", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 645.9480515210548, + "y": 852.3419983718792, + "strokeColor": "#1280d9", + "backgroundColor": "transparent", + "width": 120.7350260447434, + "height": 121.15643021681711, + "seed": 695754735, + "groupIds": [ + "Cyq7-FVop9xglZxpyq65M" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -12.286147464435611, + -3.6228383548977465 + ], + [ + -21.579515418303775, + -1.5751471108250992 + ], + [ + -30.321581883382887, + 0.630058844330035 + ], + [ + -57.37473351180413, + -4.056003810374615 + ], + [ + -76.1583628083933, + 8.190764976290506 + ], + [ + -75.64643999737507, + 37.488501237637365 + ], + [ + -64.5388401033602, + 67.89165323925961 + ], + [ + -49.887159209989235, + 83.9215878546028 + ], + [ + -35.20453792694092, + 66.90999905769168 + ], + [ + -27.160036610941365, + 67.02813509100373 + ], + [ + -24.392278116205713, + 69.55399599371968 + ], + [ + -27.801346506062657, + 73.40185536444956 + ], + [ + -32.408651805226256, + 75.96146941954038 + ], + [ + -39.89060058164566, + 80.17498794099741 + ], + [ + -28.877697031793357, + 81.64512524443401 + ], + [ + -21.001961477668093, + 81.06757130379833 + ], + [ + -16.1715103378043, + 82.43269879984662 + ], + [ + -13.017349803932964, + 103.47524155106557 + ], + [ + -5.788665632281949, + 116.43098888434183 + ], + [ + 11.19498107678127, + 112.34688063937973 + ], + [ + 16.421070977091663, + 100.60647871551129 + ], + [ + 16.932831441369796, + 82.97087406271174 + ], + [ + 19.17741607429549, + 76.43401355278773 + ], + [ + 32.13300106083193, + 76.2371201639346 + ], + [ + 41.34761165915883, + 69.18833684299231 + ], + [ + 31.069776761025103, + 68.16449122095598 + ], + [ + 24.611673606642125, + 63.51780724402198 + ], + [ + 33.393118749492004, + 47.411928035835246 + ], + [ + 42.52897199227754, + 26.89563691733846 + ], + [ + 44.5766632363501, + 4.882956043557788 + ], + [ + 29.770280394594216, + -3.4653236438152173 + ], + [ + 12.60117688660075, + -4.725441332475279 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1736, + "versionNonce": 1144795751, + "isDeleted": false, + "id": "j8Z46JIV8k8ruio3dcvoq", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 618.552263345394, + "y": 853.1684349479798, + "strokeColor": "#1280d9", + "backgroundColor": "transparent", + "width": 25.85866506937858, + "height": 79.01988005972537, + "seed": 280042113, + "groupIds": [ + "Cyq7-FVop9xglZxpyq65M" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -8.20389120221403, + 8.466415720684859 + ], + [ + -12.273021238512191, + 23.82410005122953 + ], + [ + -10.500980738833976, + 41.47887391839411 + ], + [ + -12.863701405071524, + 51.782961268374805 + ], + [ + -10.304087349980765, + 59.921221340971236 + ], + [ + -4.134761165915806, + 64.12161363650469 + ], + [ + 3.8722366474450545, + 67.53443237662594 + ], + [ + 10.76350525730483, + 69.76589078362798 + ], + [ + 12.994963664307058, + 75.41016793075151 + ], + [ + 9.516513794568286, + 79.01988005972537 + ] + ] + }, + { + "type": "line", + "version": 1175, + "versionNonce": 1386466185, + "isDeleted": false, + "id": "e7Ba7ijVuE8SXovr5XIPH", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 645.4868858504211, + "y": 852.3521984764961, + "strokeColor": "#1280d9", + "backgroundColor": "transparent", + "width": 24.02099344008271, + "height": 63.20277782185689, + "seed": 1650756111, + "groupIds": [ + "Cyq7-FVop9xglZxpyq65M" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 6.6943752210066565, + 1.378253721971954 + ], + [ + 16.932831441369807, + 9.45088266495057 + ], + [ + 21.658272773845088, + 26.18682071746718 + ], + [ + 20.476912440726238, + 42.13518521457133 + ], + [ + 24.02099344008271, + 54.34257532346573 + ], + [ + 21.264485996138784, + 63.20277782185689 + ] + ] + }, + { + "type": "line", + "version": 1874, + "versionNonce": 1928809863, + "isDeleted": false, + "id": "qCcwc9sSQ7yO9tbKnmrg6", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 663.8315128507606, + "y": 930.160053782791, + "strokeColor": "#1280d9", + "backgroundColor": "transparent", + "width": 17.956677063406048, + "height": 54.26381796792451, + "seed": 1267475041, + "groupIds": [ + "Cyq7-FVop9xglZxpyq65M" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.4069130036298153, + -7.206298032024968 + ], + [ + 3.3406244975415853, + -10.27783489813391 + ], + [ + 7.061909546865838, + -11.852982008958904 + ], + [ + 2.6121189587849574, + -14.963897552838684 + ], + [ + -1.050098073883434, + -21.46137938499201 + ], + [ + -4.948587173175543, + -30.40033923892453 + ], + [ + -10.894767516540211, + -44.891692658515225 + ], + [ + -8.335153461449469, + -53.751895156906755 + ], + [ + 2.7696336698674595, + -54.26381796792451 + ] + ] + }, + { + "type": "line", + "version": 1334, + "versionNonce": 1427337833, + "isDeleted": false, + "id": "m3Ti7dnDNtjDnzcuhoeSs", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 606.6754703144845, + "y": 883.455731658437, + "strokeColor": "#1280d9", + "backgroundColor": "transparent", + "width": 19.098658718754248, + "height": 40.56003810374615, + "seed": 478572591, + "groupIds": [ + "Cyq7-FVop9xglZxpyq65M" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 6.431850702535803, + -3.2815564808855897 + ], + [ + 14.570110775132113, + -4.134761165915864 + ], + [ + 19.098658718754248, + 2.55961405509079 + ], + [ + 19.098658718754248, + 15.751471108250957 + ], + [ + 16.342151274810366, + 25.596140550907755 + ], + [ + 14.767004163985257, + 36.42527693783029 + ] + ] + }, + { + "type": "line", + "version": 1413, + "versionNonce": 1306657959, + "isDeleted": false, + "id": "_kBOqQfEZYq2syIwitfpg", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 6.251548691252406, + "x": 659.329149788931, + "y": 878.4530317728684, + "strokeColor": "#1280d9", + "backgroundColor": "transparent", + "width": 6.227705170826862, + "height": 3.8852837370012887, + "seed": 695938625, + "groupIds": [ + "Cyq7-FVop9xglZxpyq65M" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.1577096640812266, + 1.6512455882255455 + ], + [ + 0.17542831467117817, + 3.8852837370012887 + ], + [ + 3.0699955067456366, + 0.6799246539752241 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1539, + "versionNonce": 2091597129, + "isDeleted": false, + "id": "2Hwe06Bz7PuEXQRG5thT5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0.25834385002361504, + "x": 618.5264678199959, + "y": 881.8506057257152, + "strokeColor": "#1280d9", + "backgroundColor": "transparent", + "width": 6.6159089587762745, + "height": 3.6534651106624025, + "seed": 566661711, + "groupIds": [ + "Cyq7-FVop9xglZxpyq65M" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -3.2359225085620515, + 1.5684364665730517 + ], + [ + -0.3875777579314346, + 3.6534651106624025 + ], + [ + 2.370656349383188, + 3.0509712622191048 + ], + [ + 3.3799864502142225, + 0.11909497754412564 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 141, + "versionNonce": 1688786887, + "isDeleted": false, + "id": "XRzlZvLfRV1ctNVP5oCV6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 588.5600509643555, + "y": 980, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86.87989807128906, + "height": 25, + "seed": 1349921903, + "groupIds": [], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Postgres", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Postgres", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 844, + "versionNonce": 952544297, + "isDeleted": false, + "id": "7mWVcUYEIq6l4O9wPbigM", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 711.6328923088969, + "y": 357.8446619256966, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 533.3671076911029, + "height": 353.51132859236304, + "seed": 1925962447, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "kcM7CO0USSoquYtnbcASE" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": { + "elementId": "xsYLZ_oJEXz46VEKvxb6P", + "focus": -0.04069813800123727, + "gap": 2.027469040964476 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 170.03377435776997, + -267.01132859236316 + ], + [ + 533.3671076911029, + -353.51132859236304 + ] + ] + }, + { + "id": "kcM7CO0USSoquYtnbcASE", + "type": "text", + "x": 750.3267771402997, + "y": 78.33333333333343, + "width": 262.6797790527344, + "height": 25, + "angle": 0, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 1724427977, + "version": 41, + "versionNonce": 637653001, + "isDeleted": false, + "boundElements": null, + "updated": 1692970037482, + "link": null, + "locked": false, + "text": "Produce Integration Event", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "7mWVcUYEIq6l4O9wPbigM", + "originalText": "Produce Integration Event", + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 759, + "versionNonce": 1722601991, + "isDeleted": false, + "id": "URzPYGpulIEYGYUDzshtn", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1278.798021003988, + "y": 362.0369966854631, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "width": 27.078680913577955, + "height": 297.70366335212975, + "seed": 427743151, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "_wMxaHslKsv1QTYdXlCol" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": { + "elementId": "MXTPt8G3YcNeaAySVUvSo", + "focus": -0.08420342568578082, + "gap": 2.422873728717974 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.199280425686311, + -156.03699668546312 + ], + [ + 26.879400487891644, + -297.70366335212975 + ] + ] + }, + { + "id": "_wMxaHslKsv1QTYdXlCol", + "type": "text", + "x": 1173.7688302999813, + "y": 181, + "width": 209.65982055664062, + "height": 50, + "angle": 0, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 1863816839, + "version": 21, + "versionNonce": 1153857001, + "isDeleted": false, + "boundElements": null, + "updated": 1692970003172, + "link": null, + "locked": false, + "text": "Consume Integration \nEvent", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 43, + "containerId": "URzPYGpulIEYGYUDzshtn", + "originalText": "Consume Integration Event", + "lineHeight": 1.25 + }, + { + "type": "diamond", + "version": 632, + "versionNonce": 456052649, + "isDeleted": false, + "id": "3JwXyvBM1R3-K8qaNX-8Q", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1126.7857790804385, + "y": 885.7085227074999, + "strokeColor": "#000000", + "backgroundColor": "#c6302b", + "width": 144.4017209919198, + "height": 59.418889977536374, + "seed": 1806078465, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1938, + "versionNonce": 262544231, + "isDeleted": false, + "id": "kE7wT0uZ6iGW9Fq0e6DYP", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1125.8875865575221, + "y": 915.0897819667409, + "strokeColor": "#000000", + "backgroundColor": "#912626", + "width": 143.83171419853068, + "height": 39.79683793844296, + "seed": 1967409807, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 5.078242341103406 + ], + [ + 3.005490365142849, + 9.880117752078725 + ], + [ + 30.68536571078297, + 22.325266074408653 + ], + [ + 61.059818625171836, + 35.89315505038389 + ], + [ + 73.1335988851422, + 39.79683793844296 + ], + [ + 84.07600202490063, + 36.15224904737897 + ], + [ + 115.00318879954577, + 23.301186796423416 + ], + [ + 140.64485803549132, + 10.709218542462938 + ], + [ + 143.83171419853068, + 5.527338602561531 + ], + [ + 142.72624647801834, + 0.31091279639407987 + ], + [ + 130.9288331481761, + 5.803705532689593 + ], + [ + 79.83549694074804, + 26.790319289290373 + ], + [ + 65.41259777468906, + 26.272131295300245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1854, + "versionNonce": 971567753, + "isDeleted": false, + "id": "_ZD00mNdYMgbZMgjz7riJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1125.663038426793, + "y": 892.8595170245638, + "strokeColor": "#000000", + "backgroundColor": "#912626", + "width": 143.83171419853068, + "height": 39.79683793844296, + "seed": 669428193, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 5.078242341103406 + ], + [ + 3.005490365142849, + 9.880117752078725 + ], + [ + 30.68536571078297, + 22.325266074408653 + ], + [ + 61.059818625171836, + 35.89315505038389 + ], + [ + 73.1335988851422, + 39.79683793844296 + ], + [ + 84.07600202490063, + 36.15224904737897 + ], + [ + 115.00318879954577, + 23.301186796423416 + ], + [ + 140.64485803549132, + 10.709218542462938 + ], + [ + 143.83171419853068, + 5.527338602561531 + ], + [ + 142.72624647801834, + 0.31091279639407987 + ], + [ + 130.9288331481761, + 5.803705532689593 + ], + [ + 79.83549694074804, + 26.790319289290373 + ], + [ + 65.41259777468906, + 26.272131295300245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "diamond", + "version": 547, + "versionNonce": 1799100039, + "isDeleted": false, + "id": "_hC--F17sDDZ6upwQQyMB", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1125.8875865575226, + "y": 862.8046133731356, + "strokeColor": "#000000", + "backgroundColor": "#c6302b", + "width": 144.4017209919198, + "height": 59.418889977536374, + "seed": 781852847, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false + }, + { + "type": "diamond", + "version": 422, + "versionNonce": 119257449, + "isDeleted": false, + "id": "v3N7IFZQ45GBvlWdqV4k8", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1124.9698483471136, + "y": 839.1943319838058, + "strokeColor": "#000000", + "backgroundColor": "#c6302b", + "width": 144.4017209919198, + "height": 59.418889977536374, + "seed": 941147585, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": null, + "boundElements": [ + { + "id": "o2h5sdhKIQh3pUi9m9rCD", + "type": "arrow" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1721, + "versionNonce": 1420144039, + "isDeleted": false, + "id": "06TLdbEYuOP5ieXzjM4Gm", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1124.7453002163845, + "y": 869.2492356352341, + "strokeColor": "#000000", + "backgroundColor": "#912626", + "width": 143.83171419853068, + "height": 39.79683793844296, + "seed": 118502095, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 5.078242341103406 + ], + [ + 3.005490365142849, + 9.880117752078725 + ], + [ + 30.68536571078297, + 22.325266074408653 + ], + [ + 61.059818625171836, + 35.89315505038389 + ], + [ + 73.1335988851422, + 39.79683793844296 + ], + [ + 84.07600202490063, + 36.15224904737897 + ], + [ + 115.00318879954577, + 23.301186796423416 + ], + [ + 140.64485803549132, + 10.709218542462938 + ], + [ + 143.83171419853068, + 5.527338602561531 + ], + [ + 142.72624647801834, + 0.31091279639407987 + ], + [ + 130.9288331481761, + 5.803705532689593 + ], + [ + 79.83549694074804, + 26.790319289290373 + ], + [ + 65.41259777468906, + 26.272131295300245 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 348, + "versionNonce": 1947378761, + "isDeleted": false, + "id": "O_4CR00jnWWFidlDGhhnp", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1150.8106376032472, + "y": 861.7381300744622, + "strokeColor": "#000000", + "backgroundColor": "#ffff", + "width": 31.172564030622752, + "height": 13.737062115189687, + "seed": 1465673121, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": null, + "boundElements": [ + { + "id": "o2h5sdhKIQh3pUi9m9rCD", + "type": "arrow" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 407, + "versionNonce": 118448327, + "isDeleted": false, + "id": "tVVOm7c92hXdnQhq40M0g", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1179.6320506180007, + "y": 882.5659129293203, + "strokeColor": "#000000", + "backgroundColor": "#ffff", + "width": 26.950796760272294, + "height": 12.680283220451338, + "seed": 658440431, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.012967891291325, + 6.685967516237976 + ], + [ + 26.950796760272294, + -5.994315704213363 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 664, + "versionNonce": 1017721641, + "isDeleted": false, + "id": "BzpQx5uCILvmYIzEWAkWK", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1189.5121683700795, + "y": 848.2652422307185, + "strokeColor": "#000000", + "backgroundColor": "#ffff", + "width": 26.0343744509989, + "height": 17.726093614023608, + "seed": 820054401, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.483238151591967, + 5.481616132503589 + ], + [ + -4.794763026744117, + 9.76123932992805 + ], + [ + 4.068283780267686, + 10.606596998555105 + ], + [ + 9.034760083451673, + 16.40522225679383 + ], + [ + 10.32921401353685, + 9.589526053488182 + ], + [ + 21.239611424254782, + 8.01768913838475 + ], + [ + 11.914259642212567, + 5.138189579623828 + ], + [ + 14.793759200973467, + -1.3208713572297748 + ], + [ + 6.934574625456297, + 2.2718987344352164 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 599, + "versionNonce": 605401063, + "isDeleted": false, + "id": "cPkSbVyiMu2J_YyyX2ZaJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1230.4246345779368, + "y": 861.1752356653041, + "strokeColor": "#000000", + "backgroundColor": "#912626", + "width": 33.682219609359336, + "height": 13.472887843743717, + "seed": 279907087, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 16.84110980467964, + 6.736443921871858 + ], + [ + -0.6736443921871856, + 13.472887843743717 + ], + [ + -16.841109804679697, + 6.736443921871858 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 328, + "versionNonce": 357230089, + "isDeleted": false, + "id": "b3FTR8vxKo3DumCalrxC5", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1228.6926172884644, + "y": 860.7685968320201, + "strokeColor": "#000000", + "backgroundColor": "#912626", + "width": 0.34545866266017383, + "height": 13.472887843743717, + "seed": 1575367009, + "groupIds": [ + "g2OI5KnTnxEHstsEnQ_C1" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.34545866266017383, + 13.472887843743717 + ] + ] + }, + { + "type": "text", + "version": 170, + "versionNonce": 1707416327, + "isDeleted": false, + "id": "yJ4lNmGpYTTneDB1uR3LX", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1279.2593816121419, + "y": -100.66666666666654, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 232.1479034423828, + "height": 35, + "seed": 252828591, + "groupIds": [], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 1, + "text": "RabbitMQ Broker", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "RabbitMQ Broker", + "lineHeight": 1.25, + "baseline": 25 + }, + { + "type": "text", + "version": 121, + "versionNonce": 418594025, + "isDeleted": false, + "id": "1lnHQgiitIu_ZS2Pmow3P", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1171.4400253295898, + "y": 974, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 51.11994934082031, + "height": 25, + "seed": 961986031, + "groupIds": [], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Redis", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "Redis", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "line", + "version": 2867, + "versionNonce": 278536743, + "isDeleted": false, + "id": "cva63NqJgIYF1_VRe0icU", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1367.094037390933, + "y": 851.0435395468419, + "strokeColor": "#2b8a3e", + "backgroundColor": "#40c057", + "width": 109.97416286190399, + "height": 127.52177230943171, + "seed": 413927137, + "groupIds": [ + "AWxB6as7PTK8Y_kbUqYv9", + "E3peb8QraAMznZp4X0g7G" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 88.0866670021975 + ], + [ + 1.9267089457977635, + 104.01594553464969 + ], + [ + 15.152421669615407, + 109.86860993434695 + ], + [ + 34.96137194079894, + 114.49652898419295 + ], + [ + 56.08506768192033, + 115.69166104363116 + ], + [ + 74.11972012874104, + 114.50003475943318 + ], + [ + 91.03482651513082, + 111.55421874068364 + ], + [ + 107.90637384083935, + 103.38814878372659 + ], + [ + 109.05968295510166, + 88.0866670021975 + ], + [ + 109.97416286190399, + 11.998409266661323 + ], + [ + 108.58558468592169, + -0.38357984313644206 + ], + [ + 94.9159541182098, + -6.988847587971679 + ], + [ + 79.57444171674146, + -10.730609044852848 + ], + [ + 59.62981384421779, + -11.830111265800559 + ], + [ + 46.97119225364777, + -11.798113765159314 + ], + [ + 20.01546869661114, + -9.41820976261782 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1120, + "versionNonce": 1999484873, + "isDeleted": false, + "id": "W-nygccyDNUV9AvsfLnUE", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1366.689125966406, + "y": 837.680813228588, + "strokeColor": "#2b8a3e", + "backgroundColor": "#ffffff", + "width": 109.77944901221812, + "height": 29.84847000034108, + "seed": 488969135, + "groupIds": [ + "AWxB6as7PTK8Y_kbUqYv9", + "E3peb8QraAMznZp4X0g7G" + ], + "roundness": null, + "boundElements": [ + { + "id": "8Jf5TQ1IGPxJiYzt4xTyd", + "type": "arrow" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 356, + "versionNonce": 629228871, + "isDeleted": false, + "id": "iSuAFtVBBL81i5GHJ4b0g", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1375.4622144725652, + "y": 985.7972036794578, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 94.28628540039062, + "height": 28.312777028376033, + "seed": 36340417, + "groupIds": [ + "CuoKl1AWn1aPO3WoXY-MA", + "E3peb8QraAMznZp4X0g7G" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "fontSize": 21.684194049327424, + "fontFamily": 1, + "text": "MongoDB", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "MongoDB", + "lineHeight": 1.3056873114107834, + "baseline": 19 + }, + { + "type": "arrow", + "version": 249, + "versionNonce": 1645598377, + "isDeleted": false, + "id": "8Jf5TQ1IGPxJiYzt4xTyd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1309.6400592291293, + "y": 462.0975027753551, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 132.3599407708707, + "height": 374.5833363442241, + "seed": 1458746927, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "Qr2rEKuG-6ovFcnfRedGh" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": { + "elementId": "j5U-YvSjNGkc8CY5JvVC9", + "focus": -1.038803594126667, + "gap": 11.0901806278907 + }, + "endBinding": { + "elementId": "W-nygccyDNUV9AvsfLnUE", + "focus": -0.033713326985435256, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 132.3599407708707, + 187.9024972246449 + ], + [ + 111.83800718557154, + 374.5833363442241 + ] + ] + }, + { + "id": "Qr2rEKuG-6ovFcnfRedGh", + "type": "text", + "x": 1356.9500579833984, + "y": 637.5, + "width": 170.09988403320312, + "height": 25, + "angle": 0, + "strokeColor": "#5c940d", + "backgroundColor": "#5c940d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 243198697, + "version": 11, + "versionNonce": 1406600295, + "isDeleted": false, + "boundElements": null, + "updated": 1692970003172, + "link": null, + "locked": false, + "text": "Save Read Model", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "8Jf5TQ1IGPxJiYzt4xTyd", + "originalText": "Save Read Model", + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 390, + "versionNonce": 1012341129, + "isDeleted": false, + "id": "o2h5sdhKIQh3pUi9m9rCD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1271.5495167896563, + "y": 470.0054995356029, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 69.66479398645367, + "height": 363.86405471348826, + "seed": 1763561711, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "5wuSixt5qTnvhySJlonqv" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": { + "elementId": "MXTPt8G3YcNeaAySVUvSo", + "focus": -0.10591297443641792, + "gap": 1 + }, + "endBinding": { + "elementId": "v3N7IFZQ45GBvlWdqV4k8", + "focus": 0.060687768914183544, + "gap": 6.718003913091579 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -68.03932822880226, + 192.4945004643971 + ], + [ + -69.66479398645367, + 363.86405471348826 + ] + ] + }, + { + "id": "5wuSixt5qTnvhySJlonqv", + "type": "text", + "x": 1118.4602465442524, + "y": 650, + "width": 170.09988403320312, + "height": 25, + "angle": 0, + "strokeColor": "#d9480f", + "backgroundColor": "#5c940d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 494651847, + "version": 11, + "versionNonce": 728227719, + "isDeleted": false, + "boundElements": null, + "updated": 1692970003172, + "link": null, + "locked": false, + "text": "Save Read Model", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "o2h5sdhKIQh3pUi9m9rCD", + "originalText": "Save Read Model", + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 2754, + "versionNonce": 274926409, + "isDeleted": false, + "id": "mKh1lSJ8a5TfO5qs1sKIf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1602.8620377377417, + "y": 853.3861101483237, + "strokeColor": "#364fc7", + "backgroundColor": "#228be6", + "width": 109.41316656156475, + "height": 126.87126276594809, + "seed": 1386476943, + "groupIds": [ + "bZOSPd5YBUOpQ8WwP5VO0", + "mcwzN6ShiE1CJVBRt5Pz_" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 87.63732241969315 + ], + [ + 1.916880486436992, + 103.48534308128525 + ], + [ + 15.075126673439726, + 109.30815207685929 + ], + [ + 34.78302822984747, + 113.91246335013604 + ], + [ + 55.79896852321755, + 115.10149884428735 + ], + [ + 73.74162324041447, + 113.91595124183992 + ], + [ + 90.57044288584926, + 110.98516231531816 + ], + [ + 107.35592567253869, + 102.86074882488877 + ], + [ + 108.50335156723915, + 87.63732241969315 + ], + [ + 109.41316656156475, + 11.937203406726702 + ], + [ + 108.03167175134023, + -0.3816231392408693 + ], + [ + 94.43177221840894, + -6.953196326453007 + ], + [ + 79.1685193960532, + -10.675870442458502 + ], + [ + 59.32563235207468, + -11.769763921660743 + ], + [ + 46.731584473137474, + -11.737929645535264 + ], + [ + 19.9133664973659, + -9.370165924909484 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1009, + "versionNonce": 1671227847, + "isDeleted": false, + "id": "80yiES7skMN669M7sWIKg", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1602.4591918330166, + "y": 840.0915492957746, + "strokeColor": "#364fc7", + "backgroundColor": "#ffffff", + "width": 109.21944597926513, + "height": 29.696208043484862, + "seed": 300625633, + "groupIds": [ + "bZOSPd5YBUOpQ8WwP5VO0", + "mcwzN6ShiE1CJVBRt5Pz_" + ], + "roundness": null, + "boundElements": [ + { + "id": "mK60H2Z1O4qzU5bYMtFay", + "type": "arrow" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 267, + "versionNonce": 1616165417, + "isDeleted": false, + "id": "9keLFFDl4MqK2dG4CleRh", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1584.651266317369, + "y": 983.5330257406508, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 150.70948791503906, + "height": 26.966974259349197, + "seed": 952921007, + "groupIds": [ + "OtCJ3M-FkN0R-MimbGpEt", + "mcwzN6ShiE1CJVBRt5Pz_" + ], + "roundness": null, + "boundElements": [ + { + "id": "e1jYVwg7QVn9ejhQMKUl-", + "type": "arrow" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false, + "fontSize": 21.57357940747936, + "fontFamily": 1, + "text": "EventStoreDB", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "EventStoreDB", + "lineHeight": 1.2499999999999998, + "baseline": 18 + }, + { + "type": "line", + "version": 3054, + "versionNonce": 134879463, + "isDeleted": false, + "id": "I9sawBLrz7V17tMZI8-hO", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2067.769207004556, + "y": 850.3487845605898, + "strokeColor": "#2b8a3e", + "backgroundColor": "#40c057", + "width": 104.21639803722688, + "height": 120.84529161727384, + "seed": 756203777, + "groupIds": [ + "GZG5GjblfaBlPYnPT5FeP", + "sVVmmId7HZ5mmLBeMUAQc" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 83.47483546295521 + ], + [ + 1.8258349158727951, + 98.57012683669807 + ], + [ + 14.359107328977595, + 104.11637139800536 + ], + [ + 33.13094784531426, + 108.50199290428999 + ], + [ + 53.14869952520977, + 109.63455309090278 + ], + [ + 70.23913666926, + 108.50531513251485 + ], + [ + 86.26864227431425, + 105.71372912026263 + ], + [ + 102.25686938005599, + 97.97519877015905 + ], + [ + 103.34979628746834, + 83.47483546295521 + ], + [ + 104.21639803722688, + 11.370225182055817 + ], + [ + 102.90051972428445, + -0.3634972849173858 + ], + [ + 89.94657105858381, + -6.62294218110277 + ], + [ + 75.40827295912787, + -10.16880141933449 + ], + [ + 56.5078583255801, + -11.210738526371067 + ], + [ + 44.51198664122765, + -11.180416274523498 + ], + [ + 18.967546542791364, + -8.92511360738065 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1309, + "versionNonce": 1743621385, + "isDeleted": false, + "id": "tJ2nfHkhTKq1pY5kw9bcf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2067.385494966289, + "y": 837.6856719839166, + "strokeColor": "#2b8a3e", + "backgroundColor": "#ffffff", + "width": 104.03187855070256, + "height": 28.28573502545247, + "seed": 1932954511, + "groupIds": [ + "GZG5GjblfaBlPYnPT5FeP", + "sVVmmId7HZ5mmLBeMUAQc" + ], + "roundness": null, + "boundElements": [ + { + "id": "8Jf5TQ1IGPxJiYzt4xTyd", + "type": "arrow" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 544, + "versionNonce": 21259271, + "isDeleted": false, + "id": "E_Lpkcv3hhyqzX50mWMYs", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2075.7099977241805, + "y": 978.0473378640065, + "strokeColor": "#000000", + "backgroundColor": "#228be6", + "width": 89.32839965820312, + "height": 26.830444202004685, + "seed": 1777662177, + "groupIds": [ + "iIQqp6giH9lFNQrWQzJRJ", + "sVVmmId7HZ5mmLBeMUAQc" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003172, + "link": null, + "locked": false, + "fontSize": 20.54890475500955, + "fontFamily": 1, + "text": "MongoDB", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "MongoDB", + "lineHeight": 1.3056873114107836, + "baseline": 18 + }, + { + "type": "arrow", + "version": 267, + "versionNonce": 1856570345, + "isDeleted": false, + "id": "mK60H2Z1O4qzU5bYMtFay", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1668.0683226009858, + "y": 470.9971881541178, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 15.401655934319479, + "height": 364.11128275492615, + "seed": 460035105, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "-NL7xguoXN0G5jx9F1dRr" + } + ], + "updated": 1692970003172, + "link": null, + "locked": false, + "startBinding": { + "elementId": "OL8YSkmzw1pHU0EYG5Ntz", + "focus": 0.23586464457412645, + "gap": 6.220243954135039 + }, + "endBinding": { + "elementId": "80yiES7skMN669M7sWIKg", + "focus": 0.1494208242360312, + "gap": 5.102921214054394 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -15.401655934319479, + 167.0028118458822 + ], + [ + -3.98700071839221, + 364.11128275492615 + ] + ] + }, + { + "id": "-NL7xguoXN0G5jx9F1dRr", + "type": "text", + "x": 1567.1567331949866, + "y": 625.5, + "width": 171.01986694335938, + "height": 25, + "angle": 0, + "strokeColor": "#364fc7", + "backgroundColor": "#5c940d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 863254313, + "version": 32, + "versionNonce": 1867008807, + "isDeleted": false, + "boundElements": null, + "updated": 1692970003172, + "link": null, + "locked": false, + "text": "Save Write Model", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "mK60H2Z1O4qzU5bYMtFay", + "originalText": "Save Write Model", + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 837, + "versionNonce": 1626226825, + "isDeleted": false, + "id": "CqH0rJZl_giwlh370YPWA", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1670.5296742332819, + "y": 364.5448420364169, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 120.84778808250144, + "height": 351.21150870308344, + "seed": 612938319, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "XeHygsltvkKIPJTmZ8iEa" + } + ], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": { + "elementId": "itFHGRSGVl50S5i425n1o", + "focus": -0.08056864933759697, + "gap": 1 + }, + "endBinding": { + "elementId": "7svSXNl1rfgZN1h8fLJoI", + "focus": -0.7891335971504826, + "gap": 9.839495660052705 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 34.65319750542267, + -151.13564406344005 + ], + [ + -86.19459057707877, + -351.21150870308344 + ] + ] + }, + { + "id": "XeHygsltvkKIPJTmZ8iEa", + "type": "text", + "x": 1601.6029614603842, + "y": 188.40919797297687, + "width": 207.15982055664062, + "height": 50, + "angle": 0, + "strokeColor": "#c92a2a", + "backgroundColor": "#4c6ef5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 507473577, + "version": 65, + "versionNonce": 264884487, + "isDeleted": false, + "boundElements": null, + "updated": 1692970037485, + "link": null, + "locked": false, + "text": "Produce Integration \nEvent", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 43, + "containerId": "CqH0rJZl_giwlh370YPWA", + "originalText": "Produce Integration Event", + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 293, + "versionNonce": 1145175975, + "isDeleted": false, + "id": "NP4yMKNKEebPmY70NYaPO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1718.8095042411683, + "y": 906.6547346303776, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 343, + "height": 5, + "seed": 89518742, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "AGFkVQdKXCt3HsTgXGnUe" + } + ], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 145, + 3 + ], + [ + 343, + 5 + ] + ] + }, + { + "id": "AGFkVQdKXCt3HsTgXGnUe", + "type": "text", + "x": 1766.3395793144105, + "y": 897.1547346303776, + "width": 194.93984985351562, + "height": 25, + "angle": 0, + "strokeColor": "#364fc7", + "backgroundColor": "#5c940d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 148510823, + "version": 6, + "versionNonce": 412906057, + "isDeleted": false, + "boundElements": null, + "updated": 1692970003173, + "link": null, + "locked": false, + "text": "Project Read Model", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "NP4yMKNKEebPmY70NYaPO", + "originalText": "Project Read Model", + "lineHeight": 1.25 + }, + { + "type": "freedraw", + "version": 381, + "versionNonce": 592540359, + "isDeleted": false, + "id": "zRve1bvhcB4RbDTvS1exL", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2107.7015310952524, + "y": 1221.6953371270122, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "width": 0.4261048915720485, + "height": 0.4261048915720485, + "seed": 419185814, + "groupIds": [ + "HgiQ4Iu13YrXwAPHn8jQm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.42610489157204856, + 0.42610489157204856, + 0.5 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "lastCommittedPoint": null, + "simulatePressure": true, + "pressures": [] + }, + { + "type": "line", + "version": 387, + "versionNonce": 180961577, + "isDeleted": false, + "id": "Yl0jjWQFvdOabb_IqY3P3", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2185.2474824787937, + "y": 1161.8050193824026, + "strokeColor": "#00000000", + "backgroundColor": "#FFF", + "width": 141.0236777754967, + "height": 140.44737032658887, + "seed": 927496138, + "groupIds": [ + "J2G9rIGkbjTcJWAqXpmFz", + "HgiQ4Iu13YrXwAPHn8jQm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.08542331960179972, + -2.2032171408385177 + ], + [ + -0.33796057947009217, + -4.3619193247543535 + ], + [ + -0.7520547660962698, + -6.469355747137605 + ], + [ + -1.3221320520132611, + -8.518788213847877 + ], + [ + -2.0426438306922705, + -10.503465920275413 + ], + [ + -2.908016274666193, + -12.416646468789924 + ], + [ + -3.9126755564673035, + -14.251583258271376 + ], + [ + -5.051064662587557, + -16.00152968759985 + ], + [ + -6.317618172539197, + -17.659739155655224 + ], + [ + -7.706770665834439, + -19.219473468297167 + ], + [ + -9.212948315005887, + -20.67397761742594 + ], + [ + -10.830602513524857, + -22.01651340890127 + ], + [ + -12.554151026944378, + -23.240330038113186 + ], + [ + -14.378028434776834, + -24.338685107431573 + ], + [ + -16.29668613049342, + -25.304832015736437 + ], + [ + -18.30454187964732, + -26.13202836539742 + ], + [ + -17.784183471018558, + -29.915253250156347 + ], + [ + -17.614185936753298, + -33.73031536661795 + ], + [ + -17.819946762189375, + -37.80119346498739 + ], + [ + -18.42384532699213, + -41.7548635556746 + ], + [ + -19.405814170882998, + -45.5712560766562 + ], + [ + -20.74578583358373, + -49.23030356765368 + ], + [ + -22.42370126179524, + -52.711940670133586 + ], + [ + -24.419484588259614, + -55.996102025562 + ], + [ + -26.713076759677776, + -59.06271912278831 + ], + [ + -29.284410315771495, + -61.89172975589619 + ], + [ + -32.11342620324167, + -64.46306436286226 + ], + [ + -35.1800401478505, + -66.75665811058926 + ], + [ + -38.46420150327894, + -68.75244511510698 + ], + [ + -41.94584280924863, + -70.4303581788555 + ], + [ + -45.604888198501264, + -71.77033194330113 + ], + [ + -49.42127861773796, + -72.75229973631966 + ], + [ + -53.374946606680204, + -73.35619573962086 + ], + [ + -57.44582470504955, + -73.56195354379868 + ], + [ + -59.83143967504692, + -73.49087515855635 + ], + [ + -62.19275847403252, + -73.27932619335283 + ], + [ + -64.52439222809015, + -72.92983609817212 + ], + [ + -66.82095206330317, + -72.44493412595911 + ], + [ + -69.07704069877559, + -71.82714972669764 + ], + [ + -71.2872566501218, + -71.0790123503719 + ], + [ + -73.44621104342536, + -70.2030510528877 + ], + [ + -75.54851080128, + -69.20179554694758 + ], + [ + -77.58875023580971, + -68.07777449438076 + ], + [ + -79.56154467658833, + -66.83351839604347 + ], + [ + -81.46149263923013, + -65.47155565104725 + ], + [ + -83.28320104632864, + -63.994416234811965 + ], + [ + -85.02127682047778, + -62.40462907188492 + ], + [ + -86.67031847729153, + -60.704723086813786 + ], + [ + -88.22493714285369, + -58.89722878045518 + ], + [ + -89.6797313327783, + -56.984674551919746 + ], + [ + -91.0977870218999, + -57.999009216355276 + ], + [ + -92.58465804244554, + -58.88245561557259 + ], + [ + -94.13360620027491, + -59.63367178545505 + ], + [ + -95.73788069077844, + -60.25131576188532 + ], + [ + -97.39073070934579, + -60.73404242812981 + ], + [ + -99.08541806183713, + -61.08050982007115 + ], + [ + -100.81518774015241, + -61.28937492272031 + ], + [ + -102.57329314317178, + -61.35929577196055 + ], + [ + -104.72732524992601, + -61.250418030178366 + ], + [ + -106.82181912723227, + -60.930753140038355 + ], + [ + -108.84581207370687, + -60.41075623153213 + ], + [ + -110.78834559145575, + -59.70088033290534 + ], + [ + -112.63846118258478, + -58.81158057414916 + ], + [ + -114.38519614571028, + -57.753309983510256 + ], + [ + -116.01759408468317, + -56.536522640107 + ], + [ + -117.52469019637462, + -55.17167157218579 + ], + [ + -118.89552808463544, + -53.66921401148224 + ], + [ + -120.1191471498271, + -52.0396008844983 + ], + [ + -121.18458679231078, + -50.29328627035201 + ], + [ + -122.0808864124468, + -48.440726349907685 + ], + [ + -122.79708751234168, + -46.4923709987937 + ], + [ + -123.32222949235651, + -44.45868060136342 + ], + [ + -123.64535175285233, + -42.35010293150066 + ], + [ + -123.7554957959351, + -40.17709417006977 + ], + [ + -123.67094049697577, + -38.281122708773395 + ], + [ + -123.42450565341662, + -36.40674037106143 + ], + [ + -123.02703942154446, + -34.57553628051734 + ], + [ + -122.4893899576465, + -32.80910796770507 + ], + [ + -124.51871187830787, + -32.018603880459345 + ], + [ + -126.45867391117505, + -31.077165085166037 + ], + [ + -128.3035403944233, + -29.99306404974037 + ], + [ + -130.04757671710072, + -28.774560631628688 + ], + [ + -131.68504721738287, + -27.429914688276646 + ], + [ + -133.210217284318, + -25.967398687599484 + ], + [ + -134.6173517815173, + -24.39526828355403 + ], + [ + -135.90071557259333, + -22.721791740565415 + ], + [ + -137.05457378387504, + -20.95522891607979 + ], + [ + -138.0731910162566, + -19.10384807452244 + ], + [ + -138.95083265878597, + -17.175909073339746 + ], + [ + -139.6817634437158, + -15.179675973467404 + ], + [ + -140.26024843169637, + -13.123417039330988 + ], + [ + -140.68055248633934, + -11.015387924886653 + ], + [ + -140.93694061082584, + -8.8638568945603 + ], + [ + -141.0236777754967, + -6.677083805797906 + ], + [ + -140.93823380789314, + -4.472479513326803 + ], + [ + -140.68550035506485, + -2.310036223493796 + ], + [ + -140.27087511661466, + -0.1968494270717258 + ], + [ + -139.6997558249854, + 1.8600106061051775 + ], + [ + -138.97754005663094, + 3.8534567922438328 + ], + [ + -138.10962555220385, + 5.776410454530307 + ], + [ + -137.1014098553186, + 7.621784509171192 + ], + [ + -135.95829077230712, + 9.382500279352854 + ], + [ + -134.6856663722199, + 11.051470681282012 + ], + [ + -133.28893393595297, + 12.621625445124081 + ], + [ + -131.77349074440224, + 14.085869080106711 + ], + [ + -130.1447351293362, + 15.437122909415294 + ], + [ + -128.4080643716509, + 16.668316663216707 + ], + [ + -126.56887680311485, + 17.772346443757534 + ], + [ + -124.63256970462407, + 18.742150388183454 + ], + [ + -122.60454035707446, + 19.570641412701107 + ], + [ + -123.12223585491839, + 23.28334855266911 + ], + [ + -123.29489209647852, + 27.168373553268836 + ], + [ + -123.08916279721606, + 31.218990830829128 + ], + [ + -122.48546389817835, + 35.15494531377811 + ], + [ + -121.50403099923622, + 38.95598458828551 + ], + [ + -120.1650975985149, + 42.60188146146134 + ], + [ + -118.48889929588435, + 46.07240033343478 + ], + [ + -116.49566958947011, + 49.347297197356255 + ], + [ + -114.20564618088721, + 52.406353267314984 + ], + [ + -111.6390625682612, + 55.229307722501765 + ], + [ + -108.81615224971773, + 57.79595018398469 + ], + [ + -105.75715502861613, + 60.08601983095524 + ], + [ + -102.48229599610254, + 62.07929787750183 + ], + [ + -99.011819159027, + 63.75553190979579 + ], + [ + -95.36596011725943, + 65.09450314192556 + ], + [ + -91.56494606369075, + 66.07596756704108 + ], + [ + -87.62901680168099, + 66.67968117829292 + ], + [ + -83.57840372761005, + 66.88541678279019 + ], + [ + -81.17381420468101, + 66.81307472342846 + ], + [ + -78.79839269743496, + 66.59806622044475 + ], + [ + -76.45703206799314, + 66.24343460045013 + ], + [ + -74.15462517847628, + 65.75220637609644 + ], + [ + -71.89605648402511, + 65.12741646701551 + ], + [ + -69.68622725373972, + 64.3721166067982 + ], + [ + -67.53002194276198, + 63.48931649413666 + ], + [ + -65.43233761670163, + 62.4820762696017 + ], + [ + -63.398058730700164, + 61.3534140388657 + ], + [ + -61.43208655385773, + 60.10638153551925 + ], + [ + -59.53930554131537, + 58.74399686523475 + ], + [ + -57.72460855519408, + 57.26930335462351 + ], + [ + -55.99288845761404, + 55.68533592331691 + ], + [ + -54.34902970371687, + 53.9951210839669 + ], + [ + -52.79794196958212, + 52.20171897714389 + ], + [ + -51.34450130337123, + 50.30813930154065 + ], + [ + -49.92150231025142, + 51.327258588234585 + ], + [ + -48.42157945389404, + 52.2236780078299 + ], + [ + -46.85419058633651, + 52.993328582205464 + ], + [ + -45.22877674565709, + 53.63214974022023 + ], + [ + -43.55478737691348, + 54.13607250375282 + ], + [ + -41.841655111204595, + 54.50105311562133 + ], + [ + -40.09884620754707, + 54.72302259770457 + ], + [ + -38.335784890060026, + 54.79791197188163 + ], + [ + -36.18175698679562, + 54.689024772247336 + ], + [ + -34.0872673129791, + 54.36934096640349 + ], + [ + -32.06327016301477, + 53.849318836958176 + ], + [ + -30.120736645265872, + 53.13941666652019 + ], + [ + -28.270621054136782, + 52.25008433071831 + ], + [ + -26.523886091011295, + 51.19178011216101 + ], + [ + -24.8914860502936, + 49.9749622934567 + ], + [ + -23.384392040346953, + 48.61008075023433 + ], + [ + -22.013549948596115, + 47.10760217208198 + ], + [ + -20.78993088340453, + 45.47796802764894 + ], + [ + -19.724497546155895, + 43.73165341350292 + ], + [ + -18.828195824274804, + 41.87910820527267 + ], + [ + -18.111996826124887, + 39.93078227858716 + ], + [ + -17.586854846110093, + 37.89713391605481 + ], + [ + -17.26373258561435, + 35.788621400284306 + ], + [ + -17.153584339041743, + 33.61571142086326 + ], + [ + -17.238141739745934, + 31.719735756077334 + ], + [ + -17.48457553243262, + 29.8453492148753 + ], + [ + -17.882040713432264, + 28.014149327821087 + ], + [ + -18.419692279075235, + 26.247725218498584 + ], + [ + -16.42961729175172, + 25.436947699974528 + ], + [ + -14.521476727500634, + 24.477770177748837 + ], + [ + -12.701685111748365, + 23.378288573165495 + ], + [ + -10.976656969921237, + 22.146590400589005 + ], + [ + -9.352790013486068, + 20.790779988342997 + ], + [ + -7.836507174848902, + 19.318944850791866 + ], + [ + -6.43420616547678, + 17.739180909279625 + ], + [ + -5.152301510795982, + 16.059584085150338 + ], + [ + -3.9972077362327445, + 14.288241892768408 + ], + [ + -2.975330960233709, + 12.433250253477809 + ], + [ + -2.0930773012455806, + 10.502705088622594 + ], + [ + -1.3568612846946317, + 8.504710726526477 + ], + [ + -0.7730890290274535, + 6.44733786759452 + ], + [ + -0.3481750596704596, + 4.3387076541096405 + ], + [ + -0.08852549507013441, + 2.1868907864770666 + ], + [ + -0.000554860652853644, + 2.0358660503955184e-14 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 397, + "versionNonce": 1380286951, + "isDeleted": false, + "id": "tuBOaqSOfHB6Y6CvJILps", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2099.3666172163034, + "y": 1148.5660021702458, + "strokeColor": "#f4bd19", + "backgroundColor": "#F4BD19", + "width": 69.99338465999644, + "height": 70.33883638371637, + "seed": 426983894, + "groupIds": [ + "ICCN-vf_1OuzigYt_L3_q", + "HgiQ4Iu13YrXwAPHn8jQm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 31.428404488896618, + 14.390521206445763 + ], + [ + 63.201701111934206, + -13.468763150369988 + ], + [ + 63.201701111934206, + -13.468763150369988 + ], + [ + 63.722126776399534, + -16.96280054774329 + ], + [ + 63.892057054828214, + -20.491298154450934 + ], + [ + 63.70886896958815, + -24.114546624614526 + ], + [ + 63.17121740394513, + -27.633596888009315 + ], + [ + 62.296975596505206, + -31.030569400795198 + ], + [ + 61.1040167858739, + -34.28758882262126 + ], + [ + 59.61022261763694, + -37.38677560964702 + ], + [ + 57.83345792342017, + -40.3102565232668 + ], + [ + 55.791604348809116, + -43.04015412138493 + ], + [ + 53.50253513240963, + -45.5585909619057 + ], + [ + 50.98412351282773, + -47.84769065360621 + ], + [ + 48.2542427286686, + -49.889575754390634 + ], + [ + 45.33075761155919, + -51.66637197478078 + ], + [ + 42.23155821406397, + -53.160200296372444 + ], + [ + 38.97451777478887, + -54.35318590425115 + ], + [ + 35.57749271838067, + -55.22745135632115 + ], + [ + 32.0583815043838, + -55.765119735923356 + ], + [ + 28.435040557444758, + -55.9483151772706 + ], + [ + 26.24361840896944, + -55.881313126256515 + ], + [ + 24.07633269889351, + -55.68210606684614 + ], + [ + 21.938404161541694, + -55.3533915886151 + ], + [ + 19.835061938217997, + -54.897867281138986 + ], + [ + 17.771526763247554, + -54.318232310302015 + ], + [ + 15.753019370954835, + -53.61718374024336 + ], + [ + 13.784773106133553, + -52.79742021141138 + ], + [ + 11.872012906598442, + -51.86163983881758 + ], + [ + 10.019963710164017, + -50.81254021203755 + ], + [ + 8.233854658134259, + -49.652819446083136 + ], + [ + 6.5189022813442286, + -48.38517513052973 + ], + [ + 4.880344128077745, + -47.012306956698 + ], + [ + 3.3233967291694633, + -45.536911463291105 + ], + [ + 1.853289225923838, + -43.96168623988435 + ], + [ + 0.47524655615523415, + -42.289330977798365 + ], + [ + -0.8055021388321322, + -40.52254326660862 + ], + [ + -6.1013276051682155, + -13.123311952086283 + ], + [ + -2.5447851291493517e-15, + 0.0005506571630433349 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 391, + "versionNonce": 3748873, + "isDeleted": false, + "id": "yh8npyUE9_CNnMVPOLcCo", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2066.787261492035, + "y": 1181.8362623928263, + "strokeColor": "#3cbeb1", + "backgroundColor": "#3CBEB1", + "width": 70.22368335710736, + "height": 70.6842870565638, + "seed": 1538977418, + "groupIds": [ + "lFDUf97mnBayLvsvf0wnZ", + "HgiQ4Iu13YrXwAPHn8jQm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.5176954978440147, + 3.525660251096581 + ], + [ + -0.6903517394042322, + 7.137685403508975 + ], + [ + -0.505868979307464, + 10.762228548529105 + ], + [ + 0.035437520711720794, + 14.284929542810413 + ], + [ + 0.915356141119041, + 17.687587275543166 + ], + [ + 2.1156773641251903, + 20.951967007999237 + ], + [ + 3.618187468451012, + 24.059867629368842 + ], + [ + 5.404676936307073, + 26.993079621862638 + ], + [ + 7.4569320464143, + 29.73337665373208 + ], + [ + 9.756741179238466, + 32.26255761416743 + ], + [ + 12.285894816990114, + 34.56239617142013 + ], + [ + 15.026179238390219, + 36.614682807700795 + ], + [ + 17.959387027394172, + 38.40119959824055 + ], + [ + 21.067300259233185, + 39.90374543222963 + ], + [ + 24.3317136196075, + 41.10409397791938 + ], + [ + 27.734409183748483, + 41.984044124500215 + ], + [ + 31.257181637356283, + 42.52536113324386 + ], + [ + 34.88181725915189, + 42.70985230032018 + ], + [ + 37.09353805984451, + 42.64156240512037 + ], + [ + 39.27877267134254, + 42.4386935806629 + ], + [ + 41.43263663850502, + 42.10429756053857 + ], + [ + 43.55024130270095, + 41.64140085739922 + ], + [ + 45.6266895983199, + 41.05304679785618 + ], + [ + 47.65708866324107, + 40.34226189456135 + ], + [ + 49.63655824581331, + 39.51207266016668 + ], + [ + 51.56020128042606, + 38.56553082826294 + ], + [ + 53.42312070146896, + 37.50566291150209 + ], + [ + 55.220432053800636, + 36.33550382951572 + ], + [ + 56.94725088228035, + 35.0580885019353 + ], + [ + 58.598675917807505, + 33.676451848392524 + ], + [ + 60.16982270524127, + 32.193628788518836 + ], + [ + 61.65578997548119, + 30.61265424194566 + ], + [ + 63.05170168036575, + 28.936554721325336 + ], + [ + 64.35265655079479, + 27.16838196024845 + ], + [ + 69.53333161770313, + -0.2303007988558079 + ], + [ + 62.51079661362198, + -13.584468410450697 + ], + [ + 30.967800789440147, + -27.974434756243618 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 391, + "versionNonce": 573222151, + "isDeleted": false, + "id": "q6z8y_HA7CXOdPsPBsHKF", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2066.5575113503396, + "y": 1127.3838016192228, + "strokeColor": "#e9478c", + "backgroundColor": "#E9478C", + "width": 27.398682759104187, + "height": 27.97443370537114, + "seed": 77109014, + "groupIds": [ + "V9UVK-BdagUi7uDc2TDy1", + "HgiQ4Iu13YrXwAPHn8jQm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 21.527653851046804, + 5.065524667480322 + ], + [ + 26.362327062508335, + -19.45549311501823 + ], + [ + 26.362327062508335, + -19.45549311501823 + ], + [ + 25.21368144128973, + -20.264940229022404 + ], + [ + 24.015073140374785, + -20.96643962336142 + ], + [ + 22.770558527415208, + -21.55999970501477 + ], + [ + 21.484198173552315, + -22.04562362659997 + ], + [ + 20.160052649927543, + -22.423320845968927 + ], + [ + 18.80218252768225, + -22.69309556661149 + ], + [ + 17.41465678493745, + -22.854956195507235 + ], + [ + 16.00152758585499, + -22.908909037890815 + ], + [ + 14.273472931375775, + -22.821277835274433 + ], + [ + 12.592135862471576, + -22.56400534415288 + ], + [ + 10.966459303679985, + -22.145522714181297 + ], + [ + 9.405375670814038, + -21.574260044142314 + ], + [ + 7.917821583176722, + -20.858647432818607 + ], + [ + 6.51273576181585, + -20.00711708073773 + ], + [ + 5.199054826034233, + -19.028099086682474 + ], + [ + 3.985713293389913, + -17.930023549435397 + ], + [ + 2.881647783185832, + -16.721320567779195 + ], + [ + 1.895799118214665, + -15.410420240496604 + ], + [ + 1.037099714289453, + -14.005756869860035 + ], + [ + 0.31448619071317374, + -12.51575845290728 + ], + [ + -0.2631006297214942, + -10.948855088421034 + ], + [ + -0.6867283312014747, + -9.313478976928828 + ], + [ + -0.9474581926789241, + -7.618058115468496 + ], + [ + -1.036355696595848, + -5.871026806312463 + ], + [ + -0.9715904275885934, + -4.387077211173852 + ], + [ + -0.7773345537198222, + -2.8923330542432306 + ], + [ + -0.45363851686711176, + -1.419178020896652 + ], + [ + -0.0005506571630726006, + -2.544839339257976e-15 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 392, + "versionNonce": 1827130089, + "isDeleted": false, + "id": "siFAJw9wpQ4Z-fqaXCU8t", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2064.7151028577528, + "y": 1132.563926028964, + "strokeColor": "#2c458f", + "backgroundColor": "#2C458F", + "width": 45.588067671370794, + "height": 44.78256369351191, + "seed": 540052810, + "groupIds": [ + "kcJCI2nxDjw72OA_G26_O", + "HgiQ4Iu13YrXwAPHn8jQm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -1.7771346013186435, + 0.6543740702539308 + ], + [ + -3.4787272366696214, + 1.446521722009225 + ], + [ + -5.099382726901793, + 2.368515173513654 + ], + [ + -6.633702740246736, + 3.41243084650473 + ], + [ + -8.076292097553301, + 4.570336755740451 + ], + [ + -9.421752467053052, + 5.8343135264481285 + ], + [ + -10.664687618722386, + 7.196429173385754 + ], + [ + -11.799700271665351, + 8.648760118290834 + ], + [ + -12.821394195858302, + 10.183382782900983 + ], + [ + -13.724372110405291, + 11.792365181974105 + ], + [ + -14.503237259846532, + 13.467783737247764 + ], + [ + -15.152592363285935, + 15.201714870459542 + ], + [ + -15.667040665263759, + 16.98622659636736 + ], + [ + -16.041185935756452, + 18.813399540198603 + ], + [ + -16.26963089386803, + 20.67530171671116 + ], + [ + -16.346978521420503, + 22.56400954764267 + ], + [ + -16.276209092678158, + 24.40718517712118 + ], + [ + -16.06682407088499, + 26.219666924120954 + ], + [ + -15.723207170460393, + 27.99503605972594 + ], + [ + -15.249741843105472, + 29.72689907595882 + ], + [ + -14.65081337954841, + 31.408833040413896 + ], + [ + -13.930804443336037, + 33.034440241623855 + ], + [ + -13.094099799759999, + 34.59731456114213 + ], + [ + -12.145083163239832, + 36.09103306656294 + ], + [ + -11.088138773630986, + 37.50920645339874 + ], + [ + -9.927650870789037, + 38.84541178924363 + ], + [ + -8.668002643697138, + 40.09324295565097 + ], + [ + -7.3135793830833435, + 41.24630224115383 + ], + [ + -5.868763227058114, + 42.29816671334604 + ], + [ + -4.337939466349688, + 43.24243866076081 + ], + [ + -2.725492340813574, + 44.07270355797185 + ], + [ + -1.0358050394327911, + 44.78256369351191 + ], + [ + 29.241089149950287, + 17.383334480734398 + ], + [ + 23.714962884758442, + 5.52667692235492 + ], + [ + 0, + 0.0005506571630662383 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 389, + "versionNonce": 243102759, + "isDeleted": false, + "id": "oU3xoHpDQ8X1w2W4CBvLx", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2136.6660443080236, + "y": 1208.8894939536383, + "strokeColor": "#95c63d", + "backgroundColor": "#95C63D", + "width": 27.283532359676293, + "height": 27.97443475624359, + "seed": 585188438, + "groupIds": [ + "pqP_Mg-t98BD-Yxg1qAfW", + "HgiQ4Iu13YrXwAPHn8jQm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.1484186327697514, + 0.8145774732982304 + ], + [ + 2.345454828501554, + 1.5292548081426913 + ], + [ + 3.5856944923402523, + 2.141350178044517 + ], + [ + 4.863740343389801, + 2.6481817565148864 + ], + [ + 6.174161472835852, + 3.047059310085409 + ], + [ + 7.511560599782392, + 3.3353010122672417 + ], + [ + 8.870532036353783, + 3.5102166295919526 + ], + [ + 10.24565328071529, + 3.5691159285911205 + ], + [ + 11.9737079351945, + 3.4814815733573834 + ], + [ + 13.655036597119125, + 3.22421118398072 + ], + [ + 15.280719461145395, + 2.805728554009127 + ], + [ + 16.8418030940113, + 2.2344658839701133 + ], + [ + 18.329350876413912, + 1.5188553743913307 + ], + [ + 19.7344430030095, + 0.6673208188206843 + ], + [ + 21.04812604053602, + -0.3116971752346043 + ], + [ + 22.261463369690485, + -1.4097748142265765 + ], + [ + 23.365526778149764, + -2.618471490648078 + ], + [ + 24.351379646610702, + -3.929371817930676 + ], + [ + 25.210076948791034, + -5.334035188567231 + ], + [ + 25.932690472367298, + -6.824037809009727 + ], + [ + 26.5102751910571, + -8.390939071751168 + ], + [ + 26.933902892537052, + -10.026315183243296 + ], + [ + 27.19463695750423, + -11.721733942958743 + ], + [ + 27.283532359676293, + -13.468763150369902 + ], + [ + 27.21876498892415, + -15.001380750348101 + ], + [ + 27.02451331854521, + -16.490920986913302 + ], + [ + 26.70081097645782, + -17.937274569330746 + ], + [ + 26.247725218498715, + -19.340349020824988 + ], + [ + 4.720073469196734, + -24.405318827652476 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 395, + "versionNonce": 962627017, + "isDeleted": false, + "id": "IZamyPUeLT0i0klX7nePU", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2141.040666578937, + "y": 1179.4516197350345, + "strokeColor": "#176655", + "backgroundColor": "#176655", + "width": 46.3935721746659, + "height": 44.78201303634882, + "seed": 1778668554, + "groupIds": [ + "kbXqX6SEXX8j4QdAAdn86", + "HgiQ4Iu13YrXwAPHn8jQm" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 23.714962884758435, + 5.526126265191888 + ], + [ + 23.714962884758435, + 5.526126265191888 + ], + [ + 25.49209748607707, + 4.852870118781469 + ], + [ + 27.19368696881068, + 4.046724846010807 + ], + [ + 28.814342459042876, + 3.1151054028772176 + ], + [ + 30.34866667587758, + 2.065426745377989 + ], + [ + 31.791253931439314, + 0.905120643469651 + ], + [ + 33.13671535181145, + -0.3584063538300519 + ], + [ + 34.37965365609818, + -1.7177140695849644 + ], + [ + 35.51466315642375, + -3.1654043617570196 + ], + [ + 36.53635497887184, + -4.6940454603897415 + ], + [ + 37.439331842546395, + -6.2962140025061615 + ], + [ + 38.21819646655142, + -7.9644950321089265 + ], + [ + 38.86755156999081, + -9.691473593200753 + ], + [ + 39.381999871968645, + -11.469717915825115 + ], + [ + 39.7561524985684, + -13.291813043984689 + ], + [ + 39.984595354935074, + -15.15034402168215 + ], + [ + 40.06193956715213, + -17.03787907896099 + ], + [ + 39.99116961297356, + -18.8797894580114 + ], + [ + 39.781785379334806, + -20.68879071546138 + ], + [ + 39.43816690260145, + -22.458993762109174 + ], + [ + 38.96470262611899, + -24.184492694793292 + ], + [ + 38.36577258625328, + -25.85939001733248 + ], + [ + 37.64576522634954, + -27.477779826565488 + ], + [ + 36.80906058277354, + -29.033768829800582 + ], + [ + 35.86004709887072, + -30.521451123876616 + ], + [ + 34.803104811006804, + -31.93492921261198 + ], + [ + 33.64261375554749, + -33.26830139633549 + ], + [ + 32.38296237583827, + -34.51567017886549 + ], + [ + 31.028539115224405, + -35.67112965704082 + ], + [ + 29.58372401007172, + -36.728778131190204 + ], + [ + 28.052905503725466, + -37.68272230862192 + ], + [ + 26.440455225571974, + -38.52706048966453 + ], + [ + 24.750770025936124, + -39.25588677115694 + ], + [ + -6.33163260751377, + -12.087504810908568 + ], + [ + -3.8171505886697155e-15, + 0.0005464536732788332 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "text", + "version": 120, + "versionNonce": 1865045831, + "isDeleted": false, + "id": "SGxHNC6JfD1OOsOq9IRwl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2090.8095042411683, + "y": 1239.6547346303776, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 78.65994262695312, + "height": 25, + "seed": 765060822, + "groupIds": [], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Elastic ", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Elastic ", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "arrow", + "version": 197, + "versionNonce": 667853735, + "isDeleted": false, + "id": "e1jYVwg7QVn9ejhQMKUl-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1664.8007694037994, + "y": 969.6547346303776, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 382.00873483736893, + "height": 203.01583560462376, + "seed": 1134619658, + "groupIds": [], + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "x_cefzWl7ZigCfpyuLuc8" + } + ], + "updated": 1692970009991, + "link": null, + "locked": false, + "startBinding": { + "elementId": "9keLFFDl4MqK2dG4CleRh", + "focus": -0.22919790790113156, + "gap": 13.878291110273267 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 101.74948217762403, + 197.9419832161609 + ], + [ + 382.00873483736893, + 203.01583560462376 + ] + ] + }, + { + "id": "x_cefzWl7ZigCfpyuLuc8", + "type": "text", + "x": 1669.0803266546657, + "y": 1155.0967178465385, + "width": 194.93984985351562, + "height": 25, + "angle": 0, + "strokeColor": "#364fc7", + "backgroundColor": "#5c940d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "roundness": null, + "seed": 1862951785, + "version": 4, + "versionNonce": 771302887, + "isDeleted": false, + "boundElements": null, + "updated": 1692970011464, + "link": null, + "locked": false, + "text": "Project Read Model", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "e1jYVwg7QVn9ejhQMKUl-", + "originalText": "Project Read Model", + "lineHeight": 1.25 + }, + { + "type": "line", + "version": 5417, + "versionNonce": 783181705, + "isDeleted": false, + "id": "lnpwOTp6vflEIlY-xv5nG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.5707963267948957, + "x": 1354.6938744278123, + "y": -126.4176027618272, + "strokeColor": "#087f5b", + "backgroundColor": "#40c057", + "width": 110.01809639482327, + "height": 325.0382642473265, + "seed": 143741126, + "groupIds": [ + "LVsFom64zjmSeI4uLj3np" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.3627831827107899, + 245.66215838632252 + ], + [ + 0.016977401303735845, + 273.6297283401898 + ], + [ + 5.666175285485711, + 285.71416947961745 + ], + [ + 25.33922504044844, + 295.94151962769007 + ], + [ + 58.5922518611144, + 299.12616368394976 + ], + [ + 90.36332002599681, + 294.04141295043416 + ], + [ + 107.24352246726049, + 281.88132353579994 + ], + [ + 109.62424660427436, + 271.6302405955047 + ], + [ + 109.95861069101879, + 249.11375397509093 + ], + [ + 109.69617376246939, + 20.609683905351876 + ], + [ + 109.10455668650478, + -0.9797405188983058 + ], + [ + 102.04028500309101, + -13.046211849461196 + ], + [ + 87.1644364151946, + -20.034473554200066 + ], + [ + 53.26438739651016, + -25.912100563376693 + ], + [ + 26.085145753131467, + -22.407258343206426 + ], + [ + 4.7088567846177245, + -10.519261176554966 + ], + [ + -0.05948570380447489, + -0.14760949162205886 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 3057, + "versionNonce": 661942663, + "isDeleted": false, + "id": "EMsVtNfs7kVdUe0iNtmkY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.5707963267948957, + "x": 1303.1153662298332, + "y": -2.1262309419840655, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 106.65340292727271, + "height": 26.702641621440524, + "seed": 8226458, + "groupIds": [ + "LVsFom64zjmSeI4uLj3np" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8098592207043835, + 10.644119207533665 + ], + [ + 14.927620378036915, + 19.57443740461018 + ], + [ + 31.052199515169683, + 24.982584106311805 + ], + [ + 56.31336656634713, + 25.475404166606033 + ], + [ + 85.79228165438685, + 22.03228120141009 + ], + [ + 103.00830120306117, + 9.506277579066015 + ], + [ + 106.65340292727271, + -1.227237454834491 + ] + ] + }, + { + "type": "line", + "version": 3190, + "versionNonce": 225087081, + "isDeleted": false, + "id": "ueCQy3W81IbAsTENujPmS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.5707963267948957, + "x": 1398.640902221534, + "y": -1.4131713645031176, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 106.34848862704553, + "height": 21.40483848039651, + "seed": 1962362886, + "groupIds": [ + "LVsFom64zjmSeI4uLj3np" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 2.8018260381288416, + 8.532326338095585 + ], + [ + 14.88494340011874, + 15.69086972481001 + ], + [ + 30.96342354154096, + 20.02604031464646 + ], + [ + 56.15237075853669, + 20.421084892634127 + ], + [ + 85.5470077783778, + 17.661077400379636 + ], + [ + 102.71380798280873, + 7.620232447951556 + ], + [ + 106.34848862704553, + -0.9837535877623825 + ] + ] + }, + { + "type": "ellipse", + "version": 6171, + "versionNonce": 1058711719, + "isDeleted": false, + "id": "7svSXNl1rfgZN1h8fLJoI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 1.5707963267948957, + "x": 1496.645731652404, + "y": -13.721813361878578, + "strokeColor": "#087f5b", + "backgroundColor": "#fff", + "width": 107.83239361367806, + "height": 47.9399619154969, + "seed": 1862640474, + "groupIds": [ + "LVsFom64zjmSeI4uLj3np" + ], + "roundness": null, + "boundElements": [ + { + "id": "CqH0rJZl_giwlh370YPWA", + "type": "arrow" + } + ], + "updated": 1692970003173, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 4639, + "versionNonce": 1405016393, + "isDeleted": false, + "id": "tMRJEu98ZKOPbzZW8rBkG", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1369.7722230384325, + "y": 18.44420602647921, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 58.31224894593375, + "height": 59.540147056087235, + "seed": 101989487, + "groupIds": [ + "co6rRVpep_jTgDKptXJmN" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.16114375854884855, + 15.01856182979579 + ], + [ + 1.490278813221324, + 23.481658738959325 + ], + [ + 11.284761909479947, + 24.264013048075928 + ], + [ + 31.371488696082906, + 24.431154884678694 + ], + [ + 50.75179902761935, + 24.35883551699728 + ], + [ + 57.43071530628966, + 21.810240372931762 + ], + [ + 58.116660368654564, + 13.198828239918612 + ], + [ + 57.92245683326325, + -0.7912697682713304 + ], + [ + 56.511364857428426, + -7.876331177326043 + ], + [ + 50.86422687031374, + -10.349635133991917 + ], + [ + 43.116259176266745, + -10.456911669523624 + ], + [ + 34.7831502420505, + -11.361968400500603 + ], + [ + 34.01009965856033, + -22.215150682412887 + ], + [ + 33.43263765234935, + -32.872744387046005 + ], + [ + 28.41622428055185, + -35.10899217140854 + ], + [ + 22.994362657071918, + -32.96700006822993 + ], + [ + 22.59011821881196, + -21.967195293873818 + ], + [ + 21.968907668244167, + -11.50123028416436 + ], + [ + 16.731554286940817, + -9.917557172146829 + ], + [ + 11.588441975324692, + -11.677854367659247 + ], + [ + 11.115307899604133, + -22.02352198748918 + ], + [ + 10.653254158986169, + -32.536408016243946 + ], + [ + 5.301485986389247, + -34.6389852219949 + ], + [ + 0.36227250337930084, + -32.45609231152953 + ], + [ + -0.1955885772791804, + -18.580914841520023 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1623, + "versionNonce": 1865219015, + "isDeleted": false, + "id": "ofct0d58vyIIC4QHYnX8T", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1406.178979461306, + "y": 23.002104636631284, + "strokeColor": "#000000", + "backgroundColor": "#ffff", + "width": 11.480172566759105, + "height": 11.47880457085691, + "seed": 1102005761, + "groupIds": [ + "co6rRVpep_jTgDKptXJmN" + ], + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1.1778262968426243, + -2.2176894883419727 + ], + [ + 3.722655736275587, + -2.9878094914374826 + ], + [ + 7.908142177782961, + -3.12687995678845 + ], + [ + 10.742214217504081, + -1.9724566846593783 + ], + [ + 11.470271334402812, + 1.1962828214503833 + ], + [ + 11.480172566759105, + 4.468416471571565 + ], + [ + 10.970995992677125, + 7.465177385311005 + ], + [ + 8.311775665657663, + 8.310065064747159 + ], + [ + 3.6720961435167196, + 8.351924614068462 + ], + [ + 0.8435014354623133, + 7.281220440581992 + ], + [ + 0.2595389032191419, + 4.305389301503191 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "rectangle", + "version": 180, + "versionNonce": 926522663, + "isDeleted": false, + "id": "SOuHZjjtpXN4TBtnRM-7Q", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1311.5770987542778, + "y": 140.56139908064358, + "strokeColor": "#000000", + "backgroundColor": "#5c940d", + "width": 44.76190476190459, + "height": 29.701824655095567, + "seed": 433778804, + "groupIds": [ + "olc3ZpXHKSSj8MR9_F3Ge" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 156, + "versionNonce": 681012425, + "isDeleted": false, + "id": "l0biVGPit_iqWO1KSJ5Su", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1312.1485376472704, + "y": 143.20649378848384, + "strokeColor": "#000000", + "backgroundColor": "#5c940d", + "width": 22.014749416409295, + "height": 17.13904349052929, + "seed": 971079884, + "groupIds": [ + "olc3ZpXHKSSj8MR9_F3Ge" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 22.014749416409295, + 17.13904349052929 + ] + ] + }, + { + "type": "line", + "version": 240, + "versionNonce": 1618567239, + "isDeleted": false, + "id": "2p1y9I5hKyqFnY39JFO6L", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1334.9444928543978, + "y": 161.29744832827404, + "strokeColor": "#000000", + "backgroundColor": "#5c940d", + "width": 20.9909504475987, + "height": 20.9402128366682, + "seed": 1871495668, + "groupIds": [ + "olc3ZpXHKSSj8MR9_F3Ge" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.9909504475987, + -20.9402128366682 + ] + ] + }, + { + "type": "rectangle", + "version": 232, + "versionNonce": 109112233, + "isDeleted": false, + "id": "EWHmhA28O4cHEewevzJ2t", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 839.2265527652703, + "y": 29.627617107077906, + "strokeColor": "#000000", + "backgroundColor": "#c92a2a", + "width": 44.76190476190459, + "height": 29.701824655095567, + "seed": 433778804, + "groupIds": [ + "J64Cc57X-NrK-F9Xs_AhP" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 208, + "versionNonce": 1326694247, + "isDeleted": false, + "id": "lmR5e0FT_5BTDYNWmmROA", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 839.7979916582631, + "y": 32.27271181491815, + "strokeColor": "#000000", + "backgroundColor": "#c92a2a", + "width": 22.014749416409295, + "height": 17.13904349052929, + "seed": 971079884, + "groupIds": [ + "J64Cc57X-NrK-F9Xs_AhP" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 22.014749416409295, + 17.13904349052929 + ] + ] + }, + { + "type": "line", + "version": 314, + "versionNonce": 748129929, + "isDeleted": false, + "id": "VmZJkEy9UD5i-0IcmHgVh", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 862.5939468653901, + "y": 50.363666354708386, + "strokeColor": "#000000", + "backgroundColor": "#c92a2a", + "width": 20.9909504475987, + "height": 20.9402128366682, + "seed": 1871495668, + "groupIds": [ + "J64Cc57X-NrK-F9Xs_AhP" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.9909504475987, + -20.9402128366682 + ] + ] + }, + { + "type": "rectangle", + "version": 247, + "versionNonce": 894777991, + "isDeleted": false, + "id": "h1EhegVLu-5I1NcP85Mdv", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1699.22655276527, + "y": 146.29428377374452, + "strokeColor": "#000000", + "backgroundColor": "#c92a2a", + "width": 44.76190476190459, + "height": 29.701824655095567, + "seed": 433778804, + "groupIds": [ + "4mk-4qtQYEKMve0Ibqk7c" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 223, + "versionNonce": 1637274985, + "isDeleted": false, + "id": "opz9cEnhfj77-tW7xcknC", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1699.7979916582628, + "y": 148.93937848158478, + "strokeColor": "#000000", + "backgroundColor": "#c92a2a", + "width": 22.014749416409295, + "height": 17.13904349052929, + "seed": 971079884, + "groupIds": [ + "4mk-4qtQYEKMve0Ibqk7c" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 22.014749416409295, + 17.13904349052929 + ] + ] + }, + { + "type": "line", + "version": 307, + "versionNonce": 1207587239, + "isDeleted": false, + "id": "FU-iPukMreVTn-v5jQr44", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1722.5939468653899, + "y": 167.03033302137501, + "strokeColor": "#000000", + "backgroundColor": "#c92a2a", + "width": 20.9909504475987, + "height": 20.9402128366682, + "seed": 1871495668, + "groupIds": [ + "4mk-4qtQYEKMve0Ibqk7c" + ], + "roundness": null, + "boundElements": [], + "updated": 1692970003173, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 20.9909504475987, + -20.9402128366682 + ] + ] + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/deployments/docker-compose/docker-compose.infrastructure-collector.yaml b/deployments/docker-compose/docker-compose.infrastructure-collector.yaml new file mode 100644 index 00000000..4feee767 --- /dev/null +++ b/deployments/docker-compose/docker-compose.infrastructure-collector.yaml @@ -0,0 +1,383 @@ +# Ref:https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/examples/demo/docker-compose.yaml +# https://github.com/open-telemetry/opentelemetry-demo/blob/main/docker-compose.yml +# https://www.youtube.com/watch?v=EeU-k659lpw + +version: "3.8" +name: go-food-delivery-microservices + +services: + rabbitmq: + image: rabbitmq:management + container_name: rabbitmq + pull_policy: if_not_present + restart: unless-stopped + ports: + - ${RABBITMQ_HOST_PORT:-5672}:${RABBITMQ_PORT:-5672} + - ${RABBITMQ_HOST_API_PORT:-15672}:${RABBITMQ_API_PORT:-15672} + # volumes: + # - rabbitmq:/var/lib/rabbitmq + networks: + - food-delivery + + # containers monitoring + cadvisor: + image: gcr.io/cadvisor/cadvisor:latest + restart: unless-stopped + ports: + - "9091:8080" + # network_mode: host + volumes: + - /:/rootfs:ro + - /var/run:/var/run:ro + - /sys:/sys:ro + - /var/lib/docker/:/var/lib/docker:ro + - /dev/disk/:/dev/disk:ro + devices: + - /dev/kmsg + networks: + - food-delivery + + # prometheus dashboard: http://localhost:9090 + # prometheus internal metrics: http://localhost:9090/metrics + # https://prometheus.io/docs/prometheus/latest/getting_started/ + # https://prometheus.io/docs/guides/go-application/ + prometheus: + image: prom/prometheus:latest + pull_policy: if_not_present + container_name: prometheus + restart: unless-stopped + user: root + ports: + - ${PROMETHEUS_HOST_PORT:-9090}:${PROMETHEUS_PORT:-9090} + command: + - --config.file=/etc/prometheus/prometheus.yml + volumes: + - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro + networks: + - food-delivery + + otelcol: + image: otel/opentelemetry-collector-contrib:latest + container_name: otel-col + pull_policy: if_not_present + deploy: + resources: + limits: + memory: 125M + restart: unless-stopped + command: [ "--config=/etc/otelcol-config.yml", "--config=/etc/otelcol-observability.yml", "--config=/etc/otelcol-config-extras.yml" ] + volumes: + - ./otelcollector/otelcol-config.yml:/etc/otelcol-config.yml + - ./otelcollector/otelcol-observability.yml:/etc/otelcol-observability.yml + - ./otelcollector/otelcol-config-extras.yml:/etc/otelcol-config-extras.yml + ports: + - "4317:4317" # OTLP over gRPC receiver + - "4318:4318" # OTLP over HTTP receiver + - "8889:8889" # Prometheus exporter metrics + - "8888:8888" # Prometheus metrics exposed by the collector + - "13133:13133" # health_check extension + - "55679:55679" # zpages extension + - "1888:1888" # pprof extension + depends_on: + prometheus: + condition: service_healthy + tempo: + condition: service_healthy + jaeger: + condition: service_healthy + + # node_exporter will use for gathering metrics on the system level with its own /metrics endpoint like cpu, ram, ... + # https://prometheus.io/docs/guides/node-exporter/ + node_exporter: + container_name: node_exporter + pull_policy: if_not_present + restart: unless-stopped + image: prom/node-exporter + ports: + - "9100:9100" + networks: + - food-delivery + + grafana: + image: grafana/grafana + pull_policy: if_not_present + container_name: grafana + restart: unless-stopped + volumes: + - ./monitoring/grafana.yaml:/etc/grafana/provisioning/datasources/datasource.yaml + - ./monitoring/grafana-bootstrap.ini:/etc/grafana/grafana.ini + ports: + - ${GRAFANA_HOST_PORT:-3000}:${GRAFANA_PORT:-3000} + environment: + - GF_FEATURE_TOGGLES_ENABLE=traceqlEditor + healthcheck: + interval: 5s + retries: 10 + test: wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1 + depends_on: + prometheus: + condition: service_healthy + tempo: + condition: service_healthy + networks: + - food-delivery + + # https://grafana.com/docs/tempo/latest/getting-started/ + # https://github.com/grafana/tempo/blob/main/example/docker-compose/local/docker-compose.yaml + # https://github.com/Domoryonok/tracing_demo/blob/master/grafana/docker-compose.yaml + # https://grafana.com/docs/grafana/latest/datasources/jaeger/ + # https://grafana.com/docs/tempo/latest/operations/architecture/ + tempo: + image: grafana/tempo:latest + command: ["-config.file=/etc/tempo.yaml" ] + volumes: + - ./otelcollector/tempo.yaml:/etc/tempo.yaml + - ./tempo-data:/tmp/tempo + ports: + - "3200:3200" # tempo UI + - "4322:4317" # otlp grpc + - "9411" # zipkin - export zipkin traces to tempo + - "14268" # jaeger - export jaeger traces to tempo + healthcheck: + interval: 5s + retries: 10 + test: wget --no-verbose --tries=1 --spider http://localhost:3200/status || exit 1 + networks: + - food-delivery + + postgres: + image: postgres:latest + pull_policy: if_not_present + container_name: postgres + restart: unless-stopped + ports: + - ${POSTGRES_HOST_PORT:-5432}:${POSTGRES_PORT:-5432} + #https://docs.docker.com/compose/environment-variables/env-file/#parameter-expansion + environment: + - POSTGRES_USER=${POSTGRES_USER:-postgres} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres} + networks: + - food-delivery + + # https://developer.redis.com/howtos/quick-start + # redis-stack is a image with redis modules enabled like JSON module + redis: + image: redis/redis-stack:latest + pull_policy: if_not_present + restart: unless-stopped + container_name: redis + ports: + - "6379:6379" + networks: + - food-delivery + + mongo: + image: mongo + pull_policy: if_not_present + container_name: mongo + restart: unless-stopped + # https://docs.docker.com/compose/environment-variables/env-file/#parameter-expansion + environment: + - MONGO_INITDB_ROOT_USERNAME=${MONGO_USER:-admin} + - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASS:-admin} + ports: + - ${MONGO_HOST_PORT:-27017}:${MONGO_PORT:-27017} + networks: + - food-delivery + + # https://www.jaegertracing.io/docs/1.38/apis/#opentelemetry-protocol-stable + # https://deploy-preview-1892--opentelemetry.netlify.app/blog/2022/jaeger-native-otlp/ + # https://www.jaegertracing.io/docs/1.49/deployment/ + # https://www.jaegertracing.io/docs/1.49/getting-started/ + # https://opentelemetry.io/docs/instrumentation/go/exporters/ + # https://opentelemetry.io/docs/specs/otlp/ + jaeger: + image: jaegertracing/all-in-one:latest + container_name: jaeger + pull_policy: if_not_present + restart: unless-stopped + command: + - "--memory.max-traces" + - "10000" + - "--query.base-path" + - "/jaeger/ui" + - "--prometheus.server-url" + - "http://${PROMETHEUS_ADDR}" + environment: + - COLLECTOR_ZIPKIN_HOST_PORT=:9411 + - COLLECTOR_OTLP_ENABLED=true + # Store metrics in PROMETHEUS storage instead of in-memory storage + - METRICS_STORAGE_TYPE=prometheus + - PROMETHEUS_SERVER_URL=http:${PROMETHEUS_ADDR} + # # Jaeger uses Elasticsearch as span storage instead of in-memory storage + # - SPAN_STORAGE_TYPE=elasticsearch + # - ES_SERVER_URLS=http://elastic_search:${ELASTIC_PORT:-9200} + # - ES_VERSION=8 + ports: + - "16686:16686" # Jaeger UI port + - "4320:4317" # OTLP gRPC default port - for prevent duplicate expose this port that will expose also by `otel-collector` we not expose it on `4317` - `4320` could use by the app through otlptracegrpc + - "4321:4318" # OTLP Http default port - for prevent duplicate expose this port that will expose also by `otel-collector` we not expose it on `4318` - `4321` could use by the app through otlptracehttp + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:16686" ] + interval: 10s + retries: 3 + timeout: 10s + networks: + - food-delivery + + zipkin: + image: openzipkin/zipkin:latest + pull_policy: if_not_present + restart: unless-stopped + container_name: zipkin + ports: + - "9411:9411" + networks: + - food-delivery + + # https://developers.eventstore.com/server/v21.10/installation.html#insecure-single-node + # https://hub.docker.com/r/eventstore/eventstore/tags + # https://stackoverflow.com/questions/65272764/ports-are-not-available-listen-tcp-0-0-0-0-50070-bind-an-attempt-was-made-to + # EVENTSTORE_MEM_DB=true, it tells the EventStoreDB container to use an in-memory database, which means that any data stored in EventStoreDB will not be persisted between container restarts. Once the container is stopped or restarted, all data will be lost. + eventstore: + image: eventstore/eventstore:latest + pull_policy: if_not_present + container_name: eventstore + restart: unless-stopped + environment: + - EVENTSTORE_CLUSTER_SIZE=1 + - EVENTSTORE_RUN_PROJECTIONS=All + - EVENTSTORE_START_STANDARD_PROJECTIONS=false + - EVENTSTORE_EXT_TCP_PORT=1113 + - EVENTSTORE_HTTP_PORT=2113 + - EVENTSTORE_INSECURE=true + - EVENTSTORE_ENABLE_EXTERNAL_TCP=true + - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true + - EVENTSTORE_MEM_DB=true + ports: + - ${EVENTSTORE_TCP_HOST_PORT:-1113}:${EVENTSTORE_TCP_PORT:-1113} + - ${EVENTSTORE_HOST_PORT:-2113}:${EVENTSTORE_PORT:-2113} + volumes: + - type: volume + source: eventstore-volume-data + target: /var/lib/eventstore + - type: volume + source: eventstore-volume-logs + target: /var/log/eventstore + networks: + - food-delivery + + # # https://stackoverflow.com/questions/67791781/how-to-configure-apm-server-to-docker-compose-file + # # https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html + # apm-server: + # image: docker.elastic.co/apm/apm-server:latest + # cap_add: ["CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"] + # cap_drop: ["ALL"] + # ports: + # - 8200:8200 + # command: > + # apm-server -e + # -E apm-server.rum.enabled=true + # -E setup.kibana.host=kibana:5601 + # -E setup.template.settings.index.number_of_replicas=0 + # -E apm-server.kibana.enabled=true + # -E apm-server.kibana.host=kibana:5601 + # -E output.elasticsearch.hosts=["elasticsearch:9200"] + + # elasticsearch: + # container_name: elastic_search + # restart: unless-stopped + # image: elasticsearch:8.5.2 + # environment: + # - discovery.type=single-node + # - bootstrap.memory_lock=true + # - xpack.monitoring.enabled=true + # - xpack.watcher.enabled=false + # - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + # ulimits: + # memlock: + # soft: -1 + # hard: -1 + # volumes: + # - elastic-data:/usr/share/elasticsearch/data + # ports: + # - ${ELASTIC_HOST_PORT:-9200}:${ELASTIC_PORT:-9200} + # - 9300:9300 + # networks: + # - food-delivery + + # kibana: + # image: kibana:8.5.2 + # container_name: kibana + # restart: unless-stopped + # environment: + # - ELASTICSEARCH_HOSTS=http://elastic_search:9200 + # ports: + # - ${KIBANA_HOST_PORT:-5601}:${KIBANA_PORT:-5601} + # networks: + # - food-delivery + # depends_on: + # - elasticsearch + + # zookeeper: + # image: confluentinc/cp-zookeeper:7.0.1 + # hostname: zookeeper + # container_name: zookeeper + # restart: unless-stopped + # ports: + # - "2181:2181" + # environment: + # ZOOKEEPER_CLIENT_PORT: 2181 + # ZOOKEEPER_TICK_TIME: 2000 + # networks: + # - food-delivery + + # kafka: + # image: confluentinc/cp-kafka:7.0.1 + # hostname: kafka + # container_name: kafka + # restart: unless-stopped + # depends_on: + # - zookeeper + # ports: + # - "9092:9092" + # environment: + # KAFKA_BROKER_ID: 1 + # KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' + # KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + # KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 + # KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + # ADVERTISED_HOST_NAME: kafka + # KAFKA_ADVERTISED_HOSTNAME: 127.0.0.1 + # networks: + # - food-delivery + + # kafka-ui: + # image: provectuslabs/kafka-ui + # container_name: kafka-ui + # ports: + # - "8080:8080" + # restart: always + # environment: + # - KAFKA_CLUSTERS_0_NAME=local + # - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092 + # - KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181 + + # kafdrop: + # image: obsidiandynamics/kafdrop + # container_name: kafdrop + # ports: + # - '9000:9000' + # environment: + # - 'KAFKA_BROKERCONNECT=' + # - 'JVM_OPTS=-Xms32M -Xmx64M' + # - SERVER_SERVLET_CONTEXTPATH=/ + +volumes: + eventstore-volume-data: + eventstore-volume-logs: + elastic-data: + +networks: + food-delivery: + name: food-delivery diff --git a/deployments/docker-compose/docker-compose.infrastructure.yaml b/deployments/docker-compose/docker-compose.infrastructure.yaml index ca68fdd6..95a94343 100644 --- a/deployments/docker-compose/docker-compose.infrastructure.yaml +++ b/deployments/docker-compose/docker-compose.infrastructure.yaml @@ -1,60 +1,121 @@ +# Ref:https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/examples/demo/docker-compose.yaml +# https://github.com/open-telemetry/opentelemetry-demo/blob/main/docker-compose.yml +# https://github.com/grafana/tempo/blob/main/example/docker-compose/local/docker-compose.yaml +# https://github.com/build-on-aws/instrumenting-java-apps-using-opentelemetry/blob/main/docker-compose.yaml + version: "3.8" -name: go-ecommerce-microservices +name: go-food-delivery-microservices services: rabbitmq: image: rabbitmq:management container_name: rabbitmq pull_policy: if_not_present - restart: on-failure + restart: unless-stopped ports: - ${RABBITMQ_HOST_PORT:-5672}:${RABBITMQ_PORT:-5672} - ${RABBITMQ_HOST_API_PORT:-15672}:${RABBITMQ_API_PORT:-15672} # volumes: # - rabbitmq:/var/lib/rabbitmq networks: - - ecommerce + - food-delivery + # prometheus dashboard: http://localhost:9090 + # prometheus internal metrics: http://localhost:9090/metrics + # https://prometheus.io/docs/prometheus/latest/getting_started/ + # https://prometheus.io/docs/guides/go-application/ prometheus: image: prom/prometheus:latest pull_policy: if_not_present container_name: prometheus - restart: on-failure + restart: unless-stopped user: root ports: - ${PROMETHEUS_HOST_PORT:-9090}:${PROMETHEUS_PORT:-9090} command: - --config.file=/etc/prometheus/prometheus.yml + - --web.enable-remote-write-receiver + - --enable-feature=exemplar-storage volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro + healthcheck: + interval: 5s + retries: 10 + test: wget --no-verbose --tries=1 --spider http://localhost:9090/status || exit 1 networks: - - ecommerce + - food-delivery - node_exporter: - container_name: node_exporter - pull_policy: if_not_present - restart: on-failure - image: prom/node-exporter - ports: - - "9101:9100" - networks: - - ecommerce +# # node_exporter will use for gathering metrics on the system level with its own /metrics endpoint (http://localhost:9100/metrics) like cpu, ram, ... +# # https://prometheus.io/docs/guides/node-exporter/ +# node_exporter: +# container_name: node_exporter +# pull_policy: if_not_present +# restart: unless-stopped +# image: prom/node-exporter +# ports: +# - "9100:9100" +# networks: +# - food-delivery grafana: - container_name: grafana + image: grafana/grafana:latest pull_policy: if_not_present - restart: on-failure - image: grafana/grafana + container_name: grafana + restart: unless-stopped + volumes: + - ./monitoring/grafana.yaml:/etc/grafana/provisioning/datasources/datasource.yaml + - ./monitoring/grafana-bootstrap.ini:/etc/grafana/grafana.ini ports: - ${GRAFANA_HOST_PORT:-3000}:${GRAFANA_PORT:-3000} + environment: + - GF_FEATURE_TOGGLES_ENABLE=traceqlEditor + healthcheck: + interval: 5s + retries: 10 + test: wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1 + depends_on: + prometheus: + condition: service_healthy + tempo: + condition: service_healthy networks: - - ecommerce + - food-delivery + + # https://grafana.com/docs/tempo/latest/getting-started/ + # https://github.com/grafana/tempo/blob/main/example/docker-compose/local/docker-compose.yaml + # https://github.com/Domoryonok/tracing_demo/blob/master/grafana/docker-compose.yaml + # https://grafana.com/docs/grafana/latest/datasources/jaeger/ + # https://grafana.com/docs/tempo/latest/operations/architecture/ + tempo: + image: grafana/tempo:latest + command: ["-config.file=/etc/tempo.yaml" ] + volumes: + - ./otelcollector/tempo.yaml:/etc/tempo.yaml + ports: + - "3200:3200" # tempo UI + - "4322:4317" # otlp grpc + - "9411" # zipkin - export zipkin traces to tempo + - "14268" # jaeger - export jaeger traces to tempo + healthcheck: + interval: 5s + retries: 10 + test: wget --no-verbose --tries=1 --spider http://localhost:3200/status || exit 1 + networks: + - food-delivery + +# # k6-tracing: +# # image: ghcr.io/grafana/xk6-client-tracing:latest +# # environment: +# # - ENDPOINT=tempo:4317 +# # restart: always +# # depends_on: +# # - tempo postgres: image: postgres:latest pull_policy: if_not_present container_name: postgres - restart: on-failure + restart: unless-stopped ports: - ${POSTGRES_HOST_PORT:-5432}:${POSTGRES_PORT:-5432} #https://docs.docker.com/compose/environment-variables/env-file/#parameter-expansion @@ -62,79 +123,25 @@ services: - POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres} networks: - - ecommerce + - food-delivery # https://developer.redis.com/howtos/quick-start # redis-stack is a image with redis modules enabled like JSON module redis: image: redis/redis-stack:latest pull_policy: if_not_present - restart: on-failure + restart: unless-stopped container_name: redis ports: - "6379:6379" networks: - - ecommerce - - # zookeeper: - # image: confluentinc/cp-zookeeper:7.0.1 - # hostname: zookeeper - # container_name: zookeeper - # restart: on-failure - # ports: - # - "2181:2181" - # environment: - # ZOOKEEPER_CLIENT_PORT: 2181 - # ZOOKEEPER_TICK_TIME: 2000 - # networks: - # - ecommerce - - # kafka: - # image: confluentinc/cp-kafka:7.0.1 - # hostname: kafka - # container_name: kafka - # restart: on-failure - # depends_on: - # - zookeeper - # ports: - # - "9092:9092" - # environment: - # KAFKA_BROKER_ID: 1 - # KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' - # KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT - # KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 - # KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - # ADVERTISED_HOST_NAME: kafka - # KAFKA_ADVERTISED_HOSTNAME: 127.0.0.1 - # networks: - # - ecommerce - - # kafka-ui: - # image: provectuslabs/kafka-ui - # container_name: kafka-ui - # ports: - # - "8080:8080" - # restart: always - # environment: - # - KAFKA_CLUSTERS_0_NAME=local - # - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092 - # - KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181 - - # kafdrop: - # image: obsidiandynamics/kafdrop - # container_name: kafdrop - # ports: - # - '9000:9000' - # environment: - # - 'KAFKA_BROKERCONNECT=' - # - 'JVM_OPTS=-Xms32M -Xmx64M' - # - SERVER_SERVLET_CONTEXTPATH=/ + - food-delivery mongo: - image: mongo + image: mongo:latest pull_policy: if_not_present container_name: mongo - restart: on-failure + restart: unless-stopped # https://docs.docker.com/compose/environment-variables/env-file/#parameter-expansion environment: - MONGO_INITDB_ROOT_USERNAME=${MONGO_USER:-admin} @@ -142,48 +149,57 @@ services: ports: - ${MONGO_HOST_PORT:-27017}:${MONGO_PORT:-27017} networks: - - ecommerce + - food-delivery + # https://www.jaegertracing.io/docs/1.38/apis/#opentelemetry-protocol-stable + # https://deploy-preview-1892--opentelemetry.netlify.app/blog/2022/jaeger-native-otlp/ + # https://www.jaegertracing.io/docs/1.49/deployment/ + # https://www.jaegertracing.io/docs/1.49/getting-started/ + # https://opentelemetry.io/docs/instrumentation/go/exporters/ + # https://opentelemetry.io/docs/specs/otlp/ jaeger: - container_name: jaeger - pull_policy: if_not_present - restart: on-failure image: jaegertracing/all-in-one:latest - ports: - - "16686:16686" - - "14268:14268" - - "14250:14250" - networks: - - ecommerce - - zipkin: - image: openzipkin/zipkin:latest + container_name: jaeger pull_policy: if_not_present - restart: on-failure - container_name: zipkin + restart: unless-stopped + command: + - "--memory.max-traces" + - "10000" + - "--query.base-path" + - "/jaeger/ui" + - "--prometheus.server-url" + - "http://${PROMETHEUS_ADDR}" + environment: + - COLLECTOR_ZIPKIN_HOST_PORT=:9411 + - COLLECTOR_OTLP_ENABLED=true + # Store metrics in PROMETHEUS storage instead of in-memory storage + - METRICS_STORAGE_TYPE=prometheus + - PROMETHEUS_SERVER_URL=http:${PROMETHEUS_ADDR} +# # Jaeger uses Elasticsearch as span storage instead of in-memory storage +# - SPAN_STORAGE_TYPE=elasticsearch +# - ES_SERVER_URLS=http://elastic_search:${ELASTIC_PORT:-9200} +# - ES_VERSION=8 ports: - - "9411:9411" + - "16686:16686" # Jaeger UI port + - "4320:4317" # OTLP gRPC default port - for prevent duplicate expose this port that will expose also by `otel-collector` we not expose it on `4317` - `4320` could use by the app through otlptracegrpc + - "4321:4318" # OTLP Http default port - for prevent duplicate expose this port that will expose also by `otel-collector` we not expose it on `4318` - `4321` could use by the app through otlptracehttp + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:16686" ] + interval: 10s + retries: 3 + timeout: 10s networks: - - ecommerce + - food-delivery - otel-collector: - image: otel/opentelemetry-collector-contrib-dev:latest - pull_policy: if_not_present - command: ["--config=/etc/otel-collector-config.yaml", ""] - volumes: - - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml - ports: - - "1888:1888" # pprof extension - - "8888:8888" # Prometheus metrics exposed by the collector - - "8889:8889" # Prometheus exporter metrics - - "13133:13133" # health_check extension - - "4317:4317" # OTLP gRPC receiver - - "55679:55679" # zpages extension - depends_on: - - jaeger - - zipkin - networks: - - ecommerce +# zipkin: +# image: openzipkin/zipkin:latest +# pull_policy: if_not_present +# restart: unless-stopped +# container_name: zipkin +# ports: +# - "9411:9411" +# networks: +# - food-delivery # https://developers.eventstore.com/server/v21.10/installation.html#insecure-single-node # https://hub.docker.com/r/eventstore/eventstore/tags @@ -193,7 +209,7 @@ services: image: eventstore/eventstore:latest pull_policy: if_not_present container_name: eventstore - restart: on-failure + restart: unless-stopped environment: - EVENTSTORE_CLUSTER_SIZE=1 - EVENTSTORE_RUN_PROJECTIONS=All @@ -215,48 +231,138 @@ services: source: eventstore-volume-logs target: /var/log/eventstore networks: - - ecommerce + - food-delivery + +# # containers monitoring - Analyzes resource usage and performance characteristics of running containers. +# cadvisor: +# image: gcr.io/cadvisor/cadvisor:latest +# restart: unless-stopped +# ports: +# - "9091:8080" +# # network_mode: host +# volumes: +# - /:/rootfs:ro +# - /var/run:/var/run:ro +# - /sys:/sys:ro +# - /var/lib/docker/:/var/lib/docker:ro +# - /dev/disk/:/dev/disk:ro +# devices: +# - /dev/kmsg +# networks: +# - food-delivery + +# # # https://stackoverflow.com/questions/67791781/how-to-configure-apm-server-to-docker-compose-file +# # # https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html +# # apm-server: +# # image: docker.elastic.co/apm/apm-server:latest +# # cap_add: ["CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"] +# # cap_drop: ["ALL"] +# # ports: +# # - 8200:8200 +# # command: > +# # apm-server -e +# # -E apm-server.rum.enabled=true +# # -E setup.kibana.host=kibana:5601 +# # -E setup.template.settings.index.number_of_replicas=0 +# # -E apm-server.kibana.enabled=true +# # -E apm-server.kibana.host=kibana:5601 +# # -E output.elasticsearch.hosts=["elasticsearch:9200"] + +# # elasticsearch: +# # container_name: elastic_search +# # restart: unless-stopped +# # image: elasticsearch:8.5.2 +# # environment: +# # - discovery.type=single-node +# # - bootstrap.memory_lock=true +# # - xpack.monitoring.enabled=true +# # - xpack.watcher.enabled=false +# # - "ES_JAVA_OPTS=-Xms512m -Xmx512m" +# # ulimits: +# # memlock: +# # soft: -1 +# # hard: -1 +# # volumes: +# # - elastic-data:/usr/share/elasticsearch/data +# # ports: +# # - ${ELASTIC_HOST_PORT:-9200}:${ELASTIC_PORT:-9200} +# # - 9300:9300 +# # networks: +# # - food-delivery + +# # kibana: +# # image: kibana:8.5.2 +# # container_name: kibana +# # restart: unless-stopped +# # environment: +# # - ELASTICSEARCH_HOSTS=http://elastic_search:${ELASTIC_PORT:-9200} +# # ports: +# # - ${KIBANA_HOST_PORT:-5601}:${KIBANA_PORT:-5601} +# # networks: +# # - food-delivery +# # depends_on: +# # - elasticsearch + + +# # zookeeper: +# # image: confluentinc/cp-zookeeper:7.0.1 +# # hostname: zookeeper +# # container_name: zookeeper +# # restart: unless-stopped +# # ports: +# # - "2181:2181" +# # environment: +# # ZOOKEEPER_CLIENT_PORT: 2181 +# # ZOOKEEPER_TICK_TIME: 2000 +# # networks: +# # - food-delivery + +# # kafka: +# # image: confluentinc/cp-kafka:7.0.1 +# # hostname: kafka +# # container_name: kafka +# # restart: unless-stopped +# # depends_on: +# # - zookeeper +# # ports: +# # - "9092:9092" +# # environment: +# # KAFKA_BROKER_ID: 1 +# # KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' +# # KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT +# # KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 +# # KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 +# # ADVERTISED_HOST_NAME: kafka +# # KAFKA_ADVERTISED_HOSTNAME: 127.0.0.1 +# # networks: +# # - food-delivery - # elasticsearch: - # container_name: elastic_search - # restart: on-failure - # image: elasticsearch:8.5.2 - # environment: - # - discovery.type=single-node - # - bootstrap.memory_lock=true - # - xpack.monitoring.enabled=true - # - xpack.watcher.enabled=false - # - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - # ulimits: - # memlock: - # soft: -1 - # hard: -1 - # volumes: - # - elastic-data:/usr/share/elasticsearch/data - # ports: - # - ${ELASTIC_HOST_PORT:-9200}:${ELASTIC_PORT:-9200} - # - 9300:9300 - # networks: - # - ecommerce +# # kafka-ui: +# # image: provectuslabs/kafka-ui +# # container_name: kafka-ui +# # ports: +# # - "8080:8080" +# # restart: always +# # environment: +# # - KAFKA_CLUSTERS_0_NAME=local +# # - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092 +# # - KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181 - # kibana: - # image: kibana:8.5.2 - # container_name: kibana - # restart: on-failure - # environment: - # - ELASTICSEARCH_HOSTS=http://elastic_search:9200 - # ports: - # - ${KIBANA_HOST_PORT:-5601}:${KIBANA_PORT:-5601} - # networks: - # - ecommerce - # depends_on: - # - elasticsearch +# # kafdrop: +# # image: obsidiandynamics/kafdrop +# # container_name: kafdrop +# # ports: +# # - '9000:9000' +# # environment: +# # - 'KAFKA_BROKERCONNECT=' +# # - 'JVM_OPTS=-Xms32M -Xmx64M' +# # - SERVER_SERVLET_CONTEXTPATH=/ volumes: eventstore-volume-data: eventstore-volume-logs: - elastic-data: +# elastic-data: networks: - ecommerce: - name: ecommerce + food-delivery: + name: food-delivery diff --git a/deployments/docker-compose/monitoring/grafana-bootstrap.ini b/deployments/docker-compose/monitoring/grafana-bootstrap.ini new file mode 100644 index 00000000..21b3cdbd --- /dev/null +++ b/deployments/docker-compose/monitoring/grafana-bootstrap.ini @@ -0,0 +1,2 @@ +[feature_toggles] +enable = tempoSearch tempoBackendSearch diff --git a/deployments/docker-compose/monitoring/grafana.yaml b/deployments/docker-compose/monitoring/grafana.yaml new file mode 100644 index 00000000..e1001431 --- /dev/null +++ b/deployments/docker-compose/monitoring/grafana.yaml @@ -0,0 +1,46 @@ +apiVersion: 1 + +datasources: + # showing Prometheus as one of dashboard tabs + - name: Prometheus + type: prometheus + uid: prometheus + url: http://prometheus:9090 + isDefault: true + access: proxy + editable: true + jsonData: + httpMethod: GET + + # showing Jaeger as one of dashboard tabs + - name: Jaeger + type: jaeger + url: http://jaeger:16686 + access: proxy + editable: true + + # showing Tempo as one of dashboard tabs + # https://github.com/grafana/tempo/blob/main/example/docker-compose/shared/grafana-datasources.yaml#L16 + - name: Tempo + type: tempo + access: proxy + orgId: 1 + url: http://tempo:3200 + basicAuth: false + isDefault: false + version: 1 + editable: false + apiVersion: 1 + uid: tempo + +# # showing Loki as one of dashboard tabs +# - name: loki +# type: loki +# uid: my-loki +# access: proxy +# orgId: 1 +# url: http://loki:3100 +# basicAuth: false +# isDefault: false +# version: 1 +# editable: true diff --git a/deployments/docker-compose/monitoring/prometheus.yml b/deployments/docker-compose/monitoring/prometheus.yml new file mode 100644 index 00000000..39e05870 --- /dev/null +++ b/deployments/docker-compose/monitoring/prometheus.yml @@ -0,0 +1,72 @@ +# prometheus dashboard: http://localhost:9090 +# scrap metrics our app(host.docker.internal:7000/metrics) and default internal metrics in prometheus(localhost:9090/metrics) and internal metrics in grafana(localhost:3000/metrics) + +global: + scrape_interval: 5s + evaluation_interval: 5s + +scrape_configs: + # http://localhost:9090/metrics + - job_name: "prometheus" + metrics_path: "/metrics" + static_configs: + - targets: ["localhost:9090"] + + # http://localhost:3000/metrics + # localhost doesn't work here because inner prometheus container localhost will infer to localhost inner the container which is different with system localhost. for access to actual system localhost we should use `host.docker.internal` + # instead of `localhost` to access node_exporter container inner prometheus container we can use our container network name to access this container because they are in same docker-compose and same default network + - job_name: grafana + static_configs: + - targets: [ "grafana:3000" ] + + # http://localhost:9100/metrics + # localhost doesn't work here because inner prometheus container localhost will infer to localhost inner the container which is different with system localhost. for access to actual system localhost we should use `host.docker.internal` + # instead of `localhost` to access node_exporter container inner prometheus container we can use our container network name to access this container because they are in same docker-compose and same default network + # node_exporter will use for gathering metrics on the system level with its own /metrics endpoint like cpu, ram, ... + - job_name: node + scrape_interval: 1s + static_configs: + - targets: [ 'node_exporter:9100' ] + + # http://localhost:7000/metrics + # localhost doesn't work here because inner prometheus container localhost will infer to localhost inner the container which is different with system localhost. for access to actual system localhost we should use `host.docker.internal` + # instead of `localhost` to access node_exporter container inner prometheus container we can use our container network name to access this container because they are in same docker-compose and same default network + - job_name: product_write_service + scrape_interval: 1s + static_configs: + - targets: [ "host.docker.internal:7000" ] + + # http://localhost:7001/metrics + # localhost doesn't work here because inner prometheus container localhost will infer to localhost inner the container which is different with system localhost. for access to actual system localhost we should use `host.docker.internal` + # instead of `localhost` to access node_exporter container inner prometheus container we can use our container network name to access this container because they are in same docker-compose and same default network + - job_name: product_read_service + scrape_interval: 1s + static_configs: + - targets: [ "host.docker.internal:7001" ] + + # http://localhost:8000/metrics + # localhost doesn't work here because inner prometheus container localhost will infer to localhost inner the container which is different with system localhost. for access to actual system localhost we should use `host.docker.internal` + # instead of `localhost` to access node_exporter container inner prometheus container we can use our container network name to access this container because they are in same docker-compose and same default network + - job_name: orderservice + scrape_interval: 1s + static_configs: + - targets: [ "host.docker.internal:8000" ] + + # Example job for cadvisor + - job_name: 'cadvisor' + static_configs: + - targets: ['cadvisor:8080'] + +# # scrap exported metrics by otel-collector +# # OpenTelemetry collector +# - job_name: 'otel-collector' +# scrape_interval: 10s +# static_configs: +# - targets: ['otel-collector:8889'] +# - targets: ['otel-collector:8888'] + + # internal metrics by tempo + # https://github.com/grafana/tempo/blob/main/example/docker-compose/shared/prometheus.yaml + - job_name: 'tempo' + static_configs: + - targets: [ 'tempo:3200' ] diff --git a/deployments/docker-compose/otel-collector-config.yaml b/deployments/docker-compose/otel-collector-config.yaml deleted file mode 100644 index b46e0393..00000000 --- a/deployments/docker-compose/otel-collector-config.yaml +++ /dev/null @@ -1,51 +0,0 @@ -receivers: - otlp: - protocols: - grpc: - -exporters: - prometheus: - endpoint: "0.0.0.0:8889" - const_labels: - label1: value1 - - logging: - - zipkin: - endpoint: "http://zipkin:9411/api/v2/spans" - format: proto - - jaeger: - endpoint: jaeger:14250 - tls: - insecure: true - - elasticsearch: - endpoints: - - "http://elastic_search:9200" - -processors: - batch: - -extensions: - health_check: - pprof: - endpoint: :1888 - zpages: - endpoint: :55679 - -service: - extensions: [pprof, zpages, health_check] - pipelines: - traces: - receivers: [otlp] - processors: [batch] - exporters: [logging, zipkin, jaeger] - metrics: - receivers: [otlp] - processors: [batch] - exporters: [logging, prometheus] - logs: - receivers: [otlp] - processors: [batch] - exporters: [logging, elasticsearch] diff --git a/deployments/docker-compose/otelcollector/otelcol-config-extras.yml b/deployments/docker-compose/otelcollector/otelcol-config-extras.yml new file mode 100644 index 00000000..f557025e --- /dev/null +++ b/deployments/docker-compose/otelcollector/otelcol-config-extras.yml @@ -0,0 +1,4 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 +# extra settings to be merged into OpenTelemetry Collector configuration +# do not delete this file diff --git a/deployments/docker-compose/otelcollector/otelcol-config.yml b/deployments/docker-compose/otelcollector/otelcol-config.yml new file mode 100644 index 00000000..68533ee2 --- /dev/null +++ b/deployments/docker-compose/otelcollector/otelcol-config.yml @@ -0,0 +1,54 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +# Ref: https://github.com/open-telemetry/opentelemetry-demo/blob/main/src/otelcollector/otelcol-observability.yml +# https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/examples/demo/otel-collector-config.yaml +# https://www.youtube.com/watch?v=EeU-k659lpw + +receivers: + otlp: + protocols: + grpc: + http: + cors: + allowed_origins: + - "http://*" + - "https://*" + +exporters: + logging: + +processors: + batch: + transform: + metric_statements: + - context: metric + statements: + # FIXME: remove this when this is issue is resolved: https://github.com/open-telemetry/opentelemetry-java/issues/4834 + - set(description, "") where name == "queueSize" + # FIXME: remove this when the following 2 issues are resolved + # Java: https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/9478 + # Go: https://github.com/open-telemetry/opentelemetry-go-contrib/issues/4301 + - set(description, "") where name == "rpc.server.duration" + +connectors: + spanmetrics: + +extensions: + health_check: + pprof: + endpoint: :1888 + zpages: + endpoint: :55679 + +service: + extensions: [pprof, zpages, health_check] + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [logging, zipkin, otlp] + metrics: + receivers: [otlp] + processors: [batch] + exporters: [logging, prometheus] diff --git a/deployments/docker-compose/otelcollector/otelcol-observability.yml b/deployments/docker-compose/otelcollector/otelcol-observability.yml new file mode 100644 index 00000000..32f0b51e --- /dev/null +++ b/deployments/docker-compose/otelcollector/otelcol-observability.yml @@ -0,0 +1,49 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + + +exporters: + otlp: + endpoint: "jaeger:4317" + tls: + insecure: true + prometheus: + endpoint: "otelcol:9464" + resource_to_telemetry_conversion: + enabled: true + enable_open_metrics: true + + # #https://www.elastic.co/guide/en/apm/guide/current/open-telemetry-direct.html#open-telemetry-proxy-apm + # otlp/elastic: + # # Elastic APM server https endpoint without the "https://" prefix + # endpoint: "http://elastic-apm-server:8200" + # headers: + # # Elastic APM Server secret token + # Authorization: "Bearer ${ELASTIC_APM_SECRET_TOKEN}" + + # # https://uptrace:dev/opentelemetry/prometheus-metrics:html#prometheus-receiver + # otlp/uptrace: + # endpoint: otlp.uptrace.dev:4317 + # headers: + # # Copy your project DSN here + # uptrace-dsn: 'https://@uptrace.dev/ + + + logging: + + zipkin: + endpoint: "http://zipkin-all-in-one:9411/api/v2/spans" + format: proto + + otlp: + endpoint: "jaeger-all-in-one:4317" + tls: + insecure: true + +service: + pipelines: + traces: + exporters: [otlp, logging, spanmetrics] + metrics: + exporters: [prometheus, logging] + diff --git a/deployments/docker-compose/otelcollector/tempo.yaml b/deployments/docker-compose/otelcollector/tempo.yaml new file mode 100644 index 00000000..743ef05d --- /dev/null +++ b/deployments/docker-compose/otelcollector/tempo.yaml @@ -0,0 +1,57 @@ +# https://github.com/grafana/tempo/blob/main/example/docker-compose/shared/tempo.yaml + +server: + http_listen_port: 3200 + +query_frontend: + search: + duration_slo: 5s + throughput_bytes_slo: 1.073741824e+09 + trace_by_id: + duration_slo: 5s + +distributor: + receivers: # this configuration will listen on all ports and protocols that tempo is capable of. + jaeger: # the receives all come from the OpenTelemetry collector. more configuration information can + protocols: # be found there: https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver + thrift_http: # + grpc: # for a production deployment you should only enable the receivers you need! + thrift_binary: + thrift_compact: + zipkin: + otlp: + protocols: + http: + grpc: + opencensus: + +ingester: + max_block_duration: 5m # cut the headblock when this much time passes. this is being set for demo purposes and should probably be left alone normally + +compactor: + compaction: + block_retention: 1h # overall Tempo trace retention. set for demo purposes + +metrics_generator: + registry: + external_labels: + source: tempo + cluster: docker-compose + storage: + path: /tmp/tempo/generator/wal + remote_write: + - url: http://prometheus:9090/api/v1/write + send_exemplars: true + +storage: + trace: + backend: local # backend configuration to use + wal: + path: /tmp/tempo/wal # where to store the the wal locally + local: + path: /tmp/tempo/blocks + +overrides: + defaults: + metrics_generator: + processors: [service-graphs, span-metrics] # enables metrics generator diff --git a/deployments/docker-compose/traefik.yaml b/deployments/docker-compose/traefik.yaml index ba35cfd1..9233b9bc 100644 --- a/deployments/docker-compose/traefik.yaml +++ b/deployments/docker-compose/traefik.yaml @@ -141,9 +141,9 @@ - + - + diff --git a/internal/pkg/bun/postgres/bun_postgres.go b/internal/pkg/bun/postgres/bun_postgres.go index 81d26438..634f0802 100644 --- a/internal/pkg/bun/postgres/bun_postgres.go +++ b/internal/pkg/bun/postgres/bun_postgres.go @@ -4,7 +4,7 @@ import ( "database/sql" "fmt" - bun2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/bun" + bun2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/bun" "emperror.dev/errors" "github.com/uptrace/bun" @@ -37,7 +37,7 @@ func NewBunDB(cfg *bun2.BunConfig) (*bun.DB, error) { //pgconn := pgdriver.NewConnector( // pgdriver.WithNetwork("tcp"), // pgdriver.WithAddr("localhost:5437"), - // pgdriver.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}), + // pgdriver.WithTLSConfig(&tls.config{InsecureSkipVerify: true}), // pgdriver.WithUser("test"), // pgdriver.WithPassword("test"), // pgdriver.WithDatabase("test"), diff --git a/internal/pkg/config.test.json b/internal/pkg/config.test.json new file mode 100644 index 00000000..5278a2ee --- /dev/null +++ b/internal/pkg/config.test.json @@ -0,0 +1,91 @@ +{ + "appOptions": { + "serviceName": "test", + "deliveryType": "http" + }, + "grpcOptions": { + "name": "test", + "port": ":6003", + "host": "localhost", + "development": true + }, + "echoHttpOptions": { + "name": "test", + "port": ":7000", + "development": true, + "timeout": 30, + "basePath": "/api/v1", + "host": "http://localhost", + "productsPath": "products", + "debugHeaders": true, + "httpClientDebug": true, + "debugErrorsResponse": true, + "ignoreLogUrls": [ + "metrics" + ] + }, + "logOptions": { + "level": "debug", + "logType": 0, + "callerEnabled": false + }, + "gormOptions": { + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "postgres", + "dbName": "catalogs_service", + "sslMode": false + }, + "rabbitmqOptions": { + "autoStart": true, + "reconnecting": true, + "rabbitmqHostOptions": { + "userName": "guest", + "password": "guest", + "hostName": "localhost", + "port": 5672, + "httpPort": 15672 + } + }, + "tracingOptions": { + "enable": true, + "serviceName": "test", + "instrumentationName": "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test", + "id": 1, + "useStdout": false, + "alwaysOnSampler": true, + "jaegerExporterOptions": { + "otlpEndpoint": "localhost:4317" + }, + "zipkinExporterOptions": { + "url": "http://localhost:9411/api/v2/spans" + } + }, + "metricsOptions": { + "host": "localhost", + "port": ":3001", + "metricsRoutePath": "metrics", + "serviceName": "test", + "instrumentationName": "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test" + }, + "eventStoreDbOptions": { + "host": "localhost", + "httpPort": 2113, + "tcpPort": 1113, + "subscription": { + "subscriptionId": "orders-subscription", + "prefix": ["order-"] + } + }, + "migrationOptions": { + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "postgres", + "dbName": "catalogs_service", + "sslMode": false, + "migrationsDir": "db/migrations/goose-migrate", + "skipMigration": false + } +} diff --git a/internal/pkg/config/config_helper.go b/internal/pkg/config/config_helper.go index 9a83a632..7b27cb04 100644 --- a/internal/pkg/config/config_helper.go +++ b/internal/pkg/config/config_helper.go @@ -4,10 +4,11 @@ import ( "fmt" "os" "path/filepath" + "strings" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "emperror.dev/errors" "github.com/caarlos0/env/v8" @@ -15,30 +16,45 @@ import ( "github.com/spf13/viper" ) -func BindConfig[T any](environments ...environemnt.Environment) (T, error) { +func BindConfig[T any](environments ...environment.Environment) (T, error) { return BindConfigKey[T]("", environments...) } -func BindConfigKey[T any](configKey string, environments ...environemnt.Environment) (T, error) { +func BindConfigKey[T any]( + configKey string, + environments ...environment.Environment, +) (T, error) { var configPath string - environment := environemnt.Environment("") + currentEnv := environment.Environment("") if len(environments) > 0 { - environment = environments[0] + currentEnv = environments[0] } else { - environment = constants.Dev + currentEnv = constants.Dev } + cfg := typeMapper.GenericInstanceByT[T]() + + // this should set before reading config values from json file + // https://github.com/mcuadros/go-defaults + defaults.SetDefaults(cfg) + // https://articles.wesionary.team/environment-variable-configuration-in-your-golang-project-using-viper-4e8289ef664d // when we `Set` a viper with string value, we should get it from viper with `viper.GetString`, elsewhere we get empty string - // load `config path` from environment variable or viper internal registry + // load `config path` from env variable or viper internal registry configPathFromEnv := viper.GetString(constants.ConfigPath) + if configPathFromEnv != "" { configPath = configPathFromEnv } else { // https://stackoverflow.com/questions/31873396/is-it-possible-to-get-the-current-root-of-package-structure-as-a-string-in-golan // https://stackoverflow.com/questions/18537257/how-to-get-the-directory-of-the-currently-running-file - d, err := getConfigRootPath() + appRootPath := viper.GetString(constants.AppRootPath) + if appRootPath == "" { + appRootPath = environment.GetProjectRootWorkingDirectory() + } + + d, err := searchForConfigFileDir(appRootPath, currentEnv) if err != nil { return *new(T), err } @@ -46,14 +62,8 @@ func BindConfigKey[T any](configKey string, environments ...environemnt.Environm configPath = d } - cfg := typeMapper.GenericInstanceByT[T]() - - // this should set before reading config values from json file - // https://github.com/mcuadros/go-defaults - defaults.SetDefaults(cfg) - // https://github.com/spf13/viper/issues/390#issuecomment-718756752 - viper.SetConfigName(fmt.Sprintf("config.%s", environment)) + viper.SetConfigName(fmt.Sprintf("config.%s", currentEnv)) viper.AddConfigPath(configPath) viper.SetConfigType(constants.Json) @@ -62,6 +72,7 @@ func BindConfigKey[T any](configKey string, environments ...environemnt.Environm } if len(configKey) == 0 { + // load configs from config file to config object if err := viper.Unmarshal(cfg); err != nil { return *new(T), errors.WrapIf(err, "viper.Unmarshal") } @@ -81,23 +92,61 @@ func BindConfigKey[T any](configKey string, environments ...environemnt.Environm return cfg, nil } -func getConfigRootPath() (string, error) { - // Get the current working directory - // Getwd gives us the current working directory that we are running our app with `go run` command. in goland we can specify this working directory for the project - wd, err := os.Getwd() - if err != nil { - return "", err - } - fmt.Println(fmt.Sprintf("Current working directory is: %s", wd)) - - // Get the absolute path of the executed project directory - absCurrentDir, err := filepath.Abs(wd) - if err != nil { - return "", err +// searchForConfigFileDir searches for the first directory within the specified root directory and its subdirectories +// that contains a file named "config.%s.json" where "%s" is replaced with the provided environment string. +// It returns the path of the first directory that contains the config file or an error if no such directory is found. +// +// Parameters: +// +// rootDir: The root directory to start the search from. +// environment: The environment string to replace "%s" in the config file name. +// +// Returns: +// +// string: The path of the directory containing the config file, or an empty string if not found. +// error: An error indicating any issues encountered during the search. +func searchForConfigFileDir( + rootDir string, + env environment.Environment, +) (string, error) { + var result string + + // Walk the directory tree starting from rootDir + err := filepath.Walk( + rootDir, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Check if the file is named "config.%s.json" (replace %s with the env) + if !info.IsDir() && + strings.EqualFold( + info.Name(), + fmt.Sprintf("config.%s.json", env), + ) || + strings.EqualFold( + info.Name(), + fmt.Sprintf("config.%s.yaml", env), + ) || + strings.EqualFold( + info.Name(), + fmt.Sprintf("config.%s.yml", env), + ) { + // Get the directory name containing the config file + dir := filepath.Dir(path) + result = dir + + return filepath.SkipDir // Skip further traversal + } + + return nil + }, + ) + + if result != "" { + return result, nil } - // Get the path to the "config" folder within the project directory - configPath := filepath.Join(absCurrentDir, "config") - - return configPath, nil + return "", errors.WrapIf(err, "No directory with config file found") } diff --git a/internal/pkg/config/configfx.go b/internal/pkg/config/configfx.go index b17ea753..9de11cd1 100644 --- a/internal/pkg/config/configfx.go +++ b/internal/pkg/config/configfx.go @@ -1,7 +1,7 @@ package config import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" "go.uber.org/fx" ) @@ -10,16 +10,16 @@ import ( // https://uber-go.github.io/fx/modules.html var Module = fx.Module( "configfx", - fx.Provide(func() environemnt.Environment { - return environemnt.ConfigAppEnv() + fx.Provide(func() environment.Environment { + return environment.ConfigAppEnv() }), ) -var ModuleFunc = func(e environemnt.Environment) fx.Option { +var ModuleFunc = func(e environment.Environment) fx.Option { return fx.Module( "configfx", - fx.Provide(func() environemnt.Environment { - return environemnt.ConfigAppEnv(e) + fx.Provide(func() environment.Environment { + return environment.ConfigAppEnv(e) }), ) } diff --git a/internal/pkg/config/environemnt/environment.go b/internal/pkg/config/environment/environment.go similarity index 60% rename from internal/pkg/config/environemnt/environment.go rename to internal/pkg/config/environment/environment.go index 651d1f1d..99a72e80 100644 --- a/internal/pkg/config/environemnt/environment.go +++ b/internal/pkg/config/environment/environment.go @@ -1,13 +1,12 @@ -package environemnt +package environment import ( "log" "os" "path/filepath" - "strings" "syscall" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" "emperror.dev/errors" "github.com/joho/godotenv" @@ -34,7 +33,7 @@ func ConfigAppEnv(environments ...Environment) Environment { viper.AutomaticEnv() // https://articles.wesionary.team/environment-variable-configuration-in-your-golang-project-using-viper-4e8289ef664d - // load environment variables form .env files to system environment variables, it just find `.env` file in our current `executing working directory` in our app for example `catalogs_service` + // load environment variables form .env files to system environment variables, it just finds `.env` file in our current `executing working directory` in our app for example `catalogs_service` err := loadEnvFilesRecursive() if err != nil { log.Printf(".env file cannot be found, err: %v", err) @@ -42,6 +41,8 @@ func ConfigAppEnv(environments ...Environment) Environment { setRootWorkingDirectoryEnvironment() + FixProjectRootWorkingDirectoryPath() + manualEnv := os.Getenv(constants.AppEnv) if manualEnv != "" { @@ -59,6 +60,10 @@ func (env Environment) IsProduction() bool { return env == Production } +func (env Environment) IsTest() bool { + return env == Test +} + func (env Environment) GetEnvironmentName() string { return string(env) } @@ -67,6 +72,7 @@ func EnvString(key, fallback string) string { if value, ok := syscall.Getenv(key); ok { return value } + return fallback } @@ -101,37 +107,8 @@ func loadEnvFilesRecursive() error { } func setRootWorkingDirectoryEnvironment() { - // https://articles.wesionary.team/environment-variable-configuration-in-your-golang-project-using-viper-4e8289ef664d - // when we `Set` a viper with string value, we should get it from viper with `viper.GetString`, elsewhere we get empty string - // viper will get it from `os env` or a .env file - pn := viper.GetString(constants.PROJECT_NAME_ENV) - if pn == "" { - log.Fatalf( - "can't find environment variable `%s` in the system environment variables or a .env file", - constants.PROJECT_NAME_ENV, - ) - } - - // set root working directory of our app in the viper - // https://stackoverflow.com/a/47785436/581476 - wd, _ := os.Getwd() - - for !strings.HasSuffix(wd, pn) { - wd = filepath.Dir(wd) - } - - absoluteRootWorkingDirectory, _ := filepath.Abs(wd) + absoluteRootWorkingDirectory := GetProjectRootWorkingDirectory() // when we `Set` a viper with string value, we should get it from viper with `viper.GetString`, elsewhere we get empty string viper.Set(constants.AppRootPath, absoluteRootWorkingDirectory) - - configPath := viper.GetString(constants.ConfigPath) - - // check for existing `CONFIG_PATH` variable in system environment variables - we can set it directly in .env file or system environments - if configPath == "" { - configPath := filepath.Join(absoluteRootWorkingDirectory, "config") - - // when we `Set` a viper with string value, we should get it from viper with `viper.GetString`, elsewhere we get empty string - viper.Set(constants.ConfigPath, configPath) - } } diff --git a/internal/pkg/config/environment/helpers.go b/internal/pkg/config/environment/helpers.go new file mode 100644 index 00000000..6fa5b80b --- /dev/null +++ b/internal/pkg/config/environment/helpers.go @@ -0,0 +1,90 @@ +package environment + +import ( + "log" + "os" + "path/filepath" + "strings" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" + + "emperror.dev/errors" + "github.com/spf13/viper" +) + +func FixProjectRootWorkingDirectoryPath() { + currentWD, _ := os.Getwd() + log.Printf("Current working directory is: `%s`", currentWD) + + rootDir := GetProjectRootWorkingDirectory() + // change working directory + _ = os.Chdir(rootDir) + newWD, _ := os.Getwd() + + log.Printf("New fixed working directory is: `%s`", newWD) +} + +func GetProjectRootWorkingDirectory() string { + var rootWorkingDirectory string + // https://articles.wesionary.team/environment-variable-configuration-in-your-golang-project-using-viper-4e8289ef664d + // when we `Set` a viper with string value, we should get it from viper with `viper.GetString`, elsewhere we get empty string + // viper will get it from `os env` or a .env file + pn := viper.GetString(constants.PROJECT_NAME_ENV) + if pn != "" { + rootWorkingDirectory = getProjectRootDirectoryFromProjectName(pn) + } else { + wd, _ := os.Getwd() + dir, err := searchRootDirectory(wd) + if err != nil { + log.Fatal(err) + } + rootWorkingDirectory = dir + } + + absoluteRootWorkingDirectory, _ := filepath.Abs(rootWorkingDirectory) + + return absoluteRootWorkingDirectory +} + +func getProjectRootDirectoryFromProjectName(pn string) string { + // set root working directory of our app in the viper + // https://stackoverflow.com/a/47785436/581476 + wd, _ := os.Getwd() + + for !strings.HasSuffix(wd, pn) { + wd = filepath.Dir(wd) + } + + return wd +} + +func searchRootDirectory( + dir string, +) (string, error) { + // List files and directories in the current directory + files, err := os.ReadDir(dir) + if err != nil { + return "", errors.WrapIf(err, "Error reading directory") + } + + for _, file := range files { + if !file.IsDir() { + fileName := file.Name() + if strings.EqualFold( + fileName, + "go.mod", + ) { + return dir, nil + } + } + } + + // If no config file found in this directory, recursively search its parent + parentDir := filepath.Dir(dir) + if parentDir == dir { + // We've reached the root directory, and no go.mod file was found + return "", errors.WrapIf(err, "No go.mod file found") + } + + return searchRootDirectory(parentDir) +} diff --git a/internal/pkg/core/core_fx.go b/internal/pkg/core/core_fx.go index e39f7242..892063ef 100644 --- a/internal/pkg/core/core_fx.go +++ b/internal/pkg/core/core_fx.go @@ -1,9 +1,7 @@ package core import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer/json" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer/json" "go.uber.org/fx" ) @@ -13,9 +11,9 @@ import ( var Module = fx.Module( "corefx", fx.Provide( - json.NewDefaultSerializer, - serializer.NewDefaultEventSerializer, - serializer.NewDefaultMetadataSerializer, + json.NewDefaultJsonSerializer, + json.NewDefaultEventJsonSerializer, + json.NewDefaultMessageJsonSerializer, + json.NewDefaultMetadataJsonSerializer, ), - fx.Invoke(defaultLogger.SetupDefaultLogger), ) diff --git a/internal/pkg/core/cqrs/command.go b/internal/pkg/core/cqrs/command.go new file mode 100644 index 00000000..f0e8ce63 --- /dev/null +++ b/internal/pkg/core/cqrs/command.go @@ -0,0 +1,34 @@ +package cqrs + +type command struct { + TypeInfo + Request +} + +type Command interface { + isCommand() + + Request + TypeInfo +} + +func NewCommandByT[T any]() Command { + c := &command{ + TypeInfo: NewTypeInfoT[T](), + Request: NewRequest(), + } + + return c +} + +// https://github.com/EventStore/EventStore-Client-Go/blob/master/esdb/position.go#L29 +func (c *command) isCommand() { +} + +func IsCommand(obj interface{}) bool { + if _, ok := obj.(Command); ok { + return true + } + + return false +} diff --git a/internal/pkg/core/cqrs/command_handler.go b/internal/pkg/core/cqrs/command_handler.go new file mode 100644 index 00000000..d0b815e1 --- /dev/null +++ b/internal/pkg/core/cqrs/command_handler.go @@ -0,0 +1,15 @@ +package cqrs + +import ( + "context" + + "github.com/mehdihadeli/go-mediatr" +) + +type CommandHandler[TCommand Command, TResponse any] interface { + Handle(ctx context.Context, command TCommand) (TResponse, error) +} + +type CommandHandlerVoid[TCommand Command] interface { + Handle(ctx context.Context, command TCommand) (*mediatr.Unit, error) +} diff --git a/internal/pkg/core/cqrs/command_test.go b/internal/pkg/core/cqrs/command_test.go new file mode 100644 index 00000000..36a55228 --- /dev/null +++ b/internal/pkg/core/cqrs/command_test.go @@ -0,0 +1,54 @@ +package cqrs + +import ( + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/assert" +) + +func Test_Command(t *testing.T) { + command := &CreateProductTest{ + Command: NewCommandByT[*CreateProductTest](), + ProductID: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + } + + isImplementedCommand := typemapper.ImplementedInterfaceT[Command](command) + assert.True(t, isImplementedCommand) + + var i interface{} = command + _, isCommand := i.(Command) + _, isTypeInfo := i.(TypeInfo) + _, isQuery := i.(Query) + _, isRequest := i.(Request) + + assert.True(t, isCommand) + assert.True(t, isTypeInfo) + assert.True(t, isRequest) + assert.False(t, isQuery) + + assert.True(t, IsCommand(command)) + assert.True(t, IsRequest(command)) + assert.False(t, IsQuery(command)) + + assert.Equal(t, command.ShortTypeName(), "*CreateProductTest") + assert.Equal(t, command.FullTypeName(), "*cqrs.CreateProductTest") +} + +type CreateProductTest struct { + Command + + Name string + ProductID uuid.UUID + Description string + Price float64 + CreatedAt time.Time +} diff --git a/internal/pkg/core/cqrs/handler.go b/internal/pkg/core/cqrs/handler.go new file mode 100644 index 00000000..39ee1a7e --- /dev/null +++ b/internal/pkg/core/cqrs/handler.go @@ -0,0 +1,14 @@ +package cqrs + +import "github.com/mehdihadeli/go-mediatr" + +// HandlerRegisterer for registering `RequestHandler` to mediatr registry, if handler implements this interface it will be registered automatically +type HandlerRegisterer interface { + RegisterHandler() error +} + +// RequestHandlerWithRegisterer for registering `RequestHandler` to mediatr registry and handling `RequestHandler` +type RequestHandlerWithRegisterer[TRequest any, TResponse any] interface { + HandlerRegisterer + mediatr.RequestHandler[TRequest, TResponse] +} diff --git a/internal/pkg/core/cqrs/helpers.go b/internal/pkg/core/cqrs/helpers.go new file mode 100644 index 00000000..2dbef123 --- /dev/null +++ b/internal/pkg/core/cqrs/helpers.go @@ -0,0 +1,22 @@ +package cqrs + +import ( + "fmt" + + "go.uber.org/fx" +) + +// when we register multiple handlers with output type `mediatr.RequestHandler` we get exception `type already provided`, so we should use tags annotation + +// AsHandler annotates the given constructor to state that +// it provides a handler to the "handlers" group. +func AsHandler(handler interface{}, handlerGroupName string) interface{} { + return fx.Annotate( + handler, + fx.As(new(HandlerRegisterer)), + fx.ResultTags(fmt.Sprintf( + `group:"%s"`, + handlerGroupName, + )), + ) +} diff --git a/internal/pkg/core/cqrs/internal_command.go b/internal/pkg/core/cqrs/internal_command.go new file mode 100644 index 00000000..adaf32ee --- /dev/null +++ b/internal/pkg/core/cqrs/internal_command.go @@ -0,0 +1,25 @@ +package cqrs + +type internalCommand struct { + Command +} + +type InternalCommand interface { + Command + isInternalCommand() +} + +func NewInternalCommandByT[T any]() InternalCommand { + return &internalCommand{Command: NewCommandByT[T]()} +} + +func (c *internalCommand) isInternalCommand() { +} + +func IsInternalCommand(obj interface{}) bool { + if _, ok := obj.(InternalCommand); ok { + return true + } + + return false +} diff --git a/internal/pkg/core/cqrs/internal_command_test.go b/internal/pkg/core/cqrs/internal_command_test.go new file mode 100644 index 00000000..e9729f96 --- /dev/null +++ b/internal/pkg/core/cqrs/internal_command_test.go @@ -0,0 +1 @@ +package cqrs diff --git a/internal/pkg/core/cqrs/notification.go b/internal/pkg/core/cqrs/notification.go new file mode 100644 index 00000000..974c03fa --- /dev/null +++ b/internal/pkg/core/cqrs/notification.go @@ -0,0 +1,30 @@ +package cqrs + +type notification struct { + TypeInfo +} + +type Notification interface { + isNotification() + + TypeInfo +} + +func NewNotificationByT[T any]() Notification { + n := ¬ification{ + TypeInfo: NewTypeInfoT[T](), + } + + return n +} + +func (c *notification) isNotification() { +} + +func IsNotification(obj interface{}) bool { + if _, ok := obj.(Notification); ok { + return true + } + + return false +} diff --git a/internal/pkg/core/cqrs/query.go b/internal/pkg/core/cqrs/query.go new file mode 100644 index 00000000..bbf89028 --- /dev/null +++ b/internal/pkg/core/cqrs/query.go @@ -0,0 +1,32 @@ +package cqrs + +type query struct { + TypeInfo + Request +} + +type Query interface { + isQuery() + + Request + TypeInfo +} + +func NewQueryByT[T any]() Query { + return &query{ + TypeInfo: NewTypeInfoT[T](), + Request: NewRequest(), + } +} + +// https://github.com/EventStore/EventStore-Client-Go/blob/master/esdb/position.go#L29 +func (q *query) isQuery() { +} + +func IsQuery(obj interface{}) bool { + if _, ok := obj.(Query); ok { + return true + } + + return false +} diff --git a/internal/pkg/core/cqrs/query_handler.go b/internal/pkg/core/cqrs/query_handler.go new file mode 100644 index 00000000..818c423e --- /dev/null +++ b/internal/pkg/core/cqrs/query_handler.go @@ -0,0 +1,9 @@ +package cqrs + +import ( + "context" +) + +type QueryHandler[TQuery Query, TResponse any] interface { + Handle(ctx context.Context, query TQuery) (TResponse, error) +} diff --git a/internal/pkg/core/cqrs/query_test.go b/internal/pkg/core/cqrs/query_test.go new file mode 100644 index 00000000..60da4a73 --- /dev/null +++ b/internal/pkg/core/cqrs/query_test.go @@ -0,0 +1,44 @@ +package cqrs + +import ( + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/assert" +) + +func Test_Query(t *testing.T) { + query := &GetProductById{ + Query: NewQueryByT[*GetProductById](), + ProductID: uuid.NewV4(), + } + + isImplementedQuery := typemapper.ImplementedInterfaceT[Query](query) + assert.True(t, isImplementedQuery) + + var i interface{} = query + _, isQuery := i.(Query) + _, isTypeInfo := i.(TypeInfo) + _, isCommand := i.(Command) + _, isRequest := i.(Request) + + assert.True(t, isQuery) + assert.False(t, isCommand) + assert.True(t, isTypeInfo) + assert.True(t, isRequest) + + assert.True(t, IsQuery(query)) + assert.False(t, IsCommand(query)) + assert.True(t, IsRequest(query)) + + assert.Equal(t, query.ShortTypeName(), "*GetProductById") + assert.Equal(t, query.FullTypeName(), "*cqrs.GetProductById") +} + +type GetProductById struct { + Query + + ProductID uuid.UUID +} diff --git a/internal/pkg/core/cqrs/request.go b/internal/pkg/core/cqrs/request.go new file mode 100644 index 00000000..bed4bdc5 --- /dev/null +++ b/internal/pkg/core/cqrs/request.go @@ -0,0 +1,22 @@ +package cqrs + +type request struct{} + +type Request interface { + isRequest() +} + +func NewRequest() Request { + return &request{} +} + +func (r *request) isRequest() { +} + +func IsRequest(obj interface{}) bool { + if _, ok := obj.(Request); ok { + return true + } + + return false +} diff --git a/internal/pkg/core/cqrs/tx_request.go b/internal/pkg/core/cqrs/tx_request.go new file mode 100644 index 00000000..2b4cb08b --- /dev/null +++ b/internal/pkg/core/cqrs/tx_request.go @@ -0,0 +1,9 @@ +package cqrs + +// https://www.mohitkhare.com/blog/go-naming-conventions/ +// https://github.com/EventStore/EventStore-Client-Go/blob/master/esdb/position.go +type TxRequest interface { + Request + + isTxRequest() +} diff --git a/internal/pkg/core/cqrs/type_info.go b/internal/pkg/core/cqrs/type_info.go new file mode 100644 index 00000000..f0289428 --- /dev/null +++ b/internal/pkg/core/cqrs/type_info.go @@ -0,0 +1,39 @@ +package cqrs + +import ( + "reflect" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" +) + +type TypeInfo interface { + ShortTypeName() string + FullTypeName() string + Type() reflect.Type +} + +type typeInfo struct { + shortTypeName string + fullTypeName string + typ reflect.Type +} + +func NewTypeInfoT[T any]() TypeInfo { + name := typemapper.GetGenericTypeNameByT[T]() + fullName := typemapper.GetGenericFullTypeNameByT[T]() + typ := typemapper.GetGenericTypeByT[T]() + + return &typeInfo{fullTypeName: fullName, typ: typ, shortTypeName: name} +} + +func (t *typeInfo) ShortTypeName() string { + return t.shortTypeName +} + +func (t *typeInfo) FullTypeName() string { + return t.fullTypeName +} + +func (t *typeInfo) Type() reflect.Type { + return t.typ +} diff --git a/internal/pkg/core/custom_types/custom_time.go b/internal/pkg/core/customtypes/custom_time.go similarity index 91% rename from internal/pkg/core/custom_types/custom_time.go rename to internal/pkg/core/customtypes/custom_time.go index 57791650..256df5ae 100644 --- a/internal/pkg/core/custom_types/custom_time.go +++ b/internal/pkg/core/customtypes/custom_time.go @@ -8,7 +8,7 @@ import ( "strings" "time" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" "github.com/araddon/dateparse" ) diff --git a/internal/pkg/core/data/generic_repository.go b/internal/pkg/core/data/generic_repository.go index 4abfddfd..dfe4c267 100644 --- a/internal/pkg/core/data/generic_repository.go +++ b/internal/pkg/core/data/generic_repository.go @@ -3,8 +3,8 @@ package data import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data/specification" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/data/specification" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" uuid "github.com/satori/go.uuid" ) diff --git a/internal/pkg/core/domain/domain_event.go b/internal/pkg/core/domain/domain_event.go index d9fcb435..bf8e5823 100644 --- a/internal/pkg/core/domain/domain_event.go +++ b/internal/pkg/core/domain/domain_event.go @@ -1,8 +1,8 @@ package domain import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/events" - expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/events" + expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" uuid "github.com/satori/go.uuid" ) diff --git a/internal/pkg/core/domain/event_envelope.go b/internal/pkg/core/domain/event_envelope.go index 24cd74bf..59da7c80 100644 --- a/internal/pkg/core/domain/event_envelope.go +++ b/internal/pkg/core/domain/event_envelope.go @@ -1,7 +1,7 @@ package domain import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" ) type EventEnvelope struct { diff --git a/internal/pkg/core/events/event.go b/internal/pkg/core/events/event.go index e97e19d9..1a1e5985 100644 --- a/internal/pkg/core/events/event.go +++ b/internal/pkg/core/events/event.go @@ -3,13 +3,17 @@ package events import ( "time" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + uuid "github.com/satori/go.uuid" ) type IEvent interface { GetEventId() uuid.UUID - GetEventType() string GetOccurredOn() time.Time + // GetEventTypeName get short type name of the event - we use event short type name instead of full type name because this event in other receiver packages could have different package name + GetEventTypeName() string + GetEventFullTypeName() string } type Event struct { @@ -37,3 +41,19 @@ func (e *Event) GetEventType() string { func (e *Event) GetOccurredOn() time.Time { return e.OccurredOn } + +func (e *Event) GetEventTypeName() string { + return typeMapper.GetTypeName(e) +} + +func (e *Event) GetEventFullTypeName() string { + return typeMapper.GetFullTypeName(e) +} + +func IsEvent(obj interface{}) bool { + if _, ok := obj.(IEvent); ok { + return true + } + + return false +} diff --git a/internal/pkg/core/messaging/bus/bus.go b/internal/pkg/core/messaging/bus/bus.go new file mode 100644 index 00000000..933a1956 --- /dev/null +++ b/internal/pkg/core/messaging/bus/bus.go @@ -0,0 +1,12 @@ +package bus + +import ( + consumer2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/producer" +) + +type Bus interface { + producer.Producer + consumer2.BusControl + consumer2.ConsumerConnector +} diff --git a/internal/pkg/messaging/consumer/consumer.go b/internal/pkg/core/messaging/consumer/consumer.go similarity index 70% rename from internal/pkg/messaging/consumer/consumer.go rename to internal/pkg/core/messaging/consumer/consumer.go index 22ca37d1..1285c00a 100644 --- a/internal/pkg/messaging/consumer/consumer.go +++ b/internal/pkg/core/messaging/consumer/consumer.go @@ -3,7 +3,7 @@ package consumer import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) type Consumer interface { diff --git a/internal/pkg/messaging/consumer/consumer_builder.go b/internal/pkg/core/messaging/consumer/consumer_builder.go similarity index 100% rename from internal/pkg/messaging/consumer/consumer_builder.go rename to internal/pkg/core/messaging/consumer/consumer_builder.go diff --git a/internal/pkg/messaging/consumer/consumer_connector.go b/internal/pkg/core/messaging/consumer/consumer_connector.go similarity index 83% rename from internal/pkg/messaging/consumer/consumer_connector.go rename to internal/pkg/core/messaging/consumer/consumer_connector.go index 289d6d31..e4ebadad 100644 --- a/internal/pkg/messaging/consumer/consumer_connector.go +++ b/internal/pkg/core/messaging/consumer/consumer_connector.go @@ -1,7 +1,7 @@ package consumer import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) type ConsumerConnector interface { diff --git a/internal/pkg/messaging/consumer/consumer_handler.go b/internal/pkg/core/messaging/consumer/consumer_handler.go similarity index 63% rename from internal/pkg/messaging/consumer/consumer_handler.go rename to internal/pkg/core/messaging/consumer/consumer_handler.go index 677c527e..4c3eb1d2 100644 --- a/internal/pkg/messaging/consumer/consumer_handler.go +++ b/internal/pkg/core/messaging/consumer/consumer_handler.go @@ -3,7 +3,7 @@ package consumer import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) type ConsumerHandler interface { diff --git a/internal/pkg/messaging/consumer/consumer_handler_configuration.go b/internal/pkg/core/messaging/consumer/consumer_handler_configuration.go similarity index 100% rename from internal/pkg/messaging/consumer/consumer_handler_configuration.go rename to internal/pkg/core/messaging/consumer/consumer_handler_configuration.go diff --git a/internal/pkg/messaging/consumer/consumer_handler_configuration_builder.go b/internal/pkg/core/messaging/consumer/consumer_handler_configuration_builder.go similarity index 100% rename from internal/pkg/messaging/consumer/consumer_handler_configuration_builder.go rename to internal/pkg/core/messaging/consumer/consumer_handler_configuration_builder.go diff --git a/internal/pkg/messaging/consumer/consumer_options.go b/internal/pkg/core/messaging/consumer/consumer_options.go similarity index 100% rename from internal/pkg/messaging/consumer/consumer_options.go rename to internal/pkg/core/messaging/consumer/consumer_options.go diff --git a/internal/pkg/messaging/consumer/consumers_control.go b/internal/pkg/core/messaging/consumer/consumers_control.go similarity index 71% rename from internal/pkg/messaging/consumer/consumers_control.go rename to internal/pkg/core/messaging/consumer/consumers_control.go index 2dff9c77..a3f052f0 100644 --- a/internal/pkg/messaging/consumer/consumers_control.go +++ b/internal/pkg/core/messaging/consumer/consumers_control.go @@ -3,7 +3,7 @@ package consumer import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) type BusControl interface { diff --git a/internal/pkg/messaging/message_header/message_header.go b/internal/pkg/core/messaging/messageheader/message_header.go similarity index 100% rename from internal/pkg/messaging/message_header/message_header.go rename to internal/pkg/core/messaging/messageheader/message_header.go diff --git a/internal/pkg/messaging/message_header/metadata_message_extentions.go b/internal/pkg/core/messaging/messageheader/metadata_message_extentions.go similarity index 92% rename from internal/pkg/messaging/message_header/metadata_message_extentions.go rename to internal/pkg/core/messaging/messageheader/metadata_message_extentions.go index 80226289..91f1297b 100644 --- a/internal/pkg/messaging/message_header/metadata_message_extentions.go +++ b/internal/pkg/core/messaging/messageheader/metadata_message_extentions.go @@ -3,7 +3,7 @@ package messageHeader import ( "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" ) func GetCorrelationId(m metadata.Metadata) string { diff --git a/internal/pkg/messaging/mocks/Bus.go b/internal/pkg/core/messaging/mocks/Bus.go similarity index 81% rename from internal/pkg/messaging/mocks/Bus.go rename to internal/pkg/core/messaging/mocks/Bus.go index 3e1bd32e..4f51db7c 100644 --- a/internal/pkg/messaging/mocks/Bus.go +++ b/internal/pkg/core/messaging/mocks/Bus.go @@ -1,17 +1,17 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - consumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" + consumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + metadata "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" mock "github.com/stretchr/testify/mock" - types "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) // Bus is an autogenerated mock type for the Bus type @@ -27,76 +27,14 @@ func (_m *Bus) EXPECT() *Bus_Expecter { return &Bus_Expecter{mock: &_m.Mock} } -// AddMessageConsumedHandler provides a mock function with given fields: _a0 -func (_m *Bus) IsConsumed(_a0 func(types.IMessage)) { - _m.Called(_a0) -} - -// Bus_AddMessageConsumedHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsConsumed' -type Bus_AddMessageConsumedHandler_Call struct { - *mock.Call -} - -// AddMessageConsumedHandler is a helper method to define mock.On call -// - _a0 func(types.IMessage) -func (_e *Bus_Expecter) AddMessageConsumedHandler(_a0 interface{}) *Bus_AddMessageConsumedHandler_Call { - return &Bus_AddMessageConsumedHandler_Call{Call: _e.mock.On("IsConsumed", _a0)} -} - -func (_c *Bus_AddMessageConsumedHandler_Call) Run(run func(_a0 func(types.IMessage))) *Bus_AddMessageConsumedHandler_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(func(types.IMessage))) - }) - return _c -} - -func (_c *Bus_AddMessageConsumedHandler_Call) Return() *Bus_AddMessageConsumedHandler_Call { - _c.Call.Return() - return _c -} - -func (_c *Bus_AddMessageConsumedHandler_Call) RunAndReturn(run func(func(types.IMessage))) *Bus_AddMessageConsumedHandler_Call { - _c.Call.Return(run) - return _c -} - -// AddMessageProducedHandler provides a mock function with given fields: _a0 -func (_m *Bus) IsProduced(_a0 func(types.IMessage)) { - _m.Called(_a0) -} - -// Bus_AddMessageProducedHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsProduced' -type Bus_AddMessageProducedHandler_Call struct { - *mock.Call -} - -// AddMessageProducedHandler is a helper method to define mock.On call -// - _a0 func(types.IMessage) -func (_e *Bus_Expecter) AddMessageProducedHandler(_a0 interface{}) *Bus_AddMessageProducedHandler_Call { - return &Bus_AddMessageProducedHandler_Call{Call: _e.mock.On("IsProduced", _a0)} -} - -func (_c *Bus_AddMessageProducedHandler_Call) Run(run func(_a0 func(types.IMessage))) *Bus_AddMessageProducedHandler_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(func(types.IMessage))) - }) - return _c -} - -func (_c *Bus_AddMessageProducedHandler_Call) Return() *Bus_AddMessageProducedHandler_Call { - _c.Call.Return() - return _c -} - -func (_c *Bus_AddMessageProducedHandler_Call) RunAndReturn(run func(func(types.IMessage))) *Bus_AddMessageProducedHandler_Call { - _c.Call.Return(run) - return _c -} - // ConnectConsumer provides a mock function with given fields: messageType, _a1 func (_m *Bus) ConnectConsumer(messageType types.IMessage, _a1 consumer.Consumer) error { ret := _m.Called(messageType, _a1) + if len(ret) == 0 { + panic("no return value specified for ConnectConsumer") + } + var r0 error if rf, ok := ret.Get(0).(func(types.IMessage, consumer.Consumer) error); ok { r0 = rf(messageType, _a1) @@ -140,6 +78,10 @@ func (_c *Bus_ConnectConsumer_Call) RunAndReturn(run func(types.IMessage, consum func (_m *Bus) ConnectConsumerHandler(messageType types.IMessage, consumerHandler consumer.ConsumerHandler) error { ret := _m.Called(messageType, consumerHandler) + if len(ret) == 0 { + panic("no return value specified for ConnectConsumerHandler") + } + var r0 error if rf, ok := ret.Get(0).(func(types.IMessage, consumer.ConsumerHandler) error); ok { r0 = rf(messageType, consumerHandler) @@ -179,10 +121,80 @@ func (_c *Bus_ConnectConsumerHandler_Call) RunAndReturn(run func(types.IMessage, return _c } +// IsConsumed provides a mock function with given fields: _a0 +func (_m *Bus) IsConsumed(_a0 func(types.IMessage)) { + _m.Called(_a0) +} + +// Bus_IsConsumed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsConsumed' +type Bus_IsConsumed_Call struct { + *mock.Call +} + +// IsConsumed is a helper method to define mock.On call +// - _a0 func(types.IMessage) +func (_e *Bus_Expecter) IsConsumed(_a0 interface{}) *Bus_IsConsumed_Call { + return &Bus_IsConsumed_Call{Call: _e.mock.On("IsConsumed", _a0)} +} + +func (_c *Bus_IsConsumed_Call) Run(run func(_a0 func(types.IMessage))) *Bus_IsConsumed_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(func(types.IMessage))) + }) + return _c +} + +func (_c *Bus_IsConsumed_Call) Return() *Bus_IsConsumed_Call { + _c.Call.Return() + return _c +} + +func (_c *Bus_IsConsumed_Call) RunAndReturn(run func(func(types.IMessage))) *Bus_IsConsumed_Call { + _c.Call.Return(run) + return _c +} + +// IsProduced provides a mock function with given fields: _a0 +func (_m *Bus) IsProduced(_a0 func(types.IMessage)) { + _m.Called(_a0) +} + +// Bus_IsProduced_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsProduced' +type Bus_IsProduced_Call struct { + *mock.Call +} + +// IsProduced is a helper method to define mock.On call +// - _a0 func(types.IMessage) +func (_e *Bus_Expecter) IsProduced(_a0 interface{}) *Bus_IsProduced_Call { + return &Bus_IsProduced_Call{Call: _e.mock.On("IsProduced", _a0)} +} + +func (_c *Bus_IsProduced_Call) Run(run func(_a0 func(types.IMessage))) *Bus_IsProduced_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(func(types.IMessage))) + }) + return _c +} + +func (_c *Bus_IsProduced_Call) Return() *Bus_IsProduced_Call { + _c.Call.Return() + return _c +} + +func (_c *Bus_IsProduced_Call) RunAndReturn(run func(func(types.IMessage))) *Bus_IsProduced_Call { + _c.Call.Return(run) + return _c +} + // PublishMessage provides a mock function with given fields: ctx, message, meta func (_m *Bus) PublishMessage(ctx context.Context, message types.IMessage, meta metadata.Metadata) error { ret := _m.Called(ctx, message, meta) + if len(ret) == 0 { + panic("no return value specified for PublishMessage") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, types.IMessage, metadata.Metadata) error); ok { r0 = rf(ctx, message, meta) @@ -227,6 +239,10 @@ func (_c *Bus_PublishMessage_Call) RunAndReturn(run func(context.Context, types. func (_m *Bus) PublishMessageWithTopicName(ctx context.Context, message types.IMessage, meta metadata.Metadata, topicOrExchangeName string) error { ret := _m.Called(ctx, message, meta, topicOrExchangeName) + if len(ret) == 0 { + panic("no return value specified for PublishMessageWithTopicName") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, types.IMessage, metadata.Metadata, string) error); ok { r0 = rf(ctx, message, meta, topicOrExchangeName) @@ -272,6 +288,10 @@ func (_c *Bus_PublishMessageWithTopicName_Call) RunAndReturn(run func(context.Co func (_m *Bus) Start(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -314,6 +334,10 @@ func (_c *Bus_Start_Call) RunAndReturn(run func(context.Context) error) *Bus_Sta func (_m *Bus) Stop() error { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() diff --git a/internal/pkg/messaging/mocks/BusControl.go b/internal/pkg/core/messaging/mocks/BusControl.go similarity index 74% rename from internal/pkg/messaging/mocks/BusControl.go rename to internal/pkg/core/messaging/mocks/BusControl.go index cbe10475..142b6ef9 100644 --- a/internal/pkg/messaging/mocks/BusControl.go +++ b/internal/pkg/core/messaging/mocks/BusControl.go @@ -1,11 +1,11 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - types "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" mock "github.com/stretchr/testify/mock" ) @@ -22,35 +22,35 @@ func (_m *BusControl) EXPECT() *BusControl_Expecter { return &BusControl_Expecter{mock: &_m.Mock} } -// AddMessageConsumedHandler provides a mock function with given fields: _a0 +// IsConsumed provides a mock function with given fields: _a0 func (_m *BusControl) IsConsumed(_a0 func(types.IMessage)) { _m.Called(_a0) } -// BusControl_AddMessageConsumedHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsConsumed' -type BusControl_AddMessageConsumedHandler_Call struct { +// BusControl_IsConsumed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsConsumed' +type BusControl_IsConsumed_Call struct { *mock.Call } -// AddMessageConsumedHandler is a helper method to define mock.On call +// IsConsumed is a helper method to define mock.On call // - _a0 func(types.IMessage) -func (_e *BusControl_Expecter) AddMessageConsumedHandler(_a0 interface{}) *BusControl_AddMessageConsumedHandler_Call { - return &BusControl_AddMessageConsumedHandler_Call{Call: _e.mock.On("IsConsumed", _a0)} +func (_e *BusControl_Expecter) IsConsumed(_a0 interface{}) *BusControl_IsConsumed_Call { + return &BusControl_IsConsumed_Call{Call: _e.mock.On("IsConsumed", _a0)} } -func (_c *BusControl_AddMessageConsumedHandler_Call) Run(run func(_a0 func(types.IMessage))) *BusControl_AddMessageConsumedHandler_Call { +func (_c *BusControl_IsConsumed_Call) Run(run func(_a0 func(types.IMessage))) *BusControl_IsConsumed_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(func(types.IMessage))) }) return _c } -func (_c *BusControl_AddMessageConsumedHandler_Call) Return() *BusControl_AddMessageConsumedHandler_Call { +func (_c *BusControl_IsConsumed_Call) Return() *BusControl_IsConsumed_Call { _c.Call.Return() return _c } -func (_c *BusControl_AddMessageConsumedHandler_Call) RunAndReturn(run func(func(types.IMessage))) *BusControl_AddMessageConsumedHandler_Call { +func (_c *BusControl_IsConsumed_Call) RunAndReturn(run func(func(types.IMessage))) *BusControl_IsConsumed_Call { _c.Call.Return(run) return _c } @@ -59,6 +59,10 @@ func (_c *BusControl_AddMessageConsumedHandler_Call) RunAndReturn(run func(func( func (_m *BusControl) Start(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -101,6 +105,10 @@ func (_c *BusControl_Start_Call) RunAndReturn(run func(context.Context) error) * func (_m *BusControl) Stop() error { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() diff --git a/internal/pkg/messaging/mocks/Consumer.go b/internal/pkg/core/messaging/mocks/Consumer.go similarity index 65% rename from internal/pkg/messaging/mocks/Consumer.go rename to internal/pkg/core/messaging/mocks/Consumer.go index 2f4c8243..195a579f 100644 --- a/internal/pkg/messaging/mocks/Consumer.go +++ b/internal/pkg/core/messaging/mocks/Consumer.go @@ -1,15 +1,15 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - consumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" + consumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" mock "github.com/stretchr/testify/mock" - types "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) // Consumer is an autogenerated mock type for the Consumer type @@ -25,68 +25,113 @@ func (_m *Consumer) EXPECT() *Consumer_Expecter { return &Consumer_Expecter{mock: &_m.Mock} } -// AddMessageConsumedHandler provides a mock function with given fields: _a0 -func (_m *Consumer) IsConsumed(_a0 func(types.IMessage)) { - _m.Called(_a0) +// ConnectHandler provides a mock function with given fields: handler +func (_m *Consumer) ConnectHandler(handler consumer.ConsumerHandler) { + _m.Called(handler) } -// Consumer_AddMessageConsumedHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsConsumed' -type Consumer_AddMessageConsumedHandler_Call struct { +// Consumer_ConnectHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ConnectHandler' +type Consumer_ConnectHandler_Call struct { *mock.Call } -// AddMessageConsumedHandler is a helper method to define mock.On call -// - _a0 func(types.IMessage) -func (_e *Consumer_Expecter) AddMessageConsumedHandler(_a0 interface{}) *Consumer_AddMessageConsumedHandler_Call { - return &Consumer_AddMessageConsumedHandler_Call{Call: _e.mock.On("IsConsumed", _a0)} +// ConnectHandler is a helper method to define mock.On call +// - handler consumer.ConsumerHandler +func (_e *Consumer_Expecter) ConnectHandler(handler interface{}) *Consumer_ConnectHandler_Call { + return &Consumer_ConnectHandler_Call{Call: _e.mock.On("ConnectHandler", handler)} } -func (_c *Consumer_AddMessageConsumedHandler_Call) Run(run func(_a0 func(types.IMessage))) *Consumer_AddMessageConsumedHandler_Call { +func (_c *Consumer_ConnectHandler_Call) Run(run func(handler consumer.ConsumerHandler)) *Consumer_ConnectHandler_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(func(types.IMessage))) + run(args[0].(consumer.ConsumerHandler)) }) return _c } -func (_c *Consumer_AddMessageConsumedHandler_Call) Return() *Consumer_AddMessageConsumedHandler_Call { +func (_c *Consumer_ConnectHandler_Call) Return() *Consumer_ConnectHandler_Call { _c.Call.Return() return _c } -func (_c *Consumer_AddMessageConsumedHandler_Call) RunAndReturn(run func(func(types.IMessage))) *Consumer_AddMessageConsumedHandler_Call { +func (_c *Consumer_ConnectHandler_Call) RunAndReturn(run func(consumer.ConsumerHandler)) *Consumer_ConnectHandler_Call { _c.Call.Return(run) return _c } -// ConnectHandler provides a mock function with given fields: handler -func (_m *Consumer) ConnectHandler(handler consumer.ConsumerHandler) { - _m.Called(handler) +// GetName provides a mock function with given fields: +func (_m *Consumer) GetName() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetName") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 } -// Consumer_ConnectHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ConnectHandler' -type Consumer_ConnectHandler_Call struct { +// Consumer_GetName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetName' +type Consumer_GetName_Call struct { *mock.Call } -// ConnectHandler is a helper method to define mock.On call -// - handler consumer.ConsumerHandler -func (_e *Consumer_Expecter) ConnectHandler(handler interface{}) *Consumer_ConnectHandler_Call { - return &Consumer_ConnectHandler_Call{Call: _e.mock.On("ConnectHandler", handler)} +// GetName is a helper method to define mock.On call +func (_e *Consumer_Expecter) GetName() *Consumer_GetName_Call { + return &Consumer_GetName_Call{Call: _e.mock.On("GetName")} } -func (_c *Consumer_ConnectHandler_Call) Run(run func(handler consumer.ConsumerHandler)) *Consumer_ConnectHandler_Call { +func (_c *Consumer_GetName_Call) Run(run func()) *Consumer_GetName_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(consumer.ConsumerHandler)) + run() }) return _c } -func (_c *Consumer_ConnectHandler_Call) Return() *Consumer_ConnectHandler_Call { +func (_c *Consumer_GetName_Call) Return(_a0 string) *Consumer_GetName_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Consumer_GetName_Call) RunAndReturn(run func() string) *Consumer_GetName_Call { + _c.Call.Return(run) + return _c +} + +// IsConsumed provides a mock function with given fields: _a0 +func (_m *Consumer) IsConsumed(_a0 func(types.IMessage)) { + _m.Called(_a0) +} + +// Consumer_IsConsumed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsConsumed' +type Consumer_IsConsumed_Call struct { + *mock.Call +} + +// IsConsumed is a helper method to define mock.On call +// - _a0 func(types.IMessage) +func (_e *Consumer_Expecter) IsConsumed(_a0 interface{}) *Consumer_IsConsumed_Call { + return &Consumer_IsConsumed_Call{Call: _e.mock.On("IsConsumed", _a0)} +} + +func (_c *Consumer_IsConsumed_Call) Run(run func(_a0 func(types.IMessage))) *Consumer_IsConsumed_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(func(types.IMessage))) + }) + return _c +} + +func (_c *Consumer_IsConsumed_Call) Return() *Consumer_IsConsumed_Call { _c.Call.Return() return _c } -func (_c *Consumer_ConnectHandler_Call) RunAndReturn(run func(consumer.ConsumerHandler)) *Consumer_ConnectHandler_Call { +func (_c *Consumer_IsConsumed_Call) RunAndReturn(run func(func(types.IMessage))) *Consumer_IsConsumed_Call { _c.Call.Return(run) return _c } @@ -95,6 +140,10 @@ func (_c *Consumer_ConnectHandler_Call) RunAndReturn(run func(consumer.ConsumerH func (_m *Consumer) Start(ctx context.Context) error { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for Start") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context) error); ok { r0 = rf(ctx) @@ -137,6 +186,10 @@ func (_c *Consumer_Start_Call) RunAndReturn(run func(context.Context) error) *Co func (_m *Consumer) Stop() error { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Stop") + } + var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() diff --git a/internal/pkg/messaging/mocks/ConsumerBuilderFucT.go b/internal/pkg/core/messaging/mocks/ConsumerBuilderFucT.go similarity index 97% rename from internal/pkg/messaging/mocks/ConsumerBuilderFucT.go rename to internal/pkg/core/messaging/mocks/ConsumerBuilderFucT.go index 9e57153f..69ec530f 100644 --- a/internal/pkg/messaging/mocks/ConsumerBuilderFucT.go +++ b/internal/pkg/core/messaging/mocks/ConsumerBuilderFucT.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks diff --git a/internal/pkg/messaging/mocks/ConsumerConnector.go b/internal/pkg/core/messaging/mocks/ConsumerConnector.go similarity index 90% rename from internal/pkg/messaging/mocks/ConsumerConnector.go rename to internal/pkg/core/messaging/mocks/ConsumerConnector.go index edd3e55d..91d15115 100644 --- a/internal/pkg/messaging/mocks/ConsumerConnector.go +++ b/internal/pkg/core/messaging/mocks/ConsumerConnector.go @@ -1,12 +1,12 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - consumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" + consumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" mock "github.com/stretchr/testify/mock" - types "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) // ConsumerConnector is an autogenerated mock type for the ConsumerConnector type @@ -26,6 +26,10 @@ func (_m *ConsumerConnector) EXPECT() *ConsumerConnector_Expecter { func (_m *ConsumerConnector) ConnectConsumer(messageType types.IMessage, _a1 consumer.Consumer) error { ret := _m.Called(messageType, _a1) + if len(ret) == 0 { + panic("no return value specified for ConnectConsumer") + } + var r0 error if rf, ok := ret.Get(0).(func(types.IMessage, consumer.Consumer) error); ok { r0 = rf(messageType, _a1) @@ -69,6 +73,10 @@ func (_c *ConsumerConnector_ConnectConsumer_Call) RunAndReturn(run func(types.IM func (_m *ConsumerConnector) ConnectConsumerHandler(messageType types.IMessage, consumerHandler consumer.ConsumerHandler) error { ret := _m.Called(messageType, consumerHandler) + if len(ret) == 0 { + panic("no return value specified for ConnectConsumerHandler") + } + var r0 error if rf, ok := ret.Get(0).(func(types.IMessage, consumer.ConsumerHandler) error); ok { r0 = rf(messageType, consumerHandler) diff --git a/internal/pkg/messaging/mocks/ConsumerHandler.go b/internal/pkg/core/messaging/mocks/ConsumerHandler.go similarity index 91% rename from internal/pkg/messaging/mocks/ConsumerHandler.go rename to internal/pkg/core/messaging/mocks/ConsumerHandler.go index 279c5696..d67b9d3e 100644 --- a/internal/pkg/messaging/mocks/ConsumerHandler.go +++ b/internal/pkg/core/messaging/mocks/ConsumerHandler.go @@ -1,11 +1,11 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - types "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" mock "github.com/stretchr/testify/mock" ) @@ -26,6 +26,10 @@ func (_m *ConsumerHandler) EXPECT() *ConsumerHandler_Expecter { func (_m *ConsumerHandler) Handle(ctx context.Context, consumeContext types.MessageConsumeContext) error { ret := _m.Called(ctx, consumeContext) + if len(ret) == 0 { + panic("no return value specified for Handle") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, types.MessageConsumeContext) error); ok { r0 = rf(ctx, consumeContext) diff --git a/internal/pkg/messaging/mocks/ConsumerHandlerConfigurationBuilder.go b/internal/pkg/core/messaging/mocks/ConsumerHandlerConfigurationBuilder.go similarity index 93% rename from internal/pkg/messaging/mocks/ConsumerHandlerConfigurationBuilder.go rename to internal/pkg/core/messaging/mocks/ConsumerHandlerConfigurationBuilder.go index 3cf46da4..15b43546 100644 --- a/internal/pkg/messaging/mocks/ConsumerHandlerConfigurationBuilder.go +++ b/internal/pkg/core/messaging/mocks/ConsumerHandlerConfigurationBuilder.go @@ -1,9 +1,9 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - consumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" + consumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" mock "github.com/stretchr/testify/mock" ) @@ -24,6 +24,10 @@ func (_m *ConsumerHandlerConfigurationBuilder) EXPECT() *ConsumerHandlerConfigur func (_m *ConsumerHandlerConfigurationBuilder) AddHandler(handler consumer.ConsumerHandler) consumer.ConsumerHandlerConfigurationBuilder { ret := _m.Called(handler) + if len(ret) == 0 { + panic("no return value specified for AddHandler") + } + var r0 consumer.ConsumerHandlerConfigurationBuilder if rf, ok := ret.Get(0).(func(consumer.ConsumerHandler) consumer.ConsumerHandlerConfigurationBuilder); ok { r0 = rf(handler) @@ -68,6 +72,10 @@ func (_c *ConsumerHandlerConfigurationBuilder_AddHandler_Call) RunAndReturn(run func (_m *ConsumerHandlerConfigurationBuilder) Build() *consumer.ConsumerHandlersConfiguration { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Build") + } + var r0 *consumer.ConsumerHandlersConfiguration if rf, ok := ret.Get(0).(func() *consumer.ConsumerHandlersConfiguration); ok { r0 = rf() diff --git a/internal/pkg/messaging/mocks/ConsumerHandlerConfigurationBuilderFunc.go b/internal/pkg/core/messaging/mocks/ConsumerHandlerConfigurationBuilderFunc.go similarity index 94% rename from internal/pkg/messaging/mocks/ConsumerHandlerConfigurationBuilderFunc.go rename to internal/pkg/core/messaging/mocks/ConsumerHandlerConfigurationBuilderFunc.go index 9addc220..1ae50d71 100644 --- a/internal/pkg/messaging/mocks/ConsumerHandlerConfigurationBuilderFunc.go +++ b/internal/pkg/core/messaging/mocks/ConsumerHandlerConfigurationBuilderFunc.go @@ -1,9 +1,9 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - consumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" + consumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" mock "github.com/stretchr/testify/mock" ) diff --git a/internal/pkg/messaging/mocks/ConsumerHandlerFunc.go b/internal/pkg/core/messaging/mocks/ConsumerHandlerFunc.go similarity index 65% rename from internal/pkg/messaging/mocks/ConsumerHandlerFunc.go rename to internal/pkg/core/messaging/mocks/ConsumerHandlerFunc.go index 0429b9f7..3a0e3b4e 100644 --- a/internal/pkg/messaging/mocks/ConsumerHandlerFunc.go +++ b/internal/pkg/core/messaging/mocks/ConsumerHandlerFunc.go @@ -1,8 +1,12 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks -import mock "github.com/stretchr/testify/mock" +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) // ConsumerHandlerFunc is an autogenerated mock type for the ConsumerHandlerFunc type type ConsumerHandlerFunc struct { @@ -17,13 +21,17 @@ func (_m *ConsumerHandlerFunc) EXPECT() *ConsumerHandlerFunc_Expecter { return &ConsumerHandlerFunc_Expecter{mock: &_m.Mock} } -// Execute provides a mock function with given fields: -func (_m *ConsumerHandlerFunc) Execute() error { - ret := _m.Called() +// Execute provides a mock function with given fields: ctx +func (_m *ConsumerHandlerFunc) Execute(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Execute") + } var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) } else { r0 = ret.Error(0) } @@ -37,13 +45,14 @@ type ConsumerHandlerFunc_Execute_Call struct { } // Execute is a helper method to define mock.On call -func (_e *ConsumerHandlerFunc_Expecter) Execute() *ConsumerHandlerFunc_Execute_Call { - return &ConsumerHandlerFunc_Execute_Call{Call: _e.mock.On("Execute")} +// - ctx context.Context +func (_e *ConsumerHandlerFunc_Expecter) Execute(ctx interface{}) *ConsumerHandlerFunc_Execute_Call { + return &ConsumerHandlerFunc_Execute_Call{Call: _e.mock.On("Execute", ctx)} } -func (_c *ConsumerHandlerFunc_Execute_Call) Run(run func()) *ConsumerHandlerFunc_Execute_Call { +func (_c *ConsumerHandlerFunc_Execute_Call) Run(run func(ctx context.Context)) *ConsumerHandlerFunc_Execute_Call { _c.Call.Run(func(args mock.Arguments) { - run() + run(args[0].(context.Context)) }) return _c } @@ -53,7 +62,7 @@ func (_c *ConsumerHandlerFunc_Execute_Call) Return(_a0 error) *ConsumerHandlerFu return _c } -func (_c *ConsumerHandlerFunc_Execute_Call) RunAndReturn(run func() error) *ConsumerHandlerFunc_Execute_Call { +func (_c *ConsumerHandlerFunc_Execute_Call) RunAndReturn(run func(context.Context) error) *ConsumerHandlerFunc_Execute_Call { _c.Call.Return(run) return _c } diff --git a/internal/pkg/messaging/mocks/ConsumerPipeline.go b/internal/pkg/core/messaging/mocks/ConsumerPipeline.go similarity index 88% rename from internal/pkg/messaging/mocks/ConsumerPipeline.go rename to internal/pkg/core/messaging/mocks/ConsumerPipeline.go index 9484d083..c073d34c 100644 --- a/internal/pkg/messaging/mocks/ConsumerPipeline.go +++ b/internal/pkg/core/messaging/mocks/ConsumerPipeline.go @@ -1,12 +1,12 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - pipeline "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/pipeline" - types "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + pipeline "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/pipeline" + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" mock "github.com/stretchr/testify/mock" ) @@ -27,6 +27,10 @@ func (_m *ConsumerPipeline) EXPECT() *ConsumerPipeline_Expecter { func (_m *ConsumerPipeline) Handle(ctx context.Context, consumerContext types.MessageConsumeContext, next pipeline.ConsumerHandlerFunc) error { ret := _m.Called(ctx, consumerContext, next) + if len(ret) == 0 { + panic("no return value specified for Handle") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, types.MessageConsumeContext, pipeline.ConsumerHandlerFunc) error); ok { r0 = rf(ctx, consumerContext, next) diff --git a/internal/pkg/messaging/mocks/ConsumerPipelineConfigurationBuilder.go b/internal/pkg/core/messaging/mocks/ConsumerPipelineConfigurationBuilder.go similarity index 93% rename from internal/pkg/messaging/mocks/ConsumerPipelineConfigurationBuilder.go rename to internal/pkg/core/messaging/mocks/ConsumerPipelineConfigurationBuilder.go index f9134409..6c846ae9 100644 --- a/internal/pkg/messaging/mocks/ConsumerPipelineConfigurationBuilder.go +++ b/internal/pkg/core/messaging/mocks/ConsumerPipelineConfigurationBuilder.go @@ -1,9 +1,9 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - pipeline "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/pipeline" + pipeline "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/pipeline" mock "github.com/stretchr/testify/mock" ) @@ -24,6 +24,10 @@ func (_m *ConsumerPipelineConfigurationBuilder) EXPECT() *ConsumerPipelineConfig func (_m *ConsumerPipelineConfigurationBuilder) AddPipeline(_a0 pipeline.ConsumerPipeline) pipeline.ConsumerPipelineConfigurationBuilder { ret := _m.Called(_a0) + if len(ret) == 0 { + panic("no return value specified for AddPipeline") + } + var r0 pipeline.ConsumerPipelineConfigurationBuilder if rf, ok := ret.Get(0).(func(pipeline.ConsumerPipeline) pipeline.ConsumerPipelineConfigurationBuilder); ok { r0 = rf(_a0) @@ -68,6 +72,10 @@ func (_c *ConsumerPipelineConfigurationBuilder_AddPipeline_Call) RunAndReturn(ru func (_m *ConsumerPipelineConfigurationBuilder) Build() *pipeline.ConsumerPipelineConfiguration { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Build") + } + var r0 *pipeline.ConsumerPipelineConfiguration if rf, ok := ret.Get(0).(func() *pipeline.ConsumerPipelineConfiguration); ok { r0 = rf() diff --git a/internal/pkg/messaging/mocks/ConsumerPipelineConfigurationBuilderFunc.go b/internal/pkg/core/messaging/mocks/ConsumerPipelineConfigurationBuilderFunc.go similarity index 94% rename from internal/pkg/messaging/mocks/ConsumerPipelineConfigurationBuilderFunc.go rename to internal/pkg/core/messaging/mocks/ConsumerPipelineConfigurationBuilderFunc.go index cc2c887b..31ec0b87 100644 --- a/internal/pkg/messaging/mocks/ConsumerPipelineConfigurationBuilderFunc.go +++ b/internal/pkg/core/messaging/mocks/ConsumerPipelineConfigurationBuilderFunc.go @@ -1,9 +1,9 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - pipeline "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/pipeline" + pipeline "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/pipeline" mock "github.com/stretchr/testify/mock" ) diff --git a/internal/pkg/messaging/mocks/IMessage.go b/internal/pkg/core/messaging/mocks/IMessage.go similarity index 55% rename from internal/pkg/messaging/mocks/IMessage.go rename to internal/pkg/core/messaging/mocks/IMessage.go index 95db2f29..9d06f31a 100644 --- a/internal/pkg/messaging/mocks/IMessage.go +++ b/internal/pkg/core/messaging/mocks/IMessage.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks @@ -25,6 +25,10 @@ func (_m *IMessage) EXPECT() *IMessage_Expecter { func (_m *IMessage) GeMessageId() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GeMessageId") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -66,6 +70,10 @@ func (_c *IMessage_GeMessageId_Call) RunAndReturn(run func() string) *IMessage_G func (_m *IMessage) GetCreated() time.Time { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetCreated") + } + var r0 time.Time if rf, ok := ret.Get(0).(func() time.Time); ok { r0 = rf() @@ -103,10 +111,14 @@ func (_c *IMessage_GetCreated_Call) RunAndReturn(run func() time.Time) *IMessage return _c } -// GetEventTypeName provides a mock function with given fields: -func (_m *IMessage) GetEventTypeName() string { +// GetMessageFullTypeName provides a mock function with given fields: +func (_m *IMessage) GetMessageFullTypeName() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetMessageFullTypeName") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -117,103 +129,74 @@ func (_m *IMessage) GetEventTypeName() string { return r0 } -// IMessage_GetEventTypeName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEventTypeName' -type IMessage_GetEventTypeName_Call struct { +// IMessage_GetMessageFullTypeName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMessageFullTypeName' +type IMessage_GetMessageFullTypeName_Call struct { *mock.Call } -// GetEventTypeName is a helper method to define mock.On call -func (_e *IMessage_Expecter) GetEventTypeName() *IMessage_GetEventTypeName_Call { - return &IMessage_GetEventTypeName_Call{Call: _e.mock.On("GetEventTypeName")} +// GetMessageFullTypeName is a helper method to define mock.On call +func (_e *IMessage_Expecter) GetMessageFullTypeName() *IMessage_GetMessageFullTypeName_Call { + return &IMessage_GetMessageFullTypeName_Call{Call: _e.mock.On("GetMessageFullTypeName")} } -func (_c *IMessage_GetEventTypeName_Call) Run(run func()) *IMessage_GetEventTypeName_Call { +func (_c *IMessage_GetMessageFullTypeName_Call) Run(run func()) *IMessage_GetMessageFullTypeName_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *IMessage_GetEventTypeName_Call) Return(_a0 string) *IMessage_GetEventTypeName_Call { +func (_c *IMessage_GetMessageFullTypeName_Call) Return(_a0 string) *IMessage_GetMessageFullTypeName_Call { _c.Call.Return(_a0) return _c } -func (_c *IMessage_GetEventTypeName_Call) RunAndReturn(run func() string) *IMessage_GetEventTypeName_Call { +func (_c *IMessage_GetMessageFullTypeName_Call) RunAndReturn(run func() string) *IMessage_GetMessageFullTypeName_Call { _c.Call.Return(run) return _c } -// IsMessage provides a mock function with given fields: -func (_m *IMessage) IsMessage() bool { +// GetMessageTypeName provides a mock function with given fields: +func (_m *IMessage) GetMessageTypeName() string { ret := _m.Called() - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { + if len(ret) == 0 { + panic("no return value specified for GetMessageTypeName") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() } else { - r0 = ret.Get(0).(bool) + r0 = ret.Get(0).(string) } return r0 } -// IMessage_IsMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsMessage' -type IMessage_IsMessage_Call struct { +// IMessage_GetMessageTypeName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMessageTypeName' +type IMessage_GetMessageTypeName_Call struct { *mock.Call } -// IsMessage is a helper method to define mock.On call -func (_e *IMessage_Expecter) IsMessage() *IMessage_IsMessage_Call { - return &IMessage_IsMessage_Call{Call: _e.mock.On("IsMessage")} +// GetMessageTypeName is a helper method to define mock.On call +func (_e *IMessage_Expecter) GetMessageTypeName() *IMessage_GetMessageTypeName_Call { + return &IMessage_GetMessageTypeName_Call{Call: _e.mock.On("GetMessageTypeName")} } -func (_c *IMessage_IsMessage_Call) Run(run func()) *IMessage_IsMessage_Call { +func (_c *IMessage_GetMessageTypeName_Call) Run(run func()) *IMessage_GetMessageTypeName_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *IMessage_IsMessage_Call) Return(_a0 bool) *IMessage_IsMessage_Call { +func (_c *IMessage_GetMessageTypeName_Call) Return(_a0 string) *IMessage_GetMessageTypeName_Call { _c.Call.Return(_a0) return _c } -func (_c *IMessage_IsMessage_Call) RunAndReturn(run func() bool) *IMessage_IsMessage_Call { - _c.Call.Return(run) - return _c -} - -// SetEventTypeName provides a mock function with given fields: _a0 -func (_m *IMessage) SetEventTypeName(_a0 string) { - _m.Called(_a0) -} - -// IMessage_SetEventTypeName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetEventTypeName' -type IMessage_SetEventTypeName_Call struct { - *mock.Call -} - -// SetEventTypeName is a helper method to define mock.On call -// - _a0 string -func (_e *IMessage_Expecter) SetEventTypeName(_a0 interface{}) *IMessage_SetEventTypeName_Call { - return &IMessage_SetEventTypeName_Call{Call: _e.mock.On("SetEventTypeName", _a0)} -} - -func (_c *IMessage_SetEventTypeName_Call) Run(run func(_a0 string)) *IMessage_SetEventTypeName_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c -} - -func (_c *IMessage_SetEventTypeName_Call) Return() *IMessage_SetEventTypeName_Call { - _c.Call.Return() - return _c -} - -func (_c *IMessage_SetEventTypeName_Call) RunAndReturn(run func(string)) *IMessage_SetEventTypeName_Call { +func (_c *IMessage_GetMessageTypeName_Call) RunAndReturn(run func() string) *IMessage_GetMessageTypeName_Call { _c.Call.Return(run) return _c } diff --git a/internal/pkg/messaging/mocks/MessageConsumeContext.go b/internal/pkg/core/messaging/mocks/MessageConsumeContext.go similarity index 92% rename from internal/pkg/messaging/mocks/MessageConsumeContext.go rename to internal/pkg/core/messaging/mocks/MessageConsumeContext.go index dce06413..fefe2a19 100644 --- a/internal/pkg/messaging/mocks/MessageConsumeContext.go +++ b/internal/pkg/core/messaging/mocks/MessageConsumeContext.go @@ -1,14 +1,14 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + metadata "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" mock "github.com/stretchr/testify/mock" time "time" - types "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) // MessageConsumeContext is an autogenerated mock type for the MessageConsumeContext type @@ -28,6 +28,10 @@ func (_m *MessageConsumeContext) EXPECT() *MessageConsumeContext_Expecter { func (_m *MessageConsumeContext) ContentType() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for ContentType") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -69,6 +73,10 @@ func (_c *MessageConsumeContext_ContentType_Call) RunAndReturn(run func() string func (_m *MessageConsumeContext) CorrelationId() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for CorrelationId") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -110,6 +118,10 @@ func (_c *MessageConsumeContext_CorrelationId_Call) RunAndReturn(run func() stri func (_m *MessageConsumeContext) Created() time.Time { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Created") + } + var r0 time.Time if rf, ok := ret.Get(0).(func() time.Time); ok { r0 = rf() @@ -151,6 +163,10 @@ func (_c *MessageConsumeContext_Created_Call) RunAndReturn(run func() time.Time) func (_m *MessageConsumeContext) DeliveryTag() uint64 { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for DeliveryTag") + } + var r0 uint64 if rf, ok := ret.Get(0).(func() uint64); ok { r0 = rf() @@ -192,6 +208,10 @@ func (_c *MessageConsumeContext_DeliveryTag_Call) RunAndReturn(run func() uint64 func (_m *MessageConsumeContext) Message() types.IMessage { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Message") + } + var r0 types.IMessage if rf, ok := ret.Get(0).(func() types.IMessage); ok { r0 = rf() @@ -235,6 +255,10 @@ func (_c *MessageConsumeContext_Message_Call) RunAndReturn(run func() types.IMes func (_m *MessageConsumeContext) MessageId() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for MessageId") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -276,6 +300,10 @@ func (_c *MessageConsumeContext_MessageId_Call) RunAndReturn(run func() string) func (_m *MessageConsumeContext) MessageType() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for MessageType") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -317,6 +345,10 @@ func (_c *MessageConsumeContext_MessageType_Call) RunAndReturn(run func() string func (_m *MessageConsumeContext) Metadata() metadata.Metadata { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Metadata") + } + var r0 metadata.Metadata if rf, ok := ret.Get(0).(func() metadata.Metadata); ok { r0 = rf() diff --git a/internal/pkg/core/messaging/mocks/MessagePersistenceService.go b/internal/pkg/core/messaging/mocks/MessagePersistenceService.go new file mode 100644 index 00000000..deec222e --- /dev/null +++ b/internal/pkg/core/messaging/mocks/MessagePersistenceService.go @@ -0,0 +1,648 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + persistmessage "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/persistmessage" + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + mock "github.com/stretchr/testify/mock" + + uuid "github.com/satori/go.uuid" +) + +// MessagePersistenceService is an autogenerated mock type for the MessagePersistenceService type +type MessagePersistenceService struct { + mock.Mock +} + +type MessagePersistenceService_Expecter struct { + mock *mock.Mock +} + +func (_m *MessagePersistenceService) EXPECT() *MessagePersistenceService_Expecter { + return &MessagePersistenceService_Expecter{mock: &_m.Mock} +} + +// Add provides a mock function with given fields: ctx, storeMessage +func (_m *MessagePersistenceService) Add(ctx context.Context, storeMessage *persistmessage.StoreMessage) error { + ret := _m.Called(ctx, storeMessage) + + if len(ret) == 0 { + panic("no return value specified for Add") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *persistmessage.StoreMessage) error); ok { + r0 = rf(ctx, storeMessage) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MessagePersistenceService_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add' +type MessagePersistenceService_Add_Call struct { + *mock.Call +} + +// Add is a helper method to define mock.On call +// - ctx context.Context +// - storeMessage *persistmessage.StoreMessage +func (_e *MessagePersistenceService_Expecter) Add(ctx interface{}, storeMessage interface{}) *MessagePersistenceService_Add_Call { + return &MessagePersistenceService_Add_Call{Call: _e.mock.On("Add", ctx, storeMessage)} +} + +func (_c *MessagePersistenceService_Add_Call) Run(run func(ctx context.Context, storeMessage *persistmessage.StoreMessage)) *MessagePersistenceService_Add_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*persistmessage.StoreMessage)) + }) + return _c +} + +func (_c *MessagePersistenceService_Add_Call) Return(_a0 error) *MessagePersistenceService_Add_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessagePersistenceService_Add_Call) RunAndReturn(run func(context.Context, *persistmessage.StoreMessage) error) *MessagePersistenceService_Add_Call { + _c.Call.Return(run) + return _c +} + +// AddPublishMessage provides a mock function with given fields: messageEnvelope, ctx +func (_m *MessagePersistenceService) AddPublishMessage(messageEnvelope types.MessageEnvelope, ctx context.Context) error { + ret := _m.Called(messageEnvelope, ctx) + + if len(ret) == 0 { + panic("no return value specified for AddPublishMessage") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.MessageEnvelope, context.Context) error); ok { + r0 = rf(messageEnvelope, ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MessagePersistenceService_AddPublishMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddPublishMessage' +type MessagePersistenceService_AddPublishMessage_Call struct { + *mock.Call +} + +// AddPublishMessage is a helper method to define mock.On call +// - messageEnvelope types.MessageEnvelope +// - ctx context.Context +func (_e *MessagePersistenceService_Expecter) AddPublishMessage(messageEnvelope interface{}, ctx interface{}) *MessagePersistenceService_AddPublishMessage_Call { + return &MessagePersistenceService_AddPublishMessage_Call{Call: _e.mock.On("AddPublishMessage", messageEnvelope, ctx)} +} + +func (_c *MessagePersistenceService_AddPublishMessage_Call) Run(run func(messageEnvelope types.MessageEnvelope, ctx context.Context)) *MessagePersistenceService_AddPublishMessage_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(types.MessageEnvelope), args[1].(context.Context)) + }) + return _c +} + +func (_c *MessagePersistenceService_AddPublishMessage_Call) Return(_a0 error) *MessagePersistenceService_AddPublishMessage_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessagePersistenceService_AddPublishMessage_Call) RunAndReturn(run func(types.MessageEnvelope, context.Context) error) *MessagePersistenceService_AddPublishMessage_Call { + _c.Call.Return(run) + return _c +} + +// AddReceivedMessage provides a mock function with given fields: messageEnvelope, ctx +func (_m *MessagePersistenceService) AddReceivedMessage(messageEnvelope types.MessageEnvelope, ctx context.Context) error { + ret := _m.Called(messageEnvelope, ctx) + + if len(ret) == 0 { + panic("no return value specified for AddReceivedMessage") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.MessageEnvelope, context.Context) error); ok { + r0 = rf(messageEnvelope, ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MessagePersistenceService_AddReceivedMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReceivedMessage' +type MessagePersistenceService_AddReceivedMessage_Call struct { + *mock.Call +} + +// AddReceivedMessage is a helper method to define mock.On call +// - messageEnvelope types.MessageEnvelope +// - ctx context.Context +func (_e *MessagePersistenceService_Expecter) AddReceivedMessage(messageEnvelope interface{}, ctx interface{}) *MessagePersistenceService_AddReceivedMessage_Call { + return &MessagePersistenceService_AddReceivedMessage_Call{Call: _e.mock.On("AddReceivedMessage", messageEnvelope, ctx)} +} + +func (_c *MessagePersistenceService_AddReceivedMessage_Call) Run(run func(messageEnvelope types.MessageEnvelope, ctx context.Context)) *MessagePersistenceService_AddReceivedMessage_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(types.MessageEnvelope), args[1].(context.Context)) + }) + return _c +} + +func (_c *MessagePersistenceService_AddReceivedMessage_Call) Return(_a0 error) *MessagePersistenceService_AddReceivedMessage_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessagePersistenceService_AddReceivedMessage_Call) RunAndReturn(run func(types.MessageEnvelope, context.Context) error) *MessagePersistenceService_AddReceivedMessage_Call { + _c.Call.Return(run) + return _c +} + +// ChangeState provides a mock function with given fields: ctx, messageID, status +func (_m *MessagePersistenceService) ChangeState(ctx context.Context, messageID uuid.UUID, status persistmessage.MessageStatus) error { + ret := _m.Called(ctx, messageID, status) + + if len(ret) == 0 { + panic("no return value specified for ChangeState") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID, persistmessage.MessageStatus) error); ok { + r0 = rf(ctx, messageID, status) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MessagePersistenceService_ChangeState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChangeState' +type MessagePersistenceService_ChangeState_Call struct { + *mock.Call +} + +// ChangeState is a helper method to define mock.On call +// - ctx context.Context +// - messageID uuid.UUID +// - status persistmessage.MessageStatus +func (_e *MessagePersistenceService_Expecter) ChangeState(ctx interface{}, messageID interface{}, status interface{}) *MessagePersistenceService_ChangeState_Call { + return &MessagePersistenceService_ChangeState_Call{Call: _e.mock.On("ChangeState", ctx, messageID, status)} +} + +func (_c *MessagePersistenceService_ChangeState_Call) Run(run func(ctx context.Context, messageID uuid.UUID, status persistmessage.MessageStatus)) *MessagePersistenceService_ChangeState_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID), args[2].(persistmessage.MessageStatus)) + }) + return _c +} + +func (_c *MessagePersistenceService_ChangeState_Call) Return(_a0 error) *MessagePersistenceService_ChangeState_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessagePersistenceService_ChangeState_Call) RunAndReturn(run func(context.Context, uuid.UUID, persistmessage.MessageStatus) error) *MessagePersistenceService_ChangeState_Call { + _c.Call.Return(run) + return _c +} + +// CleanupMessages provides a mock function with given fields: ctx +func (_m *MessagePersistenceService) CleanupMessages(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for CleanupMessages") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MessagePersistenceService_CleanupMessages_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CleanupMessages' +type MessagePersistenceService_CleanupMessages_Call struct { + *mock.Call +} + +// CleanupMessages is a helper method to define mock.On call +// - ctx context.Context +func (_e *MessagePersistenceService_Expecter) CleanupMessages(ctx interface{}) *MessagePersistenceService_CleanupMessages_Call { + return &MessagePersistenceService_CleanupMessages_Call{Call: _e.mock.On("CleanupMessages", ctx)} +} + +func (_c *MessagePersistenceService_CleanupMessages_Call) Run(run func(ctx context.Context)) *MessagePersistenceService_CleanupMessages_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MessagePersistenceService_CleanupMessages_Call) Return(_a0 error) *MessagePersistenceService_CleanupMessages_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessagePersistenceService_CleanupMessages_Call) RunAndReturn(run func(context.Context) error) *MessagePersistenceService_CleanupMessages_Call { + _c.Call.Return(run) + return _c +} + +// GetAllActive provides a mock function with given fields: ctx +func (_m *MessagePersistenceService) GetAllActive(ctx context.Context) ([]*persistmessage.StoreMessage, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetAllActive") + } + + var r0 []*persistmessage.StoreMessage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]*persistmessage.StoreMessage, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []*persistmessage.StoreMessage); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*persistmessage.StoreMessage) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessagePersistenceService_GetAllActive_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllActive' +type MessagePersistenceService_GetAllActive_Call struct { + *mock.Call +} + +// GetAllActive is a helper method to define mock.On call +// - ctx context.Context +func (_e *MessagePersistenceService_Expecter) GetAllActive(ctx interface{}) *MessagePersistenceService_GetAllActive_Call { + return &MessagePersistenceService_GetAllActive_Call{Call: _e.mock.On("GetAllActive", ctx)} +} + +func (_c *MessagePersistenceService_GetAllActive_Call) Run(run func(ctx context.Context)) *MessagePersistenceService_GetAllActive_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MessagePersistenceService_GetAllActive_Call) Return(_a0 []*persistmessage.StoreMessage, _a1 error) *MessagePersistenceService_GetAllActive_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessagePersistenceService_GetAllActive_Call) RunAndReturn(run func(context.Context) ([]*persistmessage.StoreMessage, error)) *MessagePersistenceService_GetAllActive_Call { + _c.Call.Return(run) + return _c +} + +// GetByFilter provides a mock function with given fields: ctx, predicate +func (_m *MessagePersistenceService) GetByFilter(ctx context.Context, predicate func(*persistmessage.StoreMessage) bool) ([]*persistmessage.StoreMessage, error) { + ret := _m.Called(ctx, predicate) + + if len(ret) == 0 { + panic("no return value specified for GetByFilter") + } + + var r0 []*persistmessage.StoreMessage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, func(*persistmessage.StoreMessage) bool) ([]*persistmessage.StoreMessage, error)); ok { + return rf(ctx, predicate) + } + if rf, ok := ret.Get(0).(func(context.Context, func(*persistmessage.StoreMessage) bool) []*persistmessage.StoreMessage); ok { + r0 = rf(ctx, predicate) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*persistmessage.StoreMessage) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, func(*persistmessage.StoreMessage) bool) error); ok { + r1 = rf(ctx, predicate) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessagePersistenceService_GetByFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByFilter' +type MessagePersistenceService_GetByFilter_Call struct { + *mock.Call +} + +// GetByFilter is a helper method to define mock.On call +// - ctx context.Context +// - predicate func(*persistmessage.StoreMessage) bool +func (_e *MessagePersistenceService_Expecter) GetByFilter(ctx interface{}, predicate interface{}) *MessagePersistenceService_GetByFilter_Call { + return &MessagePersistenceService_GetByFilter_Call{Call: _e.mock.On("GetByFilter", ctx, predicate)} +} + +func (_c *MessagePersistenceService_GetByFilter_Call) Run(run func(ctx context.Context, predicate func(*persistmessage.StoreMessage) bool)) *MessagePersistenceService_GetByFilter_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(func(*persistmessage.StoreMessage) bool)) + }) + return _c +} + +func (_c *MessagePersistenceService_GetByFilter_Call) Return(_a0 []*persistmessage.StoreMessage, _a1 error) *MessagePersistenceService_GetByFilter_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessagePersistenceService_GetByFilter_Call) RunAndReturn(run func(context.Context, func(*persistmessage.StoreMessage) bool) ([]*persistmessage.StoreMessage, error)) *MessagePersistenceService_GetByFilter_Call { + _c.Call.Return(run) + return _c +} + +// GetById provides a mock function with given fields: ctx, id +func (_m *MessagePersistenceService) GetById(ctx context.Context, id uuid.UUID) (*persistmessage.StoreMessage, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetById") + } + + var r0 *persistmessage.StoreMessage + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*persistmessage.StoreMessage, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) *persistmessage.StoreMessage); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*persistmessage.StoreMessage) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessagePersistenceService_GetById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetById' +type MessagePersistenceService_GetById_Call struct { + *mock.Call +} + +// GetById is a helper method to define mock.On call +// - ctx context.Context +// - id uuid.UUID +func (_e *MessagePersistenceService_Expecter) GetById(ctx interface{}, id interface{}) *MessagePersistenceService_GetById_Call { + return &MessagePersistenceService_GetById_Call{Call: _e.mock.On("GetById", ctx, id)} +} + +func (_c *MessagePersistenceService_GetById_Call) Run(run func(ctx context.Context, id uuid.UUID)) *MessagePersistenceService_GetById_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *MessagePersistenceService_GetById_Call) Return(_a0 *persistmessage.StoreMessage, _a1 error) *MessagePersistenceService_GetById_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessagePersistenceService_GetById_Call) RunAndReturn(run func(context.Context, uuid.UUID) (*persistmessage.StoreMessage, error)) *MessagePersistenceService_GetById_Call { + _c.Call.Return(run) + return _c +} + +// Process provides a mock function with given fields: messageID, ctx +func (_m *MessagePersistenceService) Process(messageID string, ctx context.Context) error { + ret := _m.Called(messageID, ctx) + + if len(ret) == 0 { + panic("no return value specified for Process") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, context.Context) error); ok { + r0 = rf(messageID, ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MessagePersistenceService_Process_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Process' +type MessagePersistenceService_Process_Call struct { + *mock.Call +} + +// Process is a helper method to define mock.On call +// - messageID string +// - ctx context.Context +func (_e *MessagePersistenceService_Expecter) Process(messageID interface{}, ctx interface{}) *MessagePersistenceService_Process_Call { + return &MessagePersistenceService_Process_Call{Call: _e.mock.On("Process", messageID, ctx)} +} + +func (_c *MessagePersistenceService_Process_Call) Run(run func(messageID string, ctx context.Context)) *MessagePersistenceService_Process_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(context.Context)) + }) + return _c +} + +func (_c *MessagePersistenceService_Process_Call) Return(_a0 error) *MessagePersistenceService_Process_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessagePersistenceService_Process_Call) RunAndReturn(run func(string, context.Context) error) *MessagePersistenceService_Process_Call { + _c.Call.Return(run) + return _c +} + +// ProcessAll provides a mock function with given fields: ctx +func (_m *MessagePersistenceService) ProcessAll(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ProcessAll") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MessagePersistenceService_ProcessAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ProcessAll' +type MessagePersistenceService_ProcessAll_Call struct { + *mock.Call +} + +// ProcessAll is a helper method to define mock.On call +// - ctx context.Context +func (_e *MessagePersistenceService_Expecter) ProcessAll(ctx interface{}) *MessagePersistenceService_ProcessAll_Call { + return &MessagePersistenceService_ProcessAll_Call{Call: _e.mock.On("ProcessAll", ctx)} +} + +func (_c *MessagePersistenceService_ProcessAll_Call) Run(run func(ctx context.Context)) *MessagePersistenceService_ProcessAll_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MessagePersistenceService_ProcessAll_Call) Return(_a0 error) *MessagePersistenceService_ProcessAll_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessagePersistenceService_ProcessAll_Call) RunAndReturn(run func(context.Context) error) *MessagePersistenceService_ProcessAll_Call { + _c.Call.Return(run) + return _c +} + +// Remove provides a mock function with given fields: ctx, storeMessage +func (_m *MessagePersistenceService) Remove(ctx context.Context, storeMessage *persistmessage.StoreMessage) (bool, error) { + ret := _m.Called(ctx, storeMessage) + + if len(ret) == 0 { + panic("no return value specified for Remove") + } + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *persistmessage.StoreMessage) (bool, error)); ok { + return rf(ctx, storeMessage) + } + if rf, ok := ret.Get(0).(func(context.Context, *persistmessage.StoreMessage) bool); ok { + r0 = rf(ctx, storeMessage) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, *persistmessage.StoreMessage) error); ok { + r1 = rf(ctx, storeMessage) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessagePersistenceService_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove' +type MessagePersistenceService_Remove_Call struct { + *mock.Call +} + +// Remove is a helper method to define mock.On call +// - ctx context.Context +// - storeMessage *persistmessage.StoreMessage +func (_e *MessagePersistenceService_Expecter) Remove(ctx interface{}, storeMessage interface{}) *MessagePersistenceService_Remove_Call { + return &MessagePersistenceService_Remove_Call{Call: _e.mock.On("Remove", ctx, storeMessage)} +} + +func (_c *MessagePersistenceService_Remove_Call) Run(run func(ctx context.Context, storeMessage *persistmessage.StoreMessage)) *MessagePersistenceService_Remove_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*persistmessage.StoreMessage)) + }) + return _c +} + +func (_c *MessagePersistenceService_Remove_Call) Return(_a0 bool, _a1 error) *MessagePersistenceService_Remove_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessagePersistenceService_Remove_Call) RunAndReturn(run func(context.Context, *persistmessage.StoreMessage) (bool, error)) *MessagePersistenceService_Remove_Call { + _c.Call.Return(run) + return _c +} + +// Update provides a mock function with given fields: ctx, storeMessage +func (_m *MessagePersistenceService) Update(ctx context.Context, storeMessage *persistmessage.StoreMessage) error { + ret := _m.Called(ctx, storeMessage) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *persistmessage.StoreMessage) error); ok { + r0 = rf(ctx, storeMessage) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MessagePersistenceService_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' +type MessagePersistenceService_Update_Call struct { + *mock.Call +} + +// Update is a helper method to define mock.On call +// - ctx context.Context +// - storeMessage *persistmessage.StoreMessage +func (_e *MessagePersistenceService_Expecter) Update(ctx interface{}, storeMessage interface{}) *MessagePersistenceService_Update_Call { + return &MessagePersistenceService_Update_Call{Call: _e.mock.On("Update", ctx, storeMessage)} +} + +func (_c *MessagePersistenceService_Update_Call) Run(run func(ctx context.Context, storeMessage *persistmessage.StoreMessage)) *MessagePersistenceService_Update_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*persistmessage.StoreMessage)) + }) + return _c +} + +func (_c *MessagePersistenceService_Update_Call) Return(_a0 error) *MessagePersistenceService_Update_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessagePersistenceService_Update_Call) RunAndReturn(run func(context.Context, *persistmessage.StoreMessage) error) *MessagePersistenceService_Update_Call { + _c.Call.Return(run) + return _c +} + +// NewMessagePersistenceService creates a new instance of MessagePersistenceService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMessagePersistenceService(t interface { + mock.TestingT + Cleanup(func()) +}) *MessagePersistenceService { + mock := &MessagePersistenceService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/pkg/messaging/mocks/Producer.go b/internal/pkg/core/messaging/mocks/Producer.go similarity index 80% rename from internal/pkg/messaging/mocks/Producer.go rename to internal/pkg/core/messaging/mocks/Producer.go index ea8b9614..f5016e06 100644 --- a/internal/pkg/messaging/mocks/Producer.go +++ b/internal/pkg/core/messaging/mocks/Producer.go @@ -1,14 +1,14 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + metadata "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" mock "github.com/stretchr/testify/mock" - types "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) // Producer is an autogenerated mock type for the Producer type @@ -24,35 +24,35 @@ func (_m *Producer) EXPECT() *Producer_Expecter { return &Producer_Expecter{mock: &_m.Mock} } -// AddMessageProducedHandler provides a mock function with given fields: _a0 +// IsProduced provides a mock function with given fields: _a0 func (_m *Producer) IsProduced(_a0 func(types.IMessage)) { _m.Called(_a0) } -// Producer_AddMessageProducedHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsProduced' -type Producer_AddMessageProducedHandler_Call struct { +// Producer_IsProduced_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsProduced' +type Producer_IsProduced_Call struct { *mock.Call } -// AddMessageProducedHandler is a helper method to define mock.On call +// IsProduced is a helper method to define mock.On call // - _a0 func(types.IMessage) -func (_e *Producer_Expecter) AddMessageProducedHandler(_a0 interface{}) *Producer_AddMessageProducedHandler_Call { - return &Producer_AddMessageProducedHandler_Call{Call: _e.mock.On("IsProduced", _a0)} +func (_e *Producer_Expecter) IsProduced(_a0 interface{}) *Producer_IsProduced_Call { + return &Producer_IsProduced_Call{Call: _e.mock.On("IsProduced", _a0)} } -func (_c *Producer_AddMessageProducedHandler_Call) Run(run func(_a0 func(types.IMessage))) *Producer_AddMessageProducedHandler_Call { +func (_c *Producer_IsProduced_Call) Run(run func(_a0 func(types.IMessage))) *Producer_IsProduced_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(func(types.IMessage))) }) return _c } -func (_c *Producer_AddMessageProducedHandler_Call) Return() *Producer_AddMessageProducedHandler_Call { +func (_c *Producer_IsProduced_Call) Return() *Producer_IsProduced_Call { _c.Call.Return() return _c } -func (_c *Producer_AddMessageProducedHandler_Call) RunAndReturn(run func(func(types.IMessage))) *Producer_AddMessageProducedHandler_Call { +func (_c *Producer_IsProduced_Call) RunAndReturn(run func(func(types.IMessage))) *Producer_IsProduced_Call { _c.Call.Return(run) return _c } @@ -61,6 +61,10 @@ func (_c *Producer_AddMessageProducedHandler_Call) RunAndReturn(run func(func(ty func (_m *Producer) PublishMessage(ctx context.Context, message types.IMessage, meta metadata.Metadata) error { ret := _m.Called(ctx, message, meta) + if len(ret) == 0 { + panic("no return value specified for PublishMessage") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, types.IMessage, metadata.Metadata) error); ok { r0 = rf(ctx, message, meta) @@ -105,6 +109,10 @@ func (_c *Producer_PublishMessage_Call) RunAndReturn(run func(context.Context, t func (_m *Producer) PublishMessageWithTopicName(ctx context.Context, message types.IMessage, meta metadata.Metadata, topicOrExchangeName string) error { ret := _m.Called(ctx, message, meta, topicOrExchangeName) + if len(ret) == 0 { + panic("no return value specified for PublishMessageWithTopicName") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, types.IMessage, metadata.Metadata, string) error); ok { r0 = rf(ctx, message, meta, topicOrExchangeName) diff --git a/internal/pkg/messaging/mocks/ProducerBuilderFuc.go b/internal/pkg/core/messaging/mocks/ProducerBuilderFuc.go similarity index 97% rename from internal/pkg/messaging/mocks/ProducerBuilderFuc.go rename to internal/pkg/core/messaging/mocks/ProducerBuilderFuc.go index 229cf899..b7a9517f 100644 --- a/internal/pkg/messaging/mocks/ProducerBuilderFuc.go +++ b/internal/pkg/core/messaging/mocks/ProducerBuilderFuc.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks diff --git a/internal/pkg/messaging/otel/tracing/consts.go b/internal/pkg/core/messaging/otel/tracing/consts.go similarity index 100% rename from internal/pkg/messaging/otel/tracing/consts.go rename to internal/pkg/core/messaging/otel/tracing/consts.go diff --git a/internal/pkg/messaging/otel/tracing/consumer/consumer.go b/internal/pkg/core/messaging/otel/tracing/consumer/consumer.go similarity index 71% rename from internal/pkg/messaging/otel/tracing/consumer/consumer.go rename to internal/pkg/core/messaging/otel/tracing/consumer/consumer.go index 250faf88..8541f0ab 100644 --- a/internal/pkg/messaging/otel/tracing/consumer/consumer.go +++ b/internal/pkg/core/messaging/otel/tracing/consumer/consumer.go @@ -5,11 +5,12 @@ import ( "fmt" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - messageHeader "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/message_header" - messageTracing "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/otel/tracing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing" - tracingHeaders "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing/tracing_headers" + messageHeader "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/messageheader" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/constants" + tracingHeaders "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/tracing_headers" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -35,14 +36,14 @@ func StartConsumerSpan( // If there's a span context in the message, use that as the parent context. // extracts the tracing from the header and puts it into the context - carrier := messageTracing.NewMessageCarrier(meta) + carrier := tracing.NewMessageCarrier(meta) parentSpanContext := otel.GetTextMapPropagator().Extract(ctx, carrier) opts := getTraceOptions(meta, payload, consumerTracingOptions) // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/messaging.md#span-name - // SpanName = Destination Name + Operation Name - ctx, span := messageTracing.MessagingTracer.Start( + // SpanName = Destination ShortTypeName + Operation ShortTypeName + ctx, span := tracing.MessagingTracer.Start( parentSpanContext, fmt.Sprintf("%s %s", consumerTracingOptions.Destination, "receive"), opts...) @@ -57,15 +58,15 @@ func StartConsumerSpan( } func FinishConsumerSpan(span trace.Span, err error) error { - messageName := tracing.GetSpanAttribute(span, messageTracing.MessageName).Value.AsString() + messageName := utils.GetSpanAttribute(span, tracing.MessageName).Value.AsString() if err != nil { span.AddEvent(fmt.Sprintf("failed to consume message '%s' from the broker", messageName)) - _ = messageTracing.TraceMessagingErrFromSpan(span, err) + _ = utils.TraceErrStatusFromSpan(span, err) } span.SetAttributes( - attribute.Key(tracing.SpanId).String(span.SpanContext().SpanID().String()), // current span id + attribute.Key(constants.SpanId).String(span.SpanContext().SpanID().String()), // current span id ) span.AddEvent(fmt.Sprintf("message '%s' consumed from the broker succesfully", messageName)) @@ -87,14 +88,14 @@ func getTraceOptions( semconv.MessageIDKey.String(messageHeader.GetMessageId(*meta)), semconv.MessagingMessageConversationID(correlationId), semconv.MessagingOperationReceive, - attribute.Key(tracing.TraceId).String(tracingHeaders.GetTracingTraceId(*meta)), - attribute.Key(tracing.Traceparent).String(tracingHeaders.GetTracingTraceparent(*meta)), - attribute.Key(tracing.ParentSpanId).String(tracingHeaders.GetTracingParentSpanId(*meta)), - attribute.Key(tracing.Timestamp).Int64(time.Now().UnixMilli()), - attribute.Key(messageTracing.MessageType).String(messageHeader.GetMessageType(*meta)), - attribute.Key(messageTracing.MessageName).String(messageHeader.GetMessageName(*meta)), - attribute.Key(messageTracing.Payload).String(payload), - attribute.String(messageTracing.Headers, meta.ToJson()), + attribute.Key(constants.TraceId).String(tracingHeaders.GetTracingTraceId(*meta)), + attribute.Key(constants.Traceparent).String(tracingHeaders.GetTracingTraceparent(*meta)), + attribute.Key(constants.ParentSpanId).String(tracingHeaders.GetTracingParentSpanId(*meta)), + attribute.Key(constants.Timestamp).Int64(time.Now().UnixMilli()), + attribute.Key(tracing.MessageType).String(messageHeader.GetMessageType(*meta)), + attribute.Key(tracing.MessageName).String(messageHeader.GetMessageName(*meta)), + attribute.Key(tracing.Payload).String(payload), + attribute.String(tracing.Headers, meta.ToJson()), semconv.MessagingDestinationName(consumerTracingOptions.Destination), semconv.MessagingSystemKey.String(consumerTracingOptions.MessagingSystem), } diff --git a/internal/pkg/messaging/otel/tracing/consumer/consumer_tracing_options.go b/internal/pkg/core/messaging/otel/tracing/consumer/consumer_tracing_options.go similarity index 100% rename from internal/pkg/messaging/otel/tracing/consumer/consumer_tracing_options.go rename to internal/pkg/core/messaging/otel/tracing/consumer/consumer_tracing_options.go diff --git a/internal/pkg/messaging/otel/tracing/message_carrier.go b/internal/pkg/core/messaging/otel/tracing/message_carrier.go similarity index 81% rename from internal/pkg/messaging/otel/tracing/message_carrier.go rename to internal/pkg/core/messaging/otel/tracing/message_carrier.go index 3283937f..d93d52e8 100644 --- a/internal/pkg/messaging/otel/tracing/message_carrier.go +++ b/internal/pkg/core/messaging/otel/tracing/message_carrier.go @@ -1,6 +1,6 @@ package tracing -import "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" type MessageCarrier struct { meta *metadata.Metadata diff --git a/internal/pkg/messaging/otel/tracing/producer/producer.go b/internal/pkg/core/messaging/otel/tracing/producer/producer.go similarity index 60% rename from internal/pkg/messaging/otel/tracing/producer/producer.go rename to internal/pkg/core/messaging/otel/tracing/producer/producer.go index 381a7cba..c3a0c24d 100644 --- a/internal/pkg/messaging/otel/tracing/producer/producer.go +++ b/internal/pkg/core/messaging/otel/tracing/producer/producer.go @@ -5,11 +5,12 @@ import ( "fmt" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - messageHeader "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/message_header" - messageTracing "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/otel/tracing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing" + messageHeader "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/messageheader" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/constants" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -36,19 +37,24 @@ func StartProducerSpan( // If there's a span context in the message, use that as the parent context. // extracts the tracing from the header and puts it into the context - carrier := messageTracing.NewMessageCarrier(meta) + carrier := tracing.NewMessageCarrier(meta) parentSpanContext := otel.GetTextMapPropagator().Extract(ctx, carrier) opts := getTraceOptions(meta, message, payload, producerTracingOptions) // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/messaging.md#span-name - // SpanName = Destination Name + Operation Name - ctx, span := messageTracing.MessagingTracer.Start( + // SpanName = Destination ShortTypeName + Operation ShortTypeName + ctx, span := tracing.MessagingTracer.Start( parentSpanContext, fmt.Sprintf("%s %s", producerTracingOptions.Destination, "send"), opts...) - span.AddEvent(fmt.Sprintf("start publishing message '%s' to the broker", messageHeader.GetMessageName(*meta))) + span.AddEvent( + fmt.Sprintf( + "start publishing message '%s' to the broker", + messageHeader.GetMessageName(*meta), + ), + ) // Injects current span context, so consumers can use it to propagate span. // injects the tracing from the context into the header map @@ -59,18 +65,33 @@ func StartProducerSpan( } func FinishProducerSpan(span trace.Span, err error) error { - messageName := tracing.GetSpanAttribute(span, messageTracing.MessageName).Value.AsString() + messageName := utils.GetSpanAttribute( + span, + tracing.MessageName, + ).Value.AsString() if err != nil { - span.AddEvent(fmt.Sprintf("failed to publsih message '%s' to the broker", messageName)) - _ = messageTracing.TraceMessagingErrFromSpan(span, err) + span.AddEvent( + fmt.Sprintf( + "failed to publsih message '%s' to the broker", + messageName, + ), + ) + _ = utils.TraceErrStatusFromSpan(span, err) } span.SetAttributes( - attribute.Key(tracing.TraceId).String(span.SpanContext().TraceID().String()), - attribute.Key(tracing.SpanId).String(span.SpanContext().SpanID().String()), // current span id + attribute.Key(constants.TraceId). + String(span.SpanContext().TraceID().String()), + attribute.Key(constants.SpanId). + String(span.SpanContext().SpanID().String()), // current span id ) - span.AddEvent(fmt.Sprintf("message '%s' published to the broker succesfully", messageName)) + span.AddEvent( + fmt.Sprintf( + "message '%s' published to the broker succesfully", + messageName, + ), + ) span.End() return err @@ -89,17 +110,22 @@ func getTraceOptions( attrs := []attribute.KeyValue{ semconv.MessageIDKey.String(message.GeMessageId()), semconv.MessagingMessageConversationID(correlationId), - attribute.Key(messageTracing.MessageType).String(message.GetEventTypeName()), - attribute.Key(messageTracing.MessageName).String(messageHeader.GetMessageName(*meta)), - attribute.Key(messageTracing.Payload).String(payload), - attribute.String(messageTracing.Headers, meta.ToJson()), - attribute.Key(tracing.Timestamp).Int64(time.Now().UnixMilli()), + attribute.Key(tracing.MessageType). + String(message.GetMessageTypeName()), + attribute.Key(tracing.MessageName). + String(messageHeader.GetMessageName(*meta)), + attribute.Key(tracing.Payload).String(payload), + attribute.String(tracing.Headers, meta.ToJson()), + attribute.Key(constants.Timestamp).Int64(time.Now().UnixMilli()), semconv.MessagingDestinationName(producerTracingOptions.Destination), - semconv.MessagingSystemKey.String(producerTracingOptions.MessagingSystem), + semconv.MessagingSystemKey.String( + producerTracingOptions.MessagingSystem, + ), semconv.MessagingOperationKey.String("send"), } - if producerTracingOptions.OtherAttributes != nil && len(producerTracingOptions.OtherAttributes) > 0 { + if producerTracingOptions.OtherAttributes != nil && + len(producerTracingOptions.OtherAttributes) > 0 { attrs = append(attrs, producerTracingOptions.OtherAttributes...) } @@ -110,11 +136,21 @@ func getTraceOptions( return opts } -func addAfterBaggage(ctx context.Context, message types.IMessage, meta *metadata.Metadata) context.Context { +func addAfterBaggage( + ctx context.Context, + message types.IMessage, + meta *metadata.Metadata, +) context.Context { correlationId := messageHeader.GetCorrelationId(*meta) - correlationIdBag, _ := baggage.NewMember(string(semconv.MessagingMessageConversationIDKey), correlationId) - messageIdBag, _ := baggage.NewMember(string(semconv.MessageIDKey), message.GeMessageId()) + correlationIdBag, _ := baggage.NewMember( + string(semconv.MessagingMessageConversationIDKey), + correlationId, + ) + messageIdBag, _ := baggage.NewMember( + string(semconv.MessageIDKey), + message.GeMessageId(), + ) b, _ := baggage.New(correlationIdBag, messageIdBag) ctx = baggage.ContextWithBaggage(ctx, b) diff --git a/internal/pkg/messaging/otel/tracing/producer/producer_tracing_options.go b/internal/pkg/core/messaging/otel/tracing/producer/producer_tracing_options.go similarity index 100% rename from internal/pkg/messaging/otel/tracing/producer/producer_tracing_options.go rename to internal/pkg/core/messaging/otel/tracing/producer/producer_tracing_options.go diff --git a/internal/pkg/messaging/otel/tracing/trace.go b/internal/pkg/core/messaging/otel/tracing/trace.go similarity index 52% rename from internal/pkg/messaging/otel/tracing/trace.go rename to internal/pkg/core/messaging/otel/tracing/trace.go index 34dd4d41..79c756cb 100644 --- a/internal/pkg/messaging/otel/tracing/trace.go +++ b/internal/pkg/core/messaging/otel/tracing/trace.go @@ -1,7 +1,7 @@ package tracing import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" "go.opentelemetry.io/otel/trace" ) @@ -10,6 +10,6 @@ var MessagingTracer trace.Tracer func init() { MessagingTracer = tracing.NewAppTracer( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging", + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/messaging", ) // instrumentation name } diff --git a/internal/pkg/core/messaging/persistmessage/message_persistence_service.go b/internal/pkg/core/messaging/persistmessage/message_persistence_service.go new file mode 100644 index 00000000..a28710a9 --- /dev/null +++ b/internal/pkg/core/messaging/persistmessage/message_persistence_service.go @@ -0,0 +1,41 @@ +package persistmessage + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + + uuid "github.com/satori/go.uuid" +) + +type MessagePersistenceService interface { + Add(ctx context.Context, storeMessage *StoreMessage) error + Update(ctx context.Context, storeMessage *StoreMessage) error + ChangeState( + ctx context.Context, + messageID uuid.UUID, + status MessageStatus, + ) error + GetAllActive(ctx context.Context) ([]*StoreMessage, error) + GetByFilter( + ctx context.Context, + predicate func(*StoreMessage) bool, + ) ([]*StoreMessage, error) + GetById(ctx context.Context, id uuid.UUID) (*StoreMessage, error) + Remove(ctx context.Context, storeMessage *StoreMessage) (bool, error) + CleanupMessages(ctx context.Context) error + Process(messageID string, ctx context.Context) error + ProcessAll(ctx context.Context) error + AddPublishMessage( + messageEnvelope types.MessageEnvelope, + ctx context.Context, + ) error + AddReceivedMessage( + messageEnvelope types.MessageEnvelope, + ctx context.Context, + ) error + //AddInternalMessage( + // internalCommand InternalMessage, + // ctx context.Context, + //) error +} diff --git a/internal/pkg/core/messaging/persistmessage/store_message.go b/internal/pkg/core/messaging/persistmessage/store_message.go new file mode 100644 index 00000000..f9b92e35 --- /dev/null +++ b/internal/pkg/core/messaging/persistmessage/store_message.go @@ -0,0 +1,61 @@ +package persistmessage + +import ( + "time" + + uuid "github.com/satori/go.uuid" +) + +type MessageDeliveryType int + +const ( + Outbox MessageDeliveryType = 1 + Inbox MessageDeliveryType = 2 + Internal MessageDeliveryType = 4 +) + +type MessageStatus int + +const ( + Stored MessageStatus = 1 + Processed MessageStatus = 2 +) + +type StoreMessage struct { + ID uuid.UUID `gorm:"primaryKey"` + DataType string + Data string + CreatedAt time.Time `gorm:"default:current_timestamp"` + RetryCount int + MessageStatus MessageStatus + DeliveryType MessageDeliveryType +} + +func NewStoreMessage( + id uuid.UUID, + dataType string, + data string, + deliveryType MessageDeliveryType, +) *StoreMessage { + return &StoreMessage{ + ID: id, + DataType: dataType, + Data: data, + CreatedAt: time.Now(), + MessageStatus: Stored, + RetryCount: 0, + DeliveryType: deliveryType, + } +} + +func (sm *StoreMessage) ChangeState(messageStatus MessageStatus) { + sm.MessageStatus = messageStatus +} + +func (sm *StoreMessage) IncreaseRetry() { + sm.RetryCount++ +} + +func (sm *StoreMessage) TableName() string { + return "store_messages" +} diff --git a/internal/pkg/messaging/pipeline/consumer_pipeline.go b/internal/pkg/core/messaging/pipeline/consumer_pipeline.go similarity index 70% rename from internal/pkg/messaging/pipeline/consumer_pipeline.go rename to internal/pkg/core/messaging/pipeline/consumer_pipeline.go index 05c2165b..d5a35c8f 100644 --- a/internal/pkg/messaging/pipeline/consumer_pipeline.go +++ b/internal/pkg/core/messaging/pipeline/consumer_pipeline.go @@ -3,11 +3,11 @@ package pipeline import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) // ConsumerHandlerFunc is a continuation for the next task to execute in the pipeline -type ConsumerHandlerFunc func() error +type ConsumerHandlerFunc func(ctx context.Context) error // ConsumerPipeline is a Pipeline for wrapping the inner consumer handler. type ConsumerPipeline interface { diff --git a/internal/pkg/messaging/pipeline/consumer_pipeline_configuration.go b/internal/pkg/core/messaging/pipeline/consumer_pipeline_configuration.go similarity index 100% rename from internal/pkg/messaging/pipeline/consumer_pipeline_configuration.go rename to internal/pkg/core/messaging/pipeline/consumer_pipeline_configuration.go diff --git a/internal/pkg/messaging/pipeline/consumer_pipeline_configuration_builder.go b/internal/pkg/core/messaging/pipeline/consumer_pipeline_configuration_builder.go similarity index 100% rename from internal/pkg/messaging/pipeline/consumer_pipeline_configuration_builder.go rename to internal/pkg/core/messaging/pipeline/consumer_pipeline_configuration_builder.go diff --git a/internal/pkg/messaging/producer/producer.go b/internal/pkg/core/messaging/producer/producer.go similarity index 66% rename from internal/pkg/messaging/producer/producer.go rename to internal/pkg/core/messaging/producer/producer.go index 5589fd13..378332b8 100644 --- a/internal/pkg/messaging/producer/producer.go +++ b/internal/pkg/core/messaging/producer/producer.go @@ -3,8 +3,8 @@ package producer import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" ) type Producer interface { diff --git a/internal/pkg/messaging/producer/producer_builder.go b/internal/pkg/core/messaging/producer/producer_builder.go similarity index 100% rename from internal/pkg/messaging/producer/producer_builder.go rename to internal/pkg/core/messaging/producer/producer_builder.go diff --git a/internal/pkg/messaging/types/message.go b/internal/pkg/core/messaging/types/message.go similarity index 56% rename from internal/pkg/messaging/types/message.go rename to internal/pkg/core/messaging/types/message.go index 7203ffda..10d167b1 100644 --- a/internal/pkg/messaging/types/message.go +++ b/internal/pkg/core/messaging/types/message.go @@ -2,14 +2,16 @@ package types import ( "time" + + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" ) type IMessage interface { GeMessageId() string GetCreated() time.Time - GetEventTypeName() string - SetEventTypeName(string) - IsMessage() bool + // GetMessageTypeName get short type name of the message - we use message short type name instead of full type name because this message in other receiver packages could have different package name + GetMessageTypeName() string + GetMessageFullTypeName() string } type Message struct { @@ -27,13 +29,6 @@ func NewMessageWithTypeName(messageId string, eventTypeName string) *Message { return &Message{MessageId: messageId, Created: time.Now(), EventType: eventTypeName} } -func (m *Message) IsMessage() bool { - if m == nil { - return false - } - return true -} - func (m *Message) GeMessageId() string { return m.MessageId } @@ -42,10 +37,10 @@ func (m *Message) GetCreated() time.Time { return m.Created } -func (m *Message) GetEventTypeName() string { - return m.EventType +func (m *Message) GetMessageTypeName() string { + return typeMapper.GetTypeName(m) } -func (m *Message) SetEventTypeName(eventTypeName string) { - m.EventType = eventTypeName +func (m *Message) GetMessageFullTypeName() string { + return typeMapper.GetFullTypeName(m) } diff --git a/internal/pkg/messaging/types/message_consume_context.go b/internal/pkg/core/messaging/types/message_consume_context.go similarity index 95% rename from internal/pkg/messaging/types/message_consume_context.go rename to internal/pkg/core/messaging/types/message_consume_context.go index 6d214c37..a370bba7 100644 --- a/internal/pkg/messaging/types/message_consume_context.go +++ b/internal/pkg/core/messaging/types/message_consume_context.go @@ -3,7 +3,7 @@ package types import ( "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" ) type MessageConsumeContext interface { diff --git a/internal/pkg/core/messaging/types/message_envelope.go b/internal/pkg/core/messaging/types/message_envelope.go new file mode 100644 index 00000000..f2a1ded1 --- /dev/null +++ b/internal/pkg/core/messaging/types/message_envelope.go @@ -0,0 +1,37 @@ +package types + +type MessageEnvelope struct { + Message IMessage + Headers map[string]interface{} +} + +func NewMessageEnvelope( + message IMessage, + headers map[string]interface{}, +) *MessageEnvelope { + if headers == nil { + headers = make(map[string]interface{}) + } + + return &MessageEnvelope{ + Message: message, + Headers: headers, + } +} + +type MessageEnvelopeT[T IMessage] struct { + *MessageEnvelope + Message T +} + +func NewMessageEnvelopeT[T IMessage]( + message T, + headers map[string]interface{}, +) *MessageEnvelopeT[T] { + messageEnvelope := NewMessageEnvelope(message, headers) + + return &MessageEnvelopeT[T]{ + MessageEnvelope: messageEnvelope, + Message: message, + } +} diff --git a/internal/pkg/messaging/types/mock_IMessage_test.go b/internal/pkg/core/messaging/types/mock_IMessage_test.go similarity index 98% rename from internal/pkg/messaging/types/mock_IMessage_test.go rename to internal/pkg/core/messaging/types/mock_IMessage_test.go index 0b1b265b..7f030c23 100644 --- a/internal/pkg/messaging/types/mock_IMessage_test.go +++ b/internal/pkg/core/messaging/types/mock_IMessage_test.go @@ -104,7 +104,7 @@ func (_c *MockIMessage_GetCreated_Call) RunAndReturn(run func() time.Time) *Mock } // GetEventTypeName provides a mock function with given fields: -func (_m *MockIMessage) GetEventTypeName() string { +func (_m *MockIMessage) GetMessageTypeName() string { ret := _m.Called() var r0 string @@ -117,14 +117,14 @@ func (_m *MockIMessage) GetEventTypeName() string { return r0 } -// MockIMessage_GetEventTypeName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEventTypeName' +// MockIMessage_GetEventTypeName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMessageTypeName' type MockIMessage_GetEventTypeName_Call struct { *mock.Call } // GetEventTypeName is a helper method to define mock.On call func (_e *MockIMessage_Expecter) GetEventTypeName() *MockIMessage_GetEventTypeName_Call { - return &MockIMessage_GetEventTypeName_Call{Call: _e.mock.On("GetEventTypeName")} + return &MockIMessage_GetEventTypeName_Call{Call: _e.mock.On("GetMessageTypeName")} } func (_c *MockIMessage_GetEventTypeName_Call) Run(run func()) *MockIMessage_GetEventTypeName_Call { @@ -223,7 +223,8 @@ func (_c *MockIMessage_SetEventTypeName_Call) RunAndReturn(run func(string)) *Mo func NewMockIMessage(t interface { mock.TestingT Cleanup(func()) -}) *MockIMessage { +}, +) *MockIMessage { mock := &MockIMessage{} mock.Mock.Test(t) diff --git a/internal/pkg/messaging/utils/utils.go b/internal/pkg/core/messaging/utils/utils.go similarity index 93% rename from internal/pkg/messaging/utils/utils.go rename to internal/pkg/core/messaging/utils/utils.go index 534a973b..d3dfb1a5 100644 --- a/internal/pkg/messaging/utils/utils.go +++ b/internal/pkg/core/messaging/utils/utils.go @@ -3,8 +3,8 @@ package utils import ( "reflect" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/ahmetb/go-linq/v3" "github.com/iancoleman/strcase" diff --git a/internal/pkg/core/mocks/GenericRepository.go b/internal/pkg/core/mocks/GenericRepository.go deleted file mode 100644 index 6a324359..00000000 --- a/internal/pkg/core/mocks/GenericRepository.go +++ /dev/null @@ -1,737 +0,0 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. - -package mocks - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - specification "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data/specification" - - utils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils" - - uuid "github.com/satori/go.uuid" -) - -// GenericRepository is an autogenerated mock type for the GenericRepository type -type GenericRepository[TEntity interface{}] struct { - mock.Mock -} - -type GenericRepository_Expecter[TEntity interface{}] struct { - mock *mock.Mock -} - -func (_m *GenericRepository[TEntity]) EXPECT() *GenericRepository_Expecter[TEntity] { - return &GenericRepository_Expecter[TEntity]{mock: &_m.Mock} -} - -// Add provides a mock function with given fields: ctx, entity -func (_m *GenericRepository[TEntity]) Add(ctx context.Context, entity TEntity) error { - ret := _m.Called(ctx, entity) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, TEntity) error); ok { - r0 = rf(ctx, entity) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepository_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add' -type GenericRepository_Add_Call[TEntity interface{}] struct { - *mock.Call -} - -// Add is a helper method to define mock.On call -// - ctx context.Context -// - entity TEntity -func (_e *GenericRepository_Expecter[TEntity]) Add(ctx interface{}, entity interface{}) *GenericRepository_Add_Call[TEntity] { - return &GenericRepository_Add_Call[TEntity]{Call: _e.mock.On("Add", ctx, entity)} -} - -func (_c *GenericRepository_Add_Call[TEntity]) Run(run func(ctx context.Context, entity TEntity)) *GenericRepository_Add_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(TEntity)) - }) - return _c -} - -func (_c *GenericRepository_Add_Call[TEntity]) Return(_a0 error) *GenericRepository_Add_Call[TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepository_Add_Call[TEntity]) RunAndReturn(run func(context.Context, TEntity) error) *GenericRepository_Add_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// AddAll provides a mock function with given fields: ctx, entities -func (_m *GenericRepository[TEntity]) AddAll(ctx context.Context, entities []TEntity) error { - ret := _m.Called(ctx, entities) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []TEntity) error); ok { - r0 = rf(ctx, entities) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepository_AddAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddAll' -type GenericRepository_AddAll_Call[TEntity interface{}] struct { - *mock.Call -} - -// AddAll is a helper method to define mock.On call -// - ctx context.Context -// - entities []TEntity -func (_e *GenericRepository_Expecter[TEntity]) AddAll(ctx interface{}, entities interface{}) *GenericRepository_AddAll_Call[TEntity] { - return &GenericRepository_AddAll_Call[TEntity]{Call: _e.mock.On("AddAll", ctx, entities)} -} - -func (_c *GenericRepository_AddAll_Call[TEntity]) Run(run func(ctx context.Context, entities []TEntity)) *GenericRepository_AddAll_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]TEntity)) - }) - return _c -} - -func (_c *GenericRepository_AddAll_Call[TEntity]) Return(_a0 error) *GenericRepository_AddAll_Call[TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepository_AddAll_Call[TEntity]) RunAndReturn(run func(context.Context, []TEntity) error) *GenericRepository_AddAll_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// Count provides a mock function with given fields: ctx -func (_m *GenericRepository[TEntity]) Count(ctx context.Context) int64 { - ret := _m.Called(ctx) - - var r0 int64 - if rf, ok := ret.Get(0).(func(context.Context) int64); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(int64) - } - - return r0 -} - -// GenericRepository_Count_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Count' -type GenericRepository_Count_Call[TEntity interface{}] struct { - *mock.Call -} - -// Count is a helper method to define mock.On call -// - ctx context.Context -func (_e *GenericRepository_Expecter[TEntity]) Count(ctx interface{}) *GenericRepository_Count_Call[TEntity] { - return &GenericRepository_Count_Call[TEntity]{Call: _e.mock.On("Count", ctx)} -} - -func (_c *GenericRepository_Count_Call[TEntity]) Run(run func(ctx context.Context)) *GenericRepository_Count_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *GenericRepository_Count_Call[TEntity]) Return(_a0 int64) *GenericRepository_Count_Call[TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepository_Count_Call[TEntity]) RunAndReturn(run func(context.Context) int64) *GenericRepository_Count_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function with given fields: ctx, id -func (_m *GenericRepository[TEntity]) Delete(ctx context.Context, id uuid.UUID) error { - ret := _m.Called(ctx, id) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type GenericRepository_Delete_Call[TEntity interface{}] struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - ctx context.Context -// - id uuid.UUID -func (_e *GenericRepository_Expecter[TEntity]) Delete(ctx interface{}, id interface{}) *GenericRepository_Delete_Call[TEntity] { - return &GenericRepository_Delete_Call[TEntity]{Call: _e.mock.On("Delete", ctx, id)} -} - -func (_c *GenericRepository_Delete_Call[TEntity]) Run(run func(ctx context.Context, id uuid.UUID)) *GenericRepository_Delete_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uuid.UUID)) - }) - return _c -} - -func (_c *GenericRepository_Delete_Call[TEntity]) Return(_a0 error) *GenericRepository_Delete_Call[TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepository_Delete_Call[TEntity]) RunAndReturn(run func(context.Context, uuid.UUID) error) *GenericRepository_Delete_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// Find provides a mock function with given fields: ctx, _a1 -func (_m *GenericRepository[TEntity]) Find(ctx context.Context, _a1 specification.Specification) ([]TEntity, error) { - ret := _m.Called(ctx, _a1) - - var r0 []TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, specification.Specification) ([]TEntity, error)); ok { - return rf(ctx, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, specification.Specification) []TEntity); ok { - r0 = rf(ctx, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]TEntity) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, specification.Specification) error); ok { - r1 = rf(ctx, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepository_Find_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Find' -type GenericRepository_Find_Call[TEntity interface{}] struct { - *mock.Call -} - -// Find is a helper method to define mock.On call -// - ctx context.Context -// - _a1 specification.Specification -func (_e *GenericRepository_Expecter[TEntity]) Find(ctx interface{}, _a1 interface{}) *GenericRepository_Find_Call[TEntity] { - return &GenericRepository_Find_Call[TEntity]{Call: _e.mock.On("Find", ctx, _a1)} -} - -func (_c *GenericRepository_Find_Call[TEntity]) Run(run func(ctx context.Context, _a1 specification.Specification)) *GenericRepository_Find_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(specification.Specification)) - }) - return _c -} - -func (_c *GenericRepository_Find_Call[TEntity]) Return(_a0 []TEntity, _a1 error) *GenericRepository_Find_Call[TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepository_Find_Call[TEntity]) RunAndReturn(run func(context.Context, specification.Specification) ([]TEntity, error)) *GenericRepository_Find_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// FirstOrDefault provides a mock function with given fields: ctx, filters -func (_m *GenericRepository[TEntity]) FirstOrDefault(ctx context.Context, filters map[string]interface{}) (TEntity, error) { - ret := _m.Called(ctx, filters) - - var r0 TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) (TEntity, error)); ok { - return rf(ctx, filters) - } - if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) TEntity); ok { - r0 = rf(ctx, filters) - } else { - r0 = ret.Get(0).(TEntity) - } - - if rf, ok := ret.Get(1).(func(context.Context, map[string]interface{}) error); ok { - r1 = rf(ctx, filters) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepository_FirstOrDefault_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirstOrDefault' -type GenericRepository_FirstOrDefault_Call[TEntity interface{}] struct { - *mock.Call -} - -// FirstOrDefault is a helper method to define mock.On call -// - ctx context.Context -// - filters map[string]interface{} -func (_e *GenericRepository_Expecter[TEntity]) FirstOrDefault(ctx interface{}, filters interface{}) *GenericRepository_FirstOrDefault_Call[TEntity] { - return &GenericRepository_FirstOrDefault_Call[TEntity]{Call: _e.mock.On("FirstOrDefault", ctx, filters)} -} - -func (_c *GenericRepository_FirstOrDefault_Call[TEntity]) Run(run func(ctx context.Context, filters map[string]interface{})) *GenericRepository_FirstOrDefault_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(map[string]interface{})) - }) - return _c -} - -func (_c *GenericRepository_FirstOrDefault_Call[TEntity]) Return(_a0 TEntity, _a1 error) *GenericRepository_FirstOrDefault_Call[TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepository_FirstOrDefault_Call[TEntity]) RunAndReturn(run func(context.Context, map[string]interface{}) (TEntity, error)) *GenericRepository_FirstOrDefault_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// GetAll provides a mock function with given fields: ctx, listQuery -func (_m *GenericRepository[TEntity]) GetAll(ctx context.Context, listQuery *utils.ListQuery) (*utils.ListResult[TEntity], error) { - ret := _m.Called(ctx, listQuery) - - var r0 *utils.ListResult[TEntity] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) (*utils.ListResult[TEntity], error)); ok { - return rf(ctx, listQuery) - } - if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) *utils.ListResult[TEntity]); ok { - r0 = rf(ctx, listQuery) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*utils.ListResult[TEntity]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *utils.ListQuery) error); ok { - r1 = rf(ctx, listQuery) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepository_GetAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAll' -type GenericRepository_GetAll_Call[TEntity interface{}] struct { - *mock.Call -} - -// GetAll is a helper method to define mock.On call -// - ctx context.Context -// - listQuery *utils.ListQuery -func (_e *GenericRepository_Expecter[TEntity]) GetAll(ctx interface{}, listQuery interface{}) *GenericRepository_GetAll_Call[TEntity] { - return &GenericRepository_GetAll_Call[TEntity]{Call: _e.mock.On("GetAll", ctx, listQuery)} -} - -func (_c *GenericRepository_GetAll_Call[TEntity]) Run(run func(ctx context.Context, listQuery *utils.ListQuery)) *GenericRepository_GetAll_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*utils.ListQuery)) - }) - return _c -} - -func (_c *GenericRepository_GetAll_Call[TEntity]) Return(_a0 *utils.ListResult[TEntity], _a1 error) *GenericRepository_GetAll_Call[TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepository_GetAll_Call[TEntity]) RunAndReturn(run func(context.Context, *utils.ListQuery) (*utils.ListResult[TEntity], error)) *GenericRepository_GetAll_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// GetByFilter provides a mock function with given fields: ctx, filters -func (_m *GenericRepository[TEntity]) GetByFilter(ctx context.Context, filters map[string]interface{}) ([]TEntity, error) { - ret := _m.Called(ctx, filters) - - var r0 []TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) ([]TEntity, error)); ok { - return rf(ctx, filters) - } - if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) []TEntity); ok { - r0 = rf(ctx, filters) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]TEntity) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, map[string]interface{}) error); ok { - r1 = rf(ctx, filters) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepository_GetByFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByFilter' -type GenericRepository_GetByFilter_Call[TEntity interface{}] struct { - *mock.Call -} - -// GetByFilter is a helper method to define mock.On call -// - ctx context.Context -// - filters map[string]interface{} -func (_e *GenericRepository_Expecter[TEntity]) GetByFilter(ctx interface{}, filters interface{}) *GenericRepository_GetByFilter_Call[TEntity] { - return &GenericRepository_GetByFilter_Call[TEntity]{Call: _e.mock.On("GetByFilter", ctx, filters)} -} - -func (_c *GenericRepository_GetByFilter_Call[TEntity]) Run(run func(ctx context.Context, filters map[string]interface{})) *GenericRepository_GetByFilter_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(map[string]interface{})) - }) - return _c -} - -func (_c *GenericRepository_GetByFilter_Call[TEntity]) Return(_a0 []TEntity, _a1 error) *GenericRepository_GetByFilter_Call[TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepository_GetByFilter_Call[TEntity]) RunAndReturn(run func(context.Context, map[string]interface{}) ([]TEntity, error)) *GenericRepository_GetByFilter_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// GetByFuncFilter provides a mock function with given fields: ctx, filterFunc -func (_m *GenericRepository[TEntity]) GetByFuncFilter(ctx context.Context, filterFunc func(TEntity) bool) ([]TEntity, error) { - ret := _m.Called(ctx, filterFunc) - - var r0 []TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, func(TEntity) bool) ([]TEntity, error)); ok { - return rf(ctx, filterFunc) - } - if rf, ok := ret.Get(0).(func(context.Context, func(TEntity) bool) []TEntity); ok { - r0 = rf(ctx, filterFunc) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]TEntity) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, func(TEntity) bool) error); ok { - r1 = rf(ctx, filterFunc) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepository_GetByFuncFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByFuncFilter' -type GenericRepository_GetByFuncFilter_Call[TEntity interface{}] struct { - *mock.Call -} - -// GetByFuncFilter is a helper method to define mock.On call -// - ctx context.Context -// - filterFunc func(TEntity) bool -func (_e *GenericRepository_Expecter[TEntity]) GetByFuncFilter(ctx interface{}, filterFunc interface{}) *GenericRepository_GetByFuncFilter_Call[TEntity] { - return &GenericRepository_GetByFuncFilter_Call[TEntity]{Call: _e.mock.On("GetByFuncFilter", ctx, filterFunc)} -} - -func (_c *GenericRepository_GetByFuncFilter_Call[TEntity]) Run(run func(ctx context.Context, filterFunc func(TEntity) bool)) *GenericRepository_GetByFuncFilter_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(func(TEntity) bool)) - }) - return _c -} - -func (_c *GenericRepository_GetByFuncFilter_Call[TEntity]) Return(_a0 []TEntity, _a1 error) *GenericRepository_GetByFuncFilter_Call[TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepository_GetByFuncFilter_Call[TEntity]) RunAndReturn(run func(context.Context, func(TEntity) bool) ([]TEntity, error)) *GenericRepository_GetByFuncFilter_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// GetById provides a mock function with given fields: ctx, id -func (_m *GenericRepository[TEntity]) GetById(ctx context.Context, id uuid.UUID) (TEntity, error) { - ret := _m.Called(ctx, id) - - var r0 TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (TEntity, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) TEntity); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(TEntity) - } - - if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepository_GetById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetById' -type GenericRepository_GetById_Call[TEntity interface{}] struct { - *mock.Call -} - -// GetById is a helper method to define mock.On call -// - ctx context.Context -// - id uuid.UUID -func (_e *GenericRepository_Expecter[TEntity]) GetById(ctx interface{}, id interface{}) *GenericRepository_GetById_Call[TEntity] { - return &GenericRepository_GetById_Call[TEntity]{Call: _e.mock.On("GetById", ctx, id)} -} - -func (_c *GenericRepository_GetById_Call[TEntity]) Run(run func(ctx context.Context, id uuid.UUID)) *GenericRepository_GetById_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uuid.UUID)) - }) - return _c -} - -func (_c *GenericRepository_GetById_Call[TEntity]) Return(_a0 TEntity, _a1 error) *GenericRepository_GetById_Call[TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepository_GetById_Call[TEntity]) RunAndReturn(run func(context.Context, uuid.UUID) (TEntity, error)) *GenericRepository_GetById_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// Search provides a mock function with given fields: ctx, searchTerm, listQuery -func (_m *GenericRepository[TEntity]) Search(ctx context.Context, searchTerm string, listQuery *utils.ListQuery) (*utils.ListResult[TEntity], error) { - ret := _m.Called(ctx, searchTerm, listQuery) - - var r0 *utils.ListResult[TEntity] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) (*utils.ListResult[TEntity], error)); ok { - return rf(ctx, searchTerm, listQuery) - } - if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) *utils.ListResult[TEntity]); ok { - r0 = rf(ctx, searchTerm, listQuery) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*utils.ListResult[TEntity]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, *utils.ListQuery) error); ok { - r1 = rf(ctx, searchTerm, listQuery) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepository_Search_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Search' -type GenericRepository_Search_Call[TEntity interface{}] struct { - *mock.Call -} - -// Search is a helper method to define mock.On call -// - ctx context.Context -// - searchTerm string -// - listQuery *utils.ListQuery -func (_e *GenericRepository_Expecter[TEntity]) Search(ctx interface{}, searchTerm interface{}, listQuery interface{}) *GenericRepository_Search_Call[TEntity] { - return &GenericRepository_Search_Call[TEntity]{Call: _e.mock.On("Search", ctx, searchTerm, listQuery)} -} - -func (_c *GenericRepository_Search_Call[TEntity]) Run(run func(ctx context.Context, searchTerm string, listQuery *utils.ListQuery)) *GenericRepository_Search_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(*utils.ListQuery)) - }) - return _c -} - -func (_c *GenericRepository_Search_Call[TEntity]) Return(_a0 *utils.ListResult[TEntity], _a1 error) *GenericRepository_Search_Call[TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepository_Search_Call[TEntity]) RunAndReturn(run func(context.Context, string, *utils.ListQuery) (*utils.ListResult[TEntity], error)) *GenericRepository_Search_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// SkipTake provides a mock function with given fields: ctx, skip, take -func (_m *GenericRepository[TEntity]) SkipTake(ctx context.Context, skip int, take int) ([]TEntity, error) { - ret := _m.Called(ctx, skip, take) - - var r0 []TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int, int) ([]TEntity, error)); ok { - return rf(ctx, skip, take) - } - if rf, ok := ret.Get(0).(func(context.Context, int, int) []TEntity); ok { - r0 = rf(ctx, skip, take) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]TEntity) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, int, int) error); ok { - r1 = rf(ctx, skip, take) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepository_SkipTake_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SkipTake' -type GenericRepository_SkipTake_Call[TEntity interface{}] struct { - *mock.Call -} - -// SkipTake is a helper method to define mock.On call -// - ctx context.Context -// - skip int -// - take int -func (_e *GenericRepository_Expecter[TEntity]) SkipTake(ctx interface{}, skip interface{}, take interface{}) *GenericRepository_SkipTake_Call[TEntity] { - return &GenericRepository_SkipTake_Call[TEntity]{Call: _e.mock.On("SkipTake", ctx, skip, take)} -} - -func (_c *GenericRepository_SkipTake_Call[TEntity]) Run(run func(ctx context.Context, skip int, take int)) *GenericRepository_SkipTake_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int), args[2].(int)) - }) - return _c -} - -func (_c *GenericRepository_SkipTake_Call[TEntity]) Return(_a0 []TEntity, _a1 error) *GenericRepository_SkipTake_Call[TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepository_SkipTake_Call[TEntity]) RunAndReturn(run func(context.Context, int, int) ([]TEntity, error)) *GenericRepository_SkipTake_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with given fields: ctx, entity -func (_m *GenericRepository[TEntity]) Update(ctx context.Context, entity TEntity) error { - ret := _m.Called(ctx, entity) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, TEntity) error); ok { - r0 = rf(ctx, entity) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type GenericRepository_Update_Call[TEntity interface{}] struct { - *mock.Call -} - -// Update is a helper method to define mock.On call -// - ctx context.Context -// - entity TEntity -func (_e *GenericRepository_Expecter[TEntity]) Update(ctx interface{}, entity interface{}) *GenericRepository_Update_Call[TEntity] { - return &GenericRepository_Update_Call[TEntity]{Call: _e.mock.On("Update", ctx, entity)} -} - -func (_c *GenericRepository_Update_Call[TEntity]) Run(run func(ctx context.Context, entity TEntity)) *GenericRepository_Update_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(TEntity)) - }) - return _c -} - -func (_c *GenericRepository_Update_Call[TEntity]) Return(_a0 error) *GenericRepository_Update_Call[TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepository_Update_Call[TEntity]) RunAndReturn(run func(context.Context, TEntity) error) *GenericRepository_Update_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// UpdateAll provides a mock function with given fields: ctx, entities -func (_m *GenericRepository[TEntity]) UpdateAll(ctx context.Context, entities []TEntity) error { - ret := _m.Called(ctx, entities) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []TEntity) error); ok { - r0 = rf(ctx, entities) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepository_UpdateAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateAll' -type GenericRepository_UpdateAll_Call[TEntity interface{}] struct { - *mock.Call -} - -// UpdateAll is a helper method to define mock.On call -// - ctx context.Context -// - entities []TEntity -func (_e *GenericRepository_Expecter[TEntity]) UpdateAll(ctx interface{}, entities interface{}) *GenericRepository_UpdateAll_Call[TEntity] { - return &GenericRepository_UpdateAll_Call[TEntity]{Call: _e.mock.On("UpdateAll", ctx, entities)} -} - -func (_c *GenericRepository_UpdateAll_Call[TEntity]) Run(run func(ctx context.Context, entities []TEntity)) *GenericRepository_UpdateAll_Call[TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]TEntity)) - }) - return _c -} - -func (_c *GenericRepository_UpdateAll_Call[TEntity]) Return(_a0 error) *GenericRepository_UpdateAll_Call[TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepository_UpdateAll_Call[TEntity]) RunAndReturn(run func(context.Context, []TEntity) error) *GenericRepository_UpdateAll_Call[TEntity] { - _c.Call.Return(run) - return _c -} - -// NewGenericRepository creates a new instance of GenericRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewGenericRepository[TEntity interface{}](t interface { - mock.TestingT - Cleanup(func()) -}) *GenericRepository[TEntity] { - mock := &GenericRepository[TEntity]{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/core/mocks/GenericRepositoryWithDataModel.go b/internal/pkg/core/mocks/GenericRepositoryWithDataModel.go deleted file mode 100644 index 21854bb2..00000000 --- a/internal/pkg/core/mocks/GenericRepositoryWithDataModel.go +++ /dev/null @@ -1,737 +0,0 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. - -package mocks - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - specification "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data/specification" - - utils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils" - - uuid "github.com/satori/go.uuid" -) - -// GenericRepositoryWithDataModel is an autogenerated mock type for the GenericRepositoryWithDataModel type -type GenericRepositoryWithDataModel[TDataModel interface{}, TEntity interface{}] struct { - mock.Mock -} - -type GenericRepositoryWithDataModel_Expecter[TDataModel interface{}, TEntity interface{}] struct { - mock *mock.Mock -} - -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) EXPECT() *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]{mock: &_m.Mock} -} - -// Add provides a mock function with given fields: ctx, entity -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) Add(ctx context.Context, entity TEntity) error { - ret := _m.Called(ctx, entity) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, TEntity) error); ok { - r0 = rf(ctx, entity) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepositoryWithDataModel_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add' -type GenericRepositoryWithDataModel_Add_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// Add is a helper method to define mock.On call -// - ctx context.Context -// - entity TEntity -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) Add(ctx interface{}, entity interface{}) *GenericRepositoryWithDataModel_Add_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_Add_Call[TDataModel, TEntity]{Call: _e.mock.On("Add", ctx, entity)} -} - -func (_c *GenericRepositoryWithDataModel_Add_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, entity TEntity)) *GenericRepositoryWithDataModel_Add_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(TEntity)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Add_Call[TDataModel, TEntity]) Return(_a0 error) *GenericRepositoryWithDataModel_Add_Call[TDataModel, TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Add_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, TEntity) error) *GenericRepositoryWithDataModel_Add_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// AddAll provides a mock function with given fields: ctx, entities -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) AddAll(ctx context.Context, entities []TEntity) error { - ret := _m.Called(ctx, entities) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []TEntity) error); ok { - r0 = rf(ctx, entities) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepositoryWithDataModel_AddAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddAll' -type GenericRepositoryWithDataModel_AddAll_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// AddAll is a helper method to define mock.On call -// - ctx context.Context -// - entities []TEntity -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) AddAll(ctx interface{}, entities interface{}) *GenericRepositoryWithDataModel_AddAll_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_AddAll_Call[TDataModel, TEntity]{Call: _e.mock.On("AddAll", ctx, entities)} -} - -func (_c *GenericRepositoryWithDataModel_AddAll_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, entities []TEntity)) *GenericRepositoryWithDataModel_AddAll_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]TEntity)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_AddAll_Call[TDataModel, TEntity]) Return(_a0 error) *GenericRepositoryWithDataModel_AddAll_Call[TDataModel, TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepositoryWithDataModel_AddAll_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, []TEntity) error) *GenericRepositoryWithDataModel_AddAll_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// Count provides a mock function with given fields: ctx -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) Count(ctx context.Context) int64 { - ret := _m.Called(ctx) - - var r0 int64 - if rf, ok := ret.Get(0).(func(context.Context) int64); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(int64) - } - - return r0 -} - -// GenericRepositoryWithDataModel_Count_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Count' -type GenericRepositoryWithDataModel_Count_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// Count is a helper method to define mock.On call -// - ctx context.Context -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) Count(ctx interface{}) *GenericRepositoryWithDataModel_Count_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_Count_Call[TDataModel, TEntity]{Call: _e.mock.On("Count", ctx)} -} - -func (_c *GenericRepositoryWithDataModel_Count_Call[TDataModel, TEntity]) Run(run func(ctx context.Context)) *GenericRepositoryWithDataModel_Count_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Count_Call[TDataModel, TEntity]) Return(_a0 int64) *GenericRepositoryWithDataModel_Count_Call[TDataModel, TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Count_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context) int64) *GenericRepositoryWithDataModel_Count_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function with given fields: ctx, id -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) Delete(ctx context.Context, id uuid.UUID) error { - ret := _m.Called(ctx, id) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepositoryWithDataModel_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type GenericRepositoryWithDataModel_Delete_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - ctx context.Context -// - id uuid.UUID -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) Delete(ctx interface{}, id interface{}) *GenericRepositoryWithDataModel_Delete_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_Delete_Call[TDataModel, TEntity]{Call: _e.mock.On("Delete", ctx, id)} -} - -func (_c *GenericRepositoryWithDataModel_Delete_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, id uuid.UUID)) *GenericRepositoryWithDataModel_Delete_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uuid.UUID)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Delete_Call[TDataModel, TEntity]) Return(_a0 error) *GenericRepositoryWithDataModel_Delete_Call[TDataModel, TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Delete_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, uuid.UUID) error) *GenericRepositoryWithDataModel_Delete_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// Find provides a mock function with given fields: ctx, _a1 -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) Find(ctx context.Context, _a1 specification.Specification) ([]TEntity, error) { - ret := _m.Called(ctx, _a1) - - var r0 []TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, specification.Specification) ([]TEntity, error)); ok { - return rf(ctx, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, specification.Specification) []TEntity); ok { - r0 = rf(ctx, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]TEntity) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, specification.Specification) error); ok { - r1 = rf(ctx, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepositoryWithDataModel_Find_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Find' -type GenericRepositoryWithDataModel_Find_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// Find is a helper method to define mock.On call -// - ctx context.Context -// - _a1 specification.Specification -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) Find(ctx interface{}, _a1 interface{}) *GenericRepositoryWithDataModel_Find_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_Find_Call[TDataModel, TEntity]{Call: _e.mock.On("Find", ctx, _a1)} -} - -func (_c *GenericRepositoryWithDataModel_Find_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, _a1 specification.Specification)) *GenericRepositoryWithDataModel_Find_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(specification.Specification)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Find_Call[TDataModel, TEntity]) Return(_a0 []TEntity, _a1 error) *GenericRepositoryWithDataModel_Find_Call[TDataModel, TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Find_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, specification.Specification) ([]TEntity, error)) *GenericRepositoryWithDataModel_Find_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// FirstOrDefault provides a mock function with given fields: ctx, filters -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) FirstOrDefault(ctx context.Context, filters map[string]interface{}) (TEntity, error) { - ret := _m.Called(ctx, filters) - - var r0 TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) (TEntity, error)); ok { - return rf(ctx, filters) - } - if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) TEntity); ok { - r0 = rf(ctx, filters) - } else { - r0 = ret.Get(0).(TEntity) - } - - if rf, ok := ret.Get(1).(func(context.Context, map[string]interface{}) error); ok { - r1 = rf(ctx, filters) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepositoryWithDataModel_FirstOrDefault_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirstOrDefault' -type GenericRepositoryWithDataModel_FirstOrDefault_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// FirstOrDefault is a helper method to define mock.On call -// - ctx context.Context -// - filters map[string]interface{} -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) FirstOrDefault(ctx interface{}, filters interface{}) *GenericRepositoryWithDataModel_FirstOrDefault_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_FirstOrDefault_Call[TDataModel, TEntity]{Call: _e.mock.On("FirstOrDefault", ctx, filters)} -} - -func (_c *GenericRepositoryWithDataModel_FirstOrDefault_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, filters map[string]interface{})) *GenericRepositoryWithDataModel_FirstOrDefault_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(map[string]interface{})) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_FirstOrDefault_Call[TDataModel, TEntity]) Return(_a0 TEntity, _a1 error) *GenericRepositoryWithDataModel_FirstOrDefault_Call[TDataModel, TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepositoryWithDataModel_FirstOrDefault_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, map[string]interface{}) (TEntity, error)) *GenericRepositoryWithDataModel_FirstOrDefault_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// GetAll provides a mock function with given fields: ctx, listQuery -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) GetAll(ctx context.Context, listQuery *utils.ListQuery) (*utils.ListResult[TEntity], error) { - ret := _m.Called(ctx, listQuery) - - var r0 *utils.ListResult[TEntity] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) (*utils.ListResult[TEntity], error)); ok { - return rf(ctx, listQuery) - } - if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) *utils.ListResult[TEntity]); ok { - r0 = rf(ctx, listQuery) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*utils.ListResult[TEntity]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *utils.ListQuery) error); ok { - r1 = rf(ctx, listQuery) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepositoryWithDataModel_GetAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAll' -type GenericRepositoryWithDataModel_GetAll_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// GetAll is a helper method to define mock.On call -// - ctx context.Context -// - listQuery *utils.ListQuery -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) GetAll(ctx interface{}, listQuery interface{}) *GenericRepositoryWithDataModel_GetAll_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_GetAll_Call[TDataModel, TEntity]{Call: _e.mock.On("GetAll", ctx, listQuery)} -} - -func (_c *GenericRepositoryWithDataModel_GetAll_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, listQuery *utils.ListQuery)) *GenericRepositoryWithDataModel_GetAll_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*utils.ListQuery)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_GetAll_Call[TDataModel, TEntity]) Return(_a0 *utils.ListResult[TEntity], _a1 error) *GenericRepositoryWithDataModel_GetAll_Call[TDataModel, TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepositoryWithDataModel_GetAll_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, *utils.ListQuery) (*utils.ListResult[TEntity], error)) *GenericRepositoryWithDataModel_GetAll_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// GetByFilter provides a mock function with given fields: ctx, filters -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) GetByFilter(ctx context.Context, filters map[string]interface{}) ([]TEntity, error) { - ret := _m.Called(ctx, filters) - - var r0 []TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) ([]TEntity, error)); ok { - return rf(ctx, filters) - } - if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}) []TEntity); ok { - r0 = rf(ctx, filters) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]TEntity) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, map[string]interface{}) error); ok { - r1 = rf(ctx, filters) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepositoryWithDataModel_GetByFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByFilter' -type GenericRepositoryWithDataModel_GetByFilter_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// GetByFilter is a helper method to define mock.On call -// - ctx context.Context -// - filters map[string]interface{} -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) GetByFilter(ctx interface{}, filters interface{}) *GenericRepositoryWithDataModel_GetByFilter_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_GetByFilter_Call[TDataModel, TEntity]{Call: _e.mock.On("GetByFilter", ctx, filters)} -} - -func (_c *GenericRepositoryWithDataModel_GetByFilter_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, filters map[string]interface{})) *GenericRepositoryWithDataModel_GetByFilter_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(map[string]interface{})) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_GetByFilter_Call[TDataModel, TEntity]) Return(_a0 []TEntity, _a1 error) *GenericRepositoryWithDataModel_GetByFilter_Call[TDataModel, TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepositoryWithDataModel_GetByFilter_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, map[string]interface{}) ([]TEntity, error)) *GenericRepositoryWithDataModel_GetByFilter_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// GetByFuncFilter provides a mock function with given fields: ctx, filterFunc -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) GetByFuncFilter(ctx context.Context, filterFunc func(TEntity) bool) ([]TEntity, error) { - ret := _m.Called(ctx, filterFunc) - - var r0 []TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, func(TEntity) bool) ([]TEntity, error)); ok { - return rf(ctx, filterFunc) - } - if rf, ok := ret.Get(0).(func(context.Context, func(TEntity) bool) []TEntity); ok { - r0 = rf(ctx, filterFunc) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]TEntity) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, func(TEntity) bool) error); ok { - r1 = rf(ctx, filterFunc) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepositoryWithDataModel_GetByFuncFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByFuncFilter' -type GenericRepositoryWithDataModel_GetByFuncFilter_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// GetByFuncFilter is a helper method to define mock.On call -// - ctx context.Context -// - filterFunc func(TEntity) bool -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) GetByFuncFilter(ctx interface{}, filterFunc interface{}) *GenericRepositoryWithDataModel_GetByFuncFilter_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_GetByFuncFilter_Call[TDataModel, TEntity]{Call: _e.mock.On("GetByFuncFilter", ctx, filterFunc)} -} - -func (_c *GenericRepositoryWithDataModel_GetByFuncFilter_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, filterFunc func(TEntity) bool)) *GenericRepositoryWithDataModel_GetByFuncFilter_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(func(TEntity) bool)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_GetByFuncFilter_Call[TDataModel, TEntity]) Return(_a0 []TEntity, _a1 error) *GenericRepositoryWithDataModel_GetByFuncFilter_Call[TDataModel, TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepositoryWithDataModel_GetByFuncFilter_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, func(TEntity) bool) ([]TEntity, error)) *GenericRepositoryWithDataModel_GetByFuncFilter_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// GetById provides a mock function with given fields: ctx, id -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) GetById(ctx context.Context, id uuid.UUID) (TEntity, error) { - ret := _m.Called(ctx, id) - - var r0 TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (TEntity, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) TEntity); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(TEntity) - } - - if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepositoryWithDataModel_GetById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetById' -type GenericRepositoryWithDataModel_GetById_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// GetById is a helper method to define mock.On call -// - ctx context.Context -// - id uuid.UUID -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) GetById(ctx interface{}, id interface{}) *GenericRepositoryWithDataModel_GetById_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_GetById_Call[TDataModel, TEntity]{Call: _e.mock.On("GetById", ctx, id)} -} - -func (_c *GenericRepositoryWithDataModel_GetById_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, id uuid.UUID)) *GenericRepositoryWithDataModel_GetById_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uuid.UUID)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_GetById_Call[TDataModel, TEntity]) Return(_a0 TEntity, _a1 error) *GenericRepositoryWithDataModel_GetById_Call[TDataModel, TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepositoryWithDataModel_GetById_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, uuid.UUID) (TEntity, error)) *GenericRepositoryWithDataModel_GetById_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// Search provides a mock function with given fields: ctx, searchTerm, listQuery -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) Search(ctx context.Context, searchTerm string, listQuery *utils.ListQuery) (*utils.ListResult[TEntity], error) { - ret := _m.Called(ctx, searchTerm, listQuery) - - var r0 *utils.ListResult[TEntity] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) (*utils.ListResult[TEntity], error)); ok { - return rf(ctx, searchTerm, listQuery) - } - if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) *utils.ListResult[TEntity]); ok { - r0 = rf(ctx, searchTerm, listQuery) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*utils.ListResult[TEntity]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, *utils.ListQuery) error); ok { - r1 = rf(ctx, searchTerm, listQuery) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepositoryWithDataModel_Search_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Search' -type GenericRepositoryWithDataModel_Search_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// Search is a helper method to define mock.On call -// - ctx context.Context -// - searchTerm string -// - listQuery *utils.ListQuery -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) Search(ctx interface{}, searchTerm interface{}, listQuery interface{}) *GenericRepositoryWithDataModel_Search_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_Search_Call[TDataModel, TEntity]{Call: _e.mock.On("Search", ctx, searchTerm, listQuery)} -} - -func (_c *GenericRepositoryWithDataModel_Search_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, searchTerm string, listQuery *utils.ListQuery)) *GenericRepositoryWithDataModel_Search_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(*utils.ListQuery)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Search_Call[TDataModel, TEntity]) Return(_a0 *utils.ListResult[TEntity], _a1 error) *GenericRepositoryWithDataModel_Search_Call[TDataModel, TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Search_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, string, *utils.ListQuery) (*utils.ListResult[TEntity], error)) *GenericRepositoryWithDataModel_Search_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// SkipTake provides a mock function with given fields: ctx, skip, take -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) SkipTake(ctx context.Context, skip int, take int) ([]TEntity, error) { - ret := _m.Called(ctx, skip, take) - - var r0 []TEntity - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, int, int) ([]TEntity, error)); ok { - return rf(ctx, skip, take) - } - if rf, ok := ret.Get(0).(func(context.Context, int, int) []TEntity); ok { - r0 = rf(ctx, skip, take) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]TEntity) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, int, int) error); ok { - r1 = rf(ctx, skip, take) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GenericRepositoryWithDataModel_SkipTake_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SkipTake' -type GenericRepositoryWithDataModel_SkipTake_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// SkipTake is a helper method to define mock.On call -// - ctx context.Context -// - skip int -// - take int -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) SkipTake(ctx interface{}, skip interface{}, take interface{}) *GenericRepositoryWithDataModel_SkipTake_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_SkipTake_Call[TDataModel, TEntity]{Call: _e.mock.On("SkipTake", ctx, skip, take)} -} - -func (_c *GenericRepositoryWithDataModel_SkipTake_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, skip int, take int)) *GenericRepositoryWithDataModel_SkipTake_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(int), args[2].(int)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_SkipTake_Call[TDataModel, TEntity]) Return(_a0 []TEntity, _a1 error) *GenericRepositoryWithDataModel_SkipTake_Call[TDataModel, TEntity] { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *GenericRepositoryWithDataModel_SkipTake_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, int, int) ([]TEntity, error)) *GenericRepositoryWithDataModel_SkipTake_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with given fields: ctx, entity -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) Update(ctx context.Context, entity TEntity) error { - ret := _m.Called(ctx, entity) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, TEntity) error); ok { - r0 = rf(ctx, entity) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepositoryWithDataModel_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type GenericRepositoryWithDataModel_Update_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// Update is a helper method to define mock.On call -// - ctx context.Context -// - entity TEntity -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) Update(ctx interface{}, entity interface{}) *GenericRepositoryWithDataModel_Update_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_Update_Call[TDataModel, TEntity]{Call: _e.mock.On("Update", ctx, entity)} -} - -func (_c *GenericRepositoryWithDataModel_Update_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, entity TEntity)) *GenericRepositoryWithDataModel_Update_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(TEntity)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Update_Call[TDataModel, TEntity]) Return(_a0 error) *GenericRepositoryWithDataModel_Update_Call[TDataModel, TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepositoryWithDataModel_Update_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, TEntity) error) *GenericRepositoryWithDataModel_Update_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// UpdateAll provides a mock function with given fields: ctx, entities -func (_m *GenericRepositoryWithDataModel[TDataModel, TEntity]) UpdateAll(ctx context.Context, entities []TEntity) error { - ret := _m.Called(ctx, entities) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []TEntity) error); ok { - r0 = rf(ctx, entities) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// GenericRepositoryWithDataModel_UpdateAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateAll' -type GenericRepositoryWithDataModel_UpdateAll_Call[TDataModel interface{}, TEntity interface{}] struct { - *mock.Call -} - -// UpdateAll is a helper method to define mock.On call -// - ctx context.Context -// - entities []TEntity -func (_e *GenericRepositoryWithDataModel_Expecter[TDataModel, TEntity]) UpdateAll(ctx interface{}, entities interface{}) *GenericRepositoryWithDataModel_UpdateAll_Call[TDataModel, TEntity] { - return &GenericRepositoryWithDataModel_UpdateAll_Call[TDataModel, TEntity]{Call: _e.mock.On("UpdateAll", ctx, entities)} -} - -func (_c *GenericRepositoryWithDataModel_UpdateAll_Call[TDataModel, TEntity]) Run(run func(ctx context.Context, entities []TEntity)) *GenericRepositoryWithDataModel_UpdateAll_Call[TDataModel, TEntity] { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]TEntity)) - }) - return _c -} - -func (_c *GenericRepositoryWithDataModel_UpdateAll_Call[TDataModel, TEntity]) Return(_a0 error) *GenericRepositoryWithDataModel_UpdateAll_Call[TDataModel, TEntity] { - _c.Call.Return(_a0) - return _c -} - -func (_c *GenericRepositoryWithDataModel_UpdateAll_Call[TDataModel, TEntity]) RunAndReturn(run func(context.Context, []TEntity) error) *GenericRepositoryWithDataModel_UpdateAll_Call[TDataModel, TEntity] { - _c.Call.Return(run) - return _c -} - -// NewGenericRepositoryWithDataModel creates a new instance of GenericRepositoryWithDataModel. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewGenericRepositoryWithDataModel[TDataModel interface{}, TEntity interface{}](t interface { - mock.TestingT - Cleanup(func()) -}) *GenericRepositoryWithDataModel[TDataModel, TEntity] { - mock := &GenericRepositoryWithDataModel[TDataModel, TEntity]{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/core/mocks/IAggregateRoot.go b/internal/pkg/core/mocks/IAggregateRoot.go deleted file mode 100644 index d4aff8cb..00000000 --- a/internal/pkg/core/mocks/IAggregateRoot.go +++ /dev/null @@ -1,503 +0,0 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. - -package mocks - -import ( - domain "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - mock "github.com/stretchr/testify/mock" - - time "time" - - uuid "github.com/satori/go.uuid" -) - -// IAggregateRoot is an autogenerated mock type for the IAggregateRoot type -type IAggregateRoot struct { - mock.Mock -} - -type IAggregateRoot_Expecter struct { - mock *mock.Mock -} - -func (_m *IAggregateRoot) EXPECT() *IAggregateRoot_Expecter { - return &IAggregateRoot_Expecter{mock: &_m.Mock} -} - -// AddDomainEvents provides a mock function with given fields: event -func (_m *IAggregateRoot) AddDomainEvents(event domain.IDomainEvent) error { - ret := _m.Called(event) - - var r0 error - if rf, ok := ret.Get(0).(func(domain.IDomainEvent) error); ok { - r0 = rf(event) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// IAggregateRoot_AddDomainEvents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddDomainEvents' -type IAggregateRoot_AddDomainEvents_Call struct { - *mock.Call -} - -// AddDomainEvents is a helper method to define mock.On call -// - event domain.IDomainEvent -func (_e *IAggregateRoot_Expecter) AddDomainEvents(event interface{}) *IAggregateRoot_AddDomainEvents_Call { - return &IAggregateRoot_AddDomainEvents_Call{Call: _e.mock.On("AddDomainEvents", event)} -} - -func (_c *IAggregateRoot_AddDomainEvents_Call) Run(run func(event domain.IDomainEvent)) *IAggregateRoot_AddDomainEvents_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(domain.IDomainEvent)) - }) - return _c -} - -func (_c *IAggregateRoot_AddDomainEvents_Call) Return(_a0 error) *IAggregateRoot_AddDomainEvents_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IAggregateRoot_AddDomainEvents_Call) RunAndReturn(run func(domain.IDomainEvent) error) *IAggregateRoot_AddDomainEvents_Call { - _c.Call.Return(run) - return _c -} - -// CreatedAt provides a mock function with given fields: -func (_m *IAggregateRoot) CreatedAt() time.Time { - ret := _m.Called() - - var r0 time.Time - if rf, ok := ret.Get(0).(func() time.Time); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(time.Time) - } - - return r0 -} - -// IAggregateRoot_CreatedAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatedAt' -type IAggregateRoot_CreatedAt_Call struct { - *mock.Call -} - -// CreatedAt is a helper method to define mock.On call -func (_e *IAggregateRoot_Expecter) CreatedAt() *IAggregateRoot_CreatedAt_Call { - return &IAggregateRoot_CreatedAt_Call{Call: _e.mock.On("CreatedAt")} -} - -func (_c *IAggregateRoot_CreatedAt_Call) Run(run func()) *IAggregateRoot_CreatedAt_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IAggregateRoot_CreatedAt_Call) Return(_a0 time.Time) *IAggregateRoot_CreatedAt_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IAggregateRoot_CreatedAt_Call) RunAndReturn(run func() time.Time) *IAggregateRoot_CreatedAt_Call { - _c.Call.Return(run) - return _c -} - -// GetUncommittedEvents provides a mock function with given fields: -func (_m *IAggregateRoot) GetUncommittedEvents() []domain.IDomainEvent { - ret := _m.Called() - - var r0 []domain.IDomainEvent - if rf, ok := ret.Get(0).(func() []domain.IDomainEvent); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]domain.IDomainEvent) - } - } - - return r0 -} - -// IAggregateRoot_GetUncommittedEvents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUncommittedEvents' -type IAggregateRoot_GetUncommittedEvents_Call struct { - *mock.Call -} - -// GetUncommittedEvents is a helper method to define mock.On call -func (_e *IAggregateRoot_Expecter) GetUncommittedEvents() *IAggregateRoot_GetUncommittedEvents_Call { - return &IAggregateRoot_GetUncommittedEvents_Call{Call: _e.mock.On("GetUncommittedEvents")} -} - -func (_c *IAggregateRoot_GetUncommittedEvents_Call) Run(run func()) *IAggregateRoot_GetUncommittedEvents_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IAggregateRoot_GetUncommittedEvents_Call) Return(_a0 []domain.IDomainEvent) *IAggregateRoot_GetUncommittedEvents_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IAggregateRoot_GetUncommittedEvents_Call) RunAndReturn(run func() []domain.IDomainEvent) *IAggregateRoot_GetUncommittedEvents_Call { - _c.Call.Return(run) - return _c -} - -// HasUncommittedEvents provides a mock function with given fields: -func (_m *IAggregateRoot) HasUncommittedEvents() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// IAggregateRoot_HasUncommittedEvents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasUncommittedEvents' -type IAggregateRoot_HasUncommittedEvents_Call struct { - *mock.Call -} - -// HasUncommittedEvents is a helper method to define mock.On call -func (_e *IAggregateRoot_Expecter) HasUncommittedEvents() *IAggregateRoot_HasUncommittedEvents_Call { - return &IAggregateRoot_HasUncommittedEvents_Call{Call: _e.mock.On("HasUncommittedEvents")} -} - -func (_c *IAggregateRoot_HasUncommittedEvents_Call) Run(run func()) *IAggregateRoot_HasUncommittedEvents_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IAggregateRoot_HasUncommittedEvents_Call) Return(_a0 bool) *IAggregateRoot_HasUncommittedEvents_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IAggregateRoot_HasUncommittedEvents_Call) RunAndReturn(run func() bool) *IAggregateRoot_HasUncommittedEvents_Call { - _c.Call.Return(run) - return _c -} - -// Id provides a mock function with given fields: -func (_m *IAggregateRoot) Id() uuid.UUID { - ret := _m.Called() - - var r0 uuid.UUID - if rf, ok := ret.Get(0).(func() uuid.UUID); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(uuid.UUID) - } - } - - return r0 -} - -// IAggregateRoot_Id_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Id' -type IAggregateRoot_Id_Call struct { - *mock.Call -} - -// Id is a helper method to define mock.On call -func (_e *IAggregateRoot_Expecter) Id() *IAggregateRoot_Id_Call { - return &IAggregateRoot_Id_Call{Call: _e.mock.On("Id")} -} - -func (_c *IAggregateRoot_Id_Call) Run(run func()) *IAggregateRoot_Id_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IAggregateRoot_Id_Call) Return(_a0 uuid.UUID) *IAggregateRoot_Id_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IAggregateRoot_Id_Call) RunAndReturn(run func() uuid.UUID) *IAggregateRoot_Id_Call { - _c.Call.Return(run) - return _c -} - -// MarkUncommittedEventAsCommitted provides a mock function with given fields: -func (_m *IAggregateRoot) MarkUncommittedEventAsCommitted() { - _m.Called() -} - -// IAggregateRoot_MarkUncommittedEventAsCommitted_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkUncommittedEventAsCommitted' -type IAggregateRoot_MarkUncommittedEventAsCommitted_Call struct { - *mock.Call -} - -// MarkUncommittedEventAsCommitted is a helper method to define mock.On call -func (_e *IAggregateRoot_Expecter) MarkUncommittedEventAsCommitted() *IAggregateRoot_MarkUncommittedEventAsCommitted_Call { - return &IAggregateRoot_MarkUncommittedEventAsCommitted_Call{Call: _e.mock.On("MarkUncommittedEventAsCommitted")} -} - -func (_c *IAggregateRoot_MarkUncommittedEventAsCommitted_Call) Run(run func()) *IAggregateRoot_MarkUncommittedEventAsCommitted_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IAggregateRoot_MarkUncommittedEventAsCommitted_Call) Return() *IAggregateRoot_MarkUncommittedEventAsCommitted_Call { - _c.Call.Return() - return _c -} - -func (_c *IAggregateRoot_MarkUncommittedEventAsCommitted_Call) RunAndReturn(run func()) *IAggregateRoot_MarkUncommittedEventAsCommitted_Call { - _c.Call.Return(run) - return _c -} - -// OriginalVersion provides a mock function with given fields: -func (_m *IAggregateRoot) OriginalVersion() int64 { - ret := _m.Called() - - var r0 int64 - if rf, ok := ret.Get(0).(func() int64); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(int64) - } - - return r0 -} - -// IAggregateRoot_OriginalVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OriginalVersion' -type IAggregateRoot_OriginalVersion_Call struct { - *mock.Call -} - -// OriginalVersion is a helper method to define mock.On call -func (_e *IAggregateRoot_Expecter) OriginalVersion() *IAggregateRoot_OriginalVersion_Call { - return &IAggregateRoot_OriginalVersion_Call{Call: _e.mock.On("OriginalVersion")} -} - -func (_c *IAggregateRoot_OriginalVersion_Call) Run(run func()) *IAggregateRoot_OriginalVersion_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IAggregateRoot_OriginalVersion_Call) Return(_a0 int64) *IAggregateRoot_OriginalVersion_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IAggregateRoot_OriginalVersion_Call) RunAndReturn(run func() int64) *IAggregateRoot_OriginalVersion_Call { - _c.Call.Return(run) - return _c -} - -// SetEntityType provides a mock function with given fields: entityType -func (_m *IAggregateRoot) SetEntityType(entityType string) { - _m.Called(entityType) -} - -// IAggregateRoot_SetEntityType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetEntityType' -type IAggregateRoot_SetEntityType_Call struct { - *mock.Call -} - -// SetEntityType is a helper method to define mock.On call -// - entityType string -func (_e *IAggregateRoot_Expecter) SetEntityType(entityType interface{}) *IAggregateRoot_SetEntityType_Call { - return &IAggregateRoot_SetEntityType_Call{Call: _e.mock.On("SetEntityType", entityType)} -} - -func (_c *IAggregateRoot_SetEntityType_Call) Run(run func(entityType string)) *IAggregateRoot_SetEntityType_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c -} - -func (_c *IAggregateRoot_SetEntityType_Call) Return() *IAggregateRoot_SetEntityType_Call { - _c.Call.Return() - return _c -} - -func (_c *IAggregateRoot_SetEntityType_Call) RunAndReturn(run func(string)) *IAggregateRoot_SetEntityType_Call { - _c.Call.Return(run) - return _c -} - -// SetId provides a mock function with given fields: id -func (_m *IAggregateRoot) SetId(id uuid.UUID) { - _m.Called(id) -} - -// IAggregateRoot_SetId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetId' -type IAggregateRoot_SetId_Call struct { - *mock.Call -} - -// SetId is a helper method to define mock.On call -// - id uuid.UUID -func (_e *IAggregateRoot_Expecter) SetId(id interface{}) *IAggregateRoot_SetId_Call { - return &IAggregateRoot_SetId_Call{Call: _e.mock.On("SetId", id)} -} - -func (_c *IAggregateRoot_SetId_Call) Run(run func(id uuid.UUID)) *IAggregateRoot_SetId_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(uuid.UUID)) - }) - return _c -} - -func (_c *IAggregateRoot_SetId_Call) Return() *IAggregateRoot_SetId_Call { - _c.Call.Return() - return _c -} - -func (_c *IAggregateRoot_SetId_Call) RunAndReturn(run func(uuid.UUID)) *IAggregateRoot_SetId_Call { - _c.Call.Return(run) - return _c -} - -// SetUpdatedAt provides a mock function with given fields: updatedAt -func (_m *IAggregateRoot) SetUpdatedAt(updatedAt time.Time) { - _m.Called(updatedAt) -} - -// IAggregateRoot_SetUpdatedAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUpdatedAt' -type IAggregateRoot_SetUpdatedAt_Call struct { - *mock.Call -} - -// SetUpdatedAt is a helper method to define mock.On call -// - updatedAt time.Time -func (_e *IAggregateRoot_Expecter) SetUpdatedAt(updatedAt interface{}) *IAggregateRoot_SetUpdatedAt_Call { - return &IAggregateRoot_SetUpdatedAt_Call{Call: _e.mock.On("SetUpdatedAt", updatedAt)} -} - -func (_c *IAggregateRoot_SetUpdatedAt_Call) Run(run func(updatedAt time.Time)) *IAggregateRoot_SetUpdatedAt_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(time.Time)) - }) - return _c -} - -func (_c *IAggregateRoot_SetUpdatedAt_Call) Return() *IAggregateRoot_SetUpdatedAt_Call { - _c.Call.Return() - return _c -} - -func (_c *IAggregateRoot_SetUpdatedAt_Call) RunAndReturn(run func(time.Time)) *IAggregateRoot_SetUpdatedAt_Call { - _c.Call.Return(run) - return _c -} - -// String provides a mock function with given fields: -func (_m *IAggregateRoot) String() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// IAggregateRoot_String_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'String' -type IAggregateRoot_String_Call struct { - *mock.Call -} - -// String is a helper method to define mock.On call -func (_e *IAggregateRoot_Expecter) String() *IAggregateRoot_String_Call { - return &IAggregateRoot_String_Call{Call: _e.mock.On("String")} -} - -func (_c *IAggregateRoot_String_Call) Run(run func()) *IAggregateRoot_String_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IAggregateRoot_String_Call) Return(_a0 string) *IAggregateRoot_String_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IAggregateRoot_String_Call) RunAndReturn(run func() string) *IAggregateRoot_String_Call { - _c.Call.Return(run) - return _c -} - -// UpdatedAt provides a mock function with given fields: -func (_m *IAggregateRoot) UpdatedAt() time.Time { - ret := _m.Called() - - var r0 time.Time - if rf, ok := ret.Get(0).(func() time.Time); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(time.Time) - } - - return r0 -} - -// IAggregateRoot_UpdatedAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdatedAt' -type IAggregateRoot_UpdatedAt_Call struct { - *mock.Call -} - -// UpdatedAt is a helper method to define mock.On call -func (_e *IAggregateRoot_Expecter) UpdatedAt() *IAggregateRoot_UpdatedAt_Call { - return &IAggregateRoot_UpdatedAt_Call{Call: _e.mock.On("UpdatedAt")} -} - -func (_c *IAggregateRoot_UpdatedAt_Call) Run(run func()) *IAggregateRoot_UpdatedAt_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IAggregateRoot_UpdatedAt_Call) Return(_a0 time.Time) *IAggregateRoot_UpdatedAt_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IAggregateRoot_UpdatedAt_Call) RunAndReturn(run func() time.Time) *IAggregateRoot_UpdatedAt_Call { - _c.Call.Return(run) - return _c -} - -// NewIAggregateRoot creates a new instance of IAggregateRoot. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewIAggregateRoot(t interface { - mock.TestingT - Cleanup(func()) -}) *IAggregateRoot { - mock := &IAggregateRoot{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/core/mocks/IDomainEvent.go b/internal/pkg/core/mocks/IDomainEvent.go deleted file mode 100644 index 72c220e3..00000000 --- a/internal/pkg/core/mocks/IDomainEvent.go +++ /dev/null @@ -1,293 +0,0 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. - -package mocks - -import ( - domain "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - mock "github.com/stretchr/testify/mock" - - time "time" - - uuid "github.com/satori/go.uuid" -) - -// IDomainEvent is an autogenerated mock type for the IDomainEvent type -type IDomainEvent struct { - mock.Mock -} - -type IDomainEvent_Expecter struct { - mock *mock.Mock -} - -func (_m *IDomainEvent) EXPECT() *IDomainEvent_Expecter { - return &IDomainEvent_Expecter{mock: &_m.Mock} -} - -// GetAggregateId provides a mock function with given fields: -func (_m *IDomainEvent) GetAggregateId() uuid.UUID { - ret := _m.Called() - - var r0 uuid.UUID - if rf, ok := ret.Get(0).(func() uuid.UUID); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(uuid.UUID) - } - } - - return r0 -} - -// IDomainEvent_GetAggregateId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAggregateId' -type IDomainEvent_GetAggregateId_Call struct { - *mock.Call -} - -// GetAggregateId is a helper method to define mock.On call -func (_e *IDomainEvent_Expecter) GetAggregateId() *IDomainEvent_GetAggregateId_Call { - return &IDomainEvent_GetAggregateId_Call{Call: _e.mock.On("GetAggregateId")} -} - -func (_c *IDomainEvent_GetAggregateId_Call) Run(run func()) *IDomainEvent_GetAggregateId_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IDomainEvent_GetAggregateId_Call) Return(_a0 uuid.UUID) *IDomainEvent_GetAggregateId_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IDomainEvent_GetAggregateId_Call) RunAndReturn(run func() uuid.UUID) *IDomainEvent_GetAggregateId_Call { - _c.Call.Return(run) - return _c -} - -// GetAggregateSequenceNumber provides a mock function with given fields: -func (_m *IDomainEvent) GetAggregateSequenceNumber() int64 { - ret := _m.Called() - - var r0 int64 - if rf, ok := ret.Get(0).(func() int64); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(int64) - } - - return r0 -} - -// IDomainEvent_GetAggregateSequenceNumber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAggregateSequenceNumber' -type IDomainEvent_GetAggregateSequenceNumber_Call struct { - *mock.Call -} - -// GetAggregateSequenceNumber is a helper method to define mock.On call -func (_e *IDomainEvent_Expecter) GetAggregateSequenceNumber() *IDomainEvent_GetAggregateSequenceNumber_Call { - return &IDomainEvent_GetAggregateSequenceNumber_Call{Call: _e.mock.On("GetAggregateSequenceNumber")} -} - -func (_c *IDomainEvent_GetAggregateSequenceNumber_Call) Run(run func()) *IDomainEvent_GetAggregateSequenceNumber_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IDomainEvent_GetAggregateSequenceNumber_Call) Return(_a0 int64) *IDomainEvent_GetAggregateSequenceNumber_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IDomainEvent_GetAggregateSequenceNumber_Call) RunAndReturn(run func() int64) *IDomainEvent_GetAggregateSequenceNumber_Call { - _c.Call.Return(run) - return _c -} - -// GetEventId provides a mock function with given fields: -func (_m *IDomainEvent) GetEventId() uuid.UUID { - ret := _m.Called() - - var r0 uuid.UUID - if rf, ok := ret.Get(0).(func() uuid.UUID); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(uuid.UUID) - } - } - - return r0 -} - -// IDomainEvent_GetEventId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEventId' -type IDomainEvent_GetEventId_Call struct { - *mock.Call -} - -// GetEventId is a helper method to define mock.On call -func (_e *IDomainEvent_Expecter) GetEventId() *IDomainEvent_GetEventId_Call { - return &IDomainEvent_GetEventId_Call{Call: _e.mock.On("GetEventId")} -} - -func (_c *IDomainEvent_GetEventId_Call) Run(run func()) *IDomainEvent_GetEventId_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IDomainEvent_GetEventId_Call) Return(_a0 uuid.UUID) *IDomainEvent_GetEventId_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IDomainEvent_GetEventId_Call) RunAndReturn(run func() uuid.UUID) *IDomainEvent_GetEventId_Call { - _c.Call.Return(run) - return _c -} - -// GetEventType provides a mock function with given fields: -func (_m *IDomainEvent) GetEventType() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// IDomainEvent_GetEventType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEventType' -type IDomainEvent_GetEventType_Call struct { - *mock.Call -} - -// GetEventType is a helper method to define mock.On call -func (_e *IDomainEvent_Expecter) GetEventType() *IDomainEvent_GetEventType_Call { - return &IDomainEvent_GetEventType_Call{Call: _e.mock.On("GetEventType")} -} - -func (_c *IDomainEvent_GetEventType_Call) Run(run func()) *IDomainEvent_GetEventType_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IDomainEvent_GetEventType_Call) Return(_a0 string) *IDomainEvent_GetEventType_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IDomainEvent_GetEventType_Call) RunAndReturn(run func() string) *IDomainEvent_GetEventType_Call { - _c.Call.Return(run) - return _c -} - -// GetOccurredOn provides a mock function with given fields: -func (_m *IDomainEvent) GetOccurredOn() time.Time { - ret := _m.Called() - - var r0 time.Time - if rf, ok := ret.Get(0).(func() time.Time); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(time.Time) - } - - return r0 -} - -// IDomainEvent_GetOccurredOn_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOccurredOn' -type IDomainEvent_GetOccurredOn_Call struct { - *mock.Call -} - -// GetOccurredOn is a helper method to define mock.On call -func (_e *IDomainEvent_Expecter) GetOccurredOn() *IDomainEvent_GetOccurredOn_Call { - return &IDomainEvent_GetOccurredOn_Call{Call: _e.mock.On("GetOccurredOn")} -} - -func (_c *IDomainEvent_GetOccurredOn_Call) Run(run func()) *IDomainEvent_GetOccurredOn_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IDomainEvent_GetOccurredOn_Call) Return(_a0 time.Time) *IDomainEvent_GetOccurredOn_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IDomainEvent_GetOccurredOn_Call) RunAndReturn(run func() time.Time) *IDomainEvent_GetOccurredOn_Call { - _c.Call.Return(run) - return _c -} - -// WithAggregate provides a mock function with given fields: aggregateId, aggregateSequenceNumber -func (_m *IDomainEvent) WithAggregate(aggregateId uuid.UUID, aggregateSequenceNumber int64) *domain.DomainEvent { - ret := _m.Called(aggregateId, aggregateSequenceNumber) - - var r0 *domain.DomainEvent - if rf, ok := ret.Get(0).(func(uuid.UUID, int64) *domain.DomainEvent); ok { - r0 = rf(aggregateId, aggregateSequenceNumber) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*domain.DomainEvent) - } - } - - return r0 -} - -// IDomainEvent_WithAggregate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithAggregate' -type IDomainEvent_WithAggregate_Call struct { - *mock.Call -} - -// WithAggregate is a helper method to define mock.On call -// - aggregateId uuid.UUID -// - aggregateSequenceNumber int64 -func (_e *IDomainEvent_Expecter) WithAggregate(aggregateId interface{}, aggregateSequenceNumber interface{}) *IDomainEvent_WithAggregate_Call { - return &IDomainEvent_WithAggregate_Call{Call: _e.mock.On("WithAggregate", aggregateId, aggregateSequenceNumber)} -} - -func (_c *IDomainEvent_WithAggregate_Call) Run(run func(aggregateId uuid.UUID, aggregateSequenceNumber int64)) *IDomainEvent_WithAggregate_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(uuid.UUID), args[1].(int64)) - }) - return _c -} - -func (_c *IDomainEvent_WithAggregate_Call) Return(_a0 *domain.DomainEvent) *IDomainEvent_WithAggregate_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IDomainEvent_WithAggregate_Call) RunAndReturn(run func(uuid.UUID, int64) *domain.DomainEvent) *IDomainEvent_WithAggregate_Call { - _c.Call.Return(run) - return _c -} - -// NewIDomainEvent creates a new instance of IDomainEvent. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewIDomainEvent(t interface { - mock.TestingT - Cleanup(func()) -}) *IDomainEvent { - mock := &IDomainEvent{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/core/mocks/IEntity.go b/internal/pkg/core/mocks/IEntity.go deleted file mode 100644 index 9b1e0c3d..00000000 --- a/internal/pkg/core/mocks/IEntity.go +++ /dev/null @@ -1,262 +0,0 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. - -package mocks - -import ( - time "time" - - mock "github.com/stretchr/testify/mock" - - uuid "github.com/satori/go.uuid" -) - -// IEntity is an autogenerated mock type for the IEntity type -type IEntity struct { - mock.Mock -} - -type IEntity_Expecter struct { - mock *mock.Mock -} - -func (_m *IEntity) EXPECT() *IEntity_Expecter { - return &IEntity_Expecter{mock: &_m.Mock} -} - -// CreatedAt provides a mock function with given fields: -func (_m *IEntity) CreatedAt() time.Time { - ret := _m.Called() - - var r0 time.Time - if rf, ok := ret.Get(0).(func() time.Time); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(time.Time) - } - - return r0 -} - -// IEntity_CreatedAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatedAt' -type IEntity_CreatedAt_Call struct { - *mock.Call -} - -// CreatedAt is a helper method to define mock.On call -func (_e *IEntity_Expecter) CreatedAt() *IEntity_CreatedAt_Call { - return &IEntity_CreatedAt_Call{Call: _e.mock.On("CreatedAt")} -} - -func (_c *IEntity_CreatedAt_Call) Run(run func()) *IEntity_CreatedAt_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IEntity_CreatedAt_Call) Return(_a0 time.Time) *IEntity_CreatedAt_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IEntity_CreatedAt_Call) RunAndReturn(run func() time.Time) *IEntity_CreatedAt_Call { - _c.Call.Return(run) - return _c -} - -// Id provides a mock function with given fields: -func (_m *IEntity) Id() uuid.UUID { - ret := _m.Called() - - var r0 uuid.UUID - if rf, ok := ret.Get(0).(func() uuid.UUID); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(uuid.UUID) - } - } - - return r0 -} - -// IEntity_Id_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Id' -type IEntity_Id_Call struct { - *mock.Call -} - -// Id is a helper method to define mock.On call -func (_e *IEntity_Expecter) Id() *IEntity_Id_Call { - return &IEntity_Id_Call{Call: _e.mock.On("Id")} -} - -func (_c *IEntity_Id_Call) Run(run func()) *IEntity_Id_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IEntity_Id_Call) Return(_a0 uuid.UUID) *IEntity_Id_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IEntity_Id_Call) RunAndReturn(run func() uuid.UUID) *IEntity_Id_Call { - _c.Call.Return(run) - return _c -} - -// SetEntityType provides a mock function with given fields: entityType -func (_m *IEntity) SetEntityType(entityType string) { - _m.Called(entityType) -} - -// IEntity_SetEntityType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetEntityType' -type IEntity_SetEntityType_Call struct { - *mock.Call -} - -// SetEntityType is a helper method to define mock.On call -// - entityType string -func (_e *IEntity_Expecter) SetEntityType(entityType interface{}) *IEntity_SetEntityType_Call { - return &IEntity_SetEntityType_Call{Call: _e.mock.On("SetEntityType", entityType)} -} - -func (_c *IEntity_SetEntityType_Call) Run(run func(entityType string)) *IEntity_SetEntityType_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c -} - -func (_c *IEntity_SetEntityType_Call) Return() *IEntity_SetEntityType_Call { - _c.Call.Return() - return _c -} - -func (_c *IEntity_SetEntityType_Call) RunAndReturn(run func(string)) *IEntity_SetEntityType_Call { - _c.Call.Return(run) - return _c -} - -// SetId provides a mock function with given fields: id -func (_m *IEntity) SetId(id uuid.UUID) { - _m.Called(id) -} - -// IEntity_SetId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetId' -type IEntity_SetId_Call struct { - *mock.Call -} - -// SetId is a helper method to define mock.On call -// - id uuid.UUID -func (_e *IEntity_Expecter) SetId(id interface{}) *IEntity_SetId_Call { - return &IEntity_SetId_Call{Call: _e.mock.On("SetId", id)} -} - -func (_c *IEntity_SetId_Call) Run(run func(id uuid.UUID)) *IEntity_SetId_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(uuid.UUID)) - }) - return _c -} - -func (_c *IEntity_SetId_Call) Return() *IEntity_SetId_Call { - _c.Call.Return() - return _c -} - -func (_c *IEntity_SetId_Call) RunAndReturn(run func(uuid.UUID)) *IEntity_SetId_Call { - _c.Call.Return(run) - return _c -} - -// SetUpdatedAt provides a mock function with given fields: updatedAt -func (_m *IEntity) SetUpdatedAt(updatedAt time.Time) { - _m.Called(updatedAt) -} - -// IEntity_SetUpdatedAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUpdatedAt' -type IEntity_SetUpdatedAt_Call struct { - *mock.Call -} - -// SetUpdatedAt is a helper method to define mock.On call -// - updatedAt time.Time -func (_e *IEntity_Expecter) SetUpdatedAt(updatedAt interface{}) *IEntity_SetUpdatedAt_Call { - return &IEntity_SetUpdatedAt_Call{Call: _e.mock.On("SetUpdatedAt", updatedAt)} -} - -func (_c *IEntity_SetUpdatedAt_Call) Run(run func(updatedAt time.Time)) *IEntity_SetUpdatedAt_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(time.Time)) - }) - return _c -} - -func (_c *IEntity_SetUpdatedAt_Call) Return() *IEntity_SetUpdatedAt_Call { - _c.Call.Return() - return _c -} - -func (_c *IEntity_SetUpdatedAt_Call) RunAndReturn(run func(time.Time)) *IEntity_SetUpdatedAt_Call { - _c.Call.Return(run) - return _c -} - -// UpdatedAt provides a mock function with given fields: -func (_m *IEntity) UpdatedAt() time.Time { - ret := _m.Called() - - var r0 time.Time - if rf, ok := ret.Get(0).(func() time.Time); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(time.Time) - } - - return r0 -} - -// IEntity_UpdatedAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdatedAt' -type IEntity_UpdatedAt_Call struct { - *mock.Call -} - -// UpdatedAt is a helper method to define mock.On call -func (_e *IEntity_Expecter) UpdatedAt() *IEntity_UpdatedAt_Call { - return &IEntity_UpdatedAt_Call{Call: _e.mock.On("UpdatedAt")} -} - -func (_c *IEntity_UpdatedAt_Call) Run(run func()) *IEntity_UpdatedAt_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IEntity_UpdatedAt_Call) Return(_a0 time.Time) *IEntity_UpdatedAt_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IEntity_UpdatedAt_Call) RunAndReturn(run func() time.Time) *IEntity_UpdatedAt_Call { - _c.Call.Return(run) - return _c -} - -// NewIEntity creates a new instance of IEntity. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewIEntity(t interface { - mock.TestingT - Cleanup(func()) -}) *IEntity { - mock := &IEntity{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/core/mocks/IEvent.go b/internal/pkg/core/mocks/IEvent.go deleted file mode 100644 index 57d4e505..00000000 --- a/internal/pkg/core/mocks/IEvent.go +++ /dev/null @@ -1,163 +0,0 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. - -package mocks - -import ( - time "time" - - mock "github.com/stretchr/testify/mock" - - uuid "github.com/satori/go.uuid" -) - -// IEvent is an autogenerated mock type for the IEvent type -type IEvent struct { - mock.Mock -} - -type IEvent_Expecter struct { - mock *mock.Mock -} - -func (_m *IEvent) EXPECT() *IEvent_Expecter { - return &IEvent_Expecter{mock: &_m.Mock} -} - -// GetEventId provides a mock function with given fields: -func (_m *IEvent) GetEventId() uuid.UUID { - ret := _m.Called() - - var r0 uuid.UUID - if rf, ok := ret.Get(0).(func() uuid.UUID); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(uuid.UUID) - } - } - - return r0 -} - -// IEvent_GetEventId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEventId' -type IEvent_GetEventId_Call struct { - *mock.Call -} - -// GetEventId is a helper method to define mock.On call -func (_e *IEvent_Expecter) GetEventId() *IEvent_GetEventId_Call { - return &IEvent_GetEventId_Call{Call: _e.mock.On("GetEventId")} -} - -func (_c *IEvent_GetEventId_Call) Run(run func()) *IEvent_GetEventId_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IEvent_GetEventId_Call) Return(_a0 uuid.UUID) *IEvent_GetEventId_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IEvent_GetEventId_Call) RunAndReturn(run func() uuid.UUID) *IEvent_GetEventId_Call { - _c.Call.Return(run) - return _c -} - -// GetEventType provides a mock function with given fields: -func (_m *IEvent) GetEventType() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// IEvent_GetEventType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEventType' -type IEvent_GetEventType_Call struct { - *mock.Call -} - -// GetEventType is a helper method to define mock.On call -func (_e *IEvent_Expecter) GetEventType() *IEvent_GetEventType_Call { - return &IEvent_GetEventType_Call{Call: _e.mock.On("GetEventType")} -} - -func (_c *IEvent_GetEventType_Call) Run(run func()) *IEvent_GetEventType_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IEvent_GetEventType_Call) Return(_a0 string) *IEvent_GetEventType_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IEvent_GetEventType_Call) RunAndReturn(run func() string) *IEvent_GetEventType_Call { - _c.Call.Return(run) - return _c -} - -// GetOccurredOn provides a mock function with given fields: -func (_m *IEvent) GetOccurredOn() time.Time { - ret := _m.Called() - - var r0 time.Time - if rf, ok := ret.Get(0).(func() time.Time); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(time.Time) - } - - return r0 -} - -// IEvent_GetOccurredOn_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOccurredOn' -type IEvent_GetOccurredOn_Call struct { - *mock.Call -} - -// GetOccurredOn is a helper method to define mock.On call -func (_e *IEvent_Expecter) GetOccurredOn() *IEvent_GetOccurredOn_Call { - return &IEvent_GetOccurredOn_Call{Call: _e.mock.On("GetOccurredOn")} -} - -func (_c *IEvent_GetOccurredOn_Call) Run(run func()) *IEvent_GetOccurredOn_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *IEvent_GetOccurredOn_Call) Return(_a0 time.Time) *IEvent_GetOccurredOn_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IEvent_GetOccurredOn_Call) RunAndReturn(run func() time.Time) *IEvent_GetOccurredOn_Call { - _c.Call.Return(run) - return _c -} - -// NewIEvent creates a new instance of IEvent. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewIEvent(t interface { - mock.TestingT - Cleanup(func()) -}) *IEvent { - mock := &IEvent{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/core/mocks/Specification.go b/internal/pkg/core/mocks/Specification.go deleted file mode 100644 index e81d137b..00000000 --- a/internal/pkg/core/mocks/Specification.go +++ /dev/null @@ -1,116 +0,0 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Specification is an autogenerated mock type for the Specification type -type Specification struct { - mock.Mock -} - -type Specification_Expecter struct { - mock *mock.Mock -} - -func (_m *Specification) EXPECT() *Specification_Expecter { - return &Specification_Expecter{mock: &_m.Mock} -} - -// GetQuery provides a mock function with given fields: -func (_m *Specification) GetQuery() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Specification_GetQuery_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetQuery' -type Specification_GetQuery_Call struct { - *mock.Call -} - -// GetQuery is a helper method to define mock.On call -func (_e *Specification_Expecter) GetQuery() *Specification_GetQuery_Call { - return &Specification_GetQuery_Call{Call: _e.mock.On("GetQuery")} -} - -func (_c *Specification_GetQuery_Call) Run(run func()) *Specification_GetQuery_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Specification_GetQuery_Call) Return(_a0 string) *Specification_GetQuery_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Specification_GetQuery_Call) RunAndReturn(run func() string) *Specification_GetQuery_Call { - _c.Call.Return(run) - return _c -} - -// GetValues provides a mock function with given fields: -func (_m *Specification) GetValues() []interface{} { - ret := _m.Called() - - var r0 []interface{} - if rf, ok := ret.Get(0).(func() []interface{}); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]interface{}) - } - } - - return r0 -} - -// Specification_GetValues_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetValues' -type Specification_GetValues_Call struct { - *mock.Call -} - -// GetValues is a helper method to define mock.On call -func (_e *Specification_Expecter) GetValues() *Specification_GetValues_Call { - return &Specification_GetValues_Call{Call: _e.mock.On("GetValues")} -} - -func (_c *Specification_GetValues_Call) Run(run func()) *Specification_GetValues_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Specification_GetValues_Call) Return(_a0 []interface{}) *Specification_GetValues_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Specification_GetValues_Call) RunAndReturn(run func() []interface{}) *Specification_GetValues_Call { - _c.Call.Return(run) - return _c -} - -// NewSpecification creates a new instance of Specification. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewSpecification(t interface { - mock.TestingT - Cleanup(func()) -}) *Specification { - mock := &Specification{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/core/serializer/event_serializer.go b/internal/pkg/core/serializer/event_serializer.go index 3a4995c0..f1200a5c 100644 --- a/internal/pkg/core/serializer/event_serializer.go +++ b/internal/pkg/core/serializer/event_serializer.go @@ -3,152 +3,15 @@ package serializer import ( "reflect" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/events" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" - - "emperror.dev/errors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" ) type EventSerializer interface { - Serialize(event interface{}) (*EventSerializationResult, error) - Deserialize(data []byte, eventType string, contentType string) (interface{}, error) - DeserializeType(data []byte, eventType reflect.Type, contentType string) (interface{}, error) - DeserializeMessage(data []byte, eventType string, contentType string) (interface{}, error) - DeserializeEvent(data []byte, eventType string, contentType string) (interface{}, error) + Serialize(event domain.IDomainEvent) (*EventSerializationResult, error) + SerializeObject(event interface{}) (*EventSerializationResult, error) + Deserialize(data []byte, eventType string, contentType string) (domain.IDomainEvent, error) + DeserializeObject(data []byte, eventType string, contentType string) (interface{}, error) + DeserializeType(data []byte, eventType reflect.Type, contentType string) (domain.IDomainEvent, error) ContentType() string Serializer() Serializer } - -type EventSerializationResult struct { - Data []byte - ContentType string -} - -type DefaultEventSerializer struct { - serializer Serializer -} - -func NewDefaultEventSerializer(serializer Serializer) EventSerializer { - return &DefaultEventSerializer{serializer: serializer} -} - -func (s *DefaultEventSerializer) Serializer() Serializer { - return s.serializer -} - -func (s *DefaultEventSerializer) Serialize( - event interface{}, -) (*EventSerializationResult, error) { - if event == nil { - return &EventSerializationResult{Data: nil, ContentType: s.ContentType()}, nil - } - - // here we just get type name instead of full type name - eventType := typeMapper.GetTypeName(event) - - data, err := s.serializer.Marshal(event) - if err != nil { - return nil, errors.WrapIff(err, "event.GetJsonData type: %s", eventType) - } - - result := &EventSerializationResult{Data: data, ContentType: s.ContentType()} - - return result, nil -} - -func (s *DefaultEventSerializer) Deserialize( - data []byte, - eventType string, - contentType string, -) (interface{}, error) { - if data == nil { - return nil, nil - } - - targetEventPointer := typeMapper.InstanceByTypeName(eventType) - - if contentType != s.ContentType() { - return nil, errors.Errorf("contentType: %s is not supported", contentType) - } - - if err := s.serializer.Unmarshal(data, targetEventPointer); err != nil { - return nil, errors.WrapIff(err, "event.GetJsonData type: %s", eventType) - } - - return targetEventPointer, nil -} - -func (s *DefaultEventSerializer) DeserializeType( - data []byte, - eventType reflect.Type, - contentType string, -) (interface{}, error) { - if data == nil { - return nil, nil - } - - targetEventPointer := typeMapper.InstanceByType(eventType) - - if contentType != s.ContentType() { - return nil, errors.Errorf("contentType: %s is not supported", contentType) - } - - if err := s.serializer.Unmarshal(data, targetEventPointer); err != nil { - return nil, errors.WrapIff(err, "event.GetJsonData type: %s", eventType) - } - - return targetEventPointer, nil -} - -func (s *DefaultEventSerializer) DeserializeMessage( - data []byte, - eventType string, - contentType string, -) (interface{}, error) { - if data == nil { - return nil, nil - } - - targetEventPointer := typeMapper.InstanceByTypeNameAndImplementedInterface[types.IMessage]( - eventType, - ) - - if contentType != s.ContentType() { - return nil, errors.Errorf("contentType: %s is not supported", contentType) - } - - if err := s.serializer.Unmarshal(data, targetEventPointer); err != nil { - return nil, errors.WrapIff(err, "event.GetJsonData type: %s", eventType) - } - - return targetEventPointer, nil -} - -func (s *DefaultEventSerializer) DeserializeEvent( - data []byte, - eventType string, - contentType string, -) (interface{}, error) { - if data == nil { - return nil, nil - } - - targetEventPointer := typeMapper.InstanceByTypeNameAndImplementedInterface[events.IEvent]( - eventType, - ) - - if contentType != s.ContentType() { - return nil, errors.Errorf("contentType: %s is not supported", contentType) - } - - if err := s.serializer.Unmarshal(data, targetEventPointer); err != nil { - return nil, errors.WrapIff(err, "event.GetJsonData type: %s", eventType) - } - - return targetEventPointer, nil -} - -func (s *DefaultEventSerializer) ContentType() string { - return "application/json" -} diff --git a/internal/pkg/core/serializer/event_serrialization_result.go b/internal/pkg/core/serializer/event_serrialization_result.go new file mode 100644 index 00000000..55ead52b --- /dev/null +++ b/internal/pkg/core/serializer/event_serrialization_result.go @@ -0,0 +1,6 @@ +package serializer + +type EventSerializationResult struct { + Data []byte + ContentType string +} diff --git a/internal/pkg/core/serializer/json/event_serializer.go b/internal/pkg/core/serializer/json/event_serializer.go new file mode 100644 index 00000000..ce9d9063 --- /dev/null +++ b/internal/pkg/core/serializer/json/event_serializer.go @@ -0,0 +1,118 @@ +package json + +import ( + "reflect" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "emperror.dev/errors" +) + +type DefaultEventJsonSerializer struct { + serializer serializer.Serializer +} + +func NewDefaultEventJsonSerializer(serializer serializer.Serializer) serializer.EventSerializer { + return &DefaultEventJsonSerializer{serializer: serializer} +} + +func (s *DefaultEventJsonSerializer) Serialize(event domain.IDomainEvent) (*serializer.EventSerializationResult, error) { + return s.SerializeObject(event) +} + +func (s *DefaultEventJsonSerializer) SerializeObject(event interface{}) (*serializer.EventSerializationResult, error) { + if event == nil { + return &serializer.EventSerializationResult{Data: nil, ContentType: s.ContentType()}, nil + } + + // we use event short type name instead of full type name because this event in other receiver packages could have different package name + eventType := typeMapper.GetTypeName(event) + + data, err := s.serializer.Marshal(event) + if err != nil { + return nil, errors.WrapIff(err, "error in Marshaling: `%s`", eventType) + } + + result := &serializer.EventSerializationResult{Data: data, ContentType: s.ContentType()} + + return result, nil +} + +func (s *DefaultEventJsonSerializer) Deserialize( + data []byte, + eventType string, + contentType string, +) (domain.IDomainEvent, error) { + if data == nil { + return nil, nil + } + + targetEventPointer := typeMapper.EmptyInstanceByTypeNameAndImplementedInterface[domain.IDomainEvent]( + eventType, + ) + + if targetEventPointer == nil { + return nil, errors.Errorf("event type `%s` is not impelemted IDomainEvent or can't be instansiated", eventType) + } + + if contentType != s.ContentType() { + return nil, errors.Errorf("contentType: %s is not supported", contentType) + } + + if err := s.serializer.Unmarshal(data, targetEventPointer); err != nil { + return nil, errors.WrapIff(err, "error in Unmarshaling: `%s`", eventType) + } + + return targetEventPointer.(domain.IDomainEvent), nil +} + +func (s *DefaultEventJsonSerializer) DeserializeObject( + data []byte, + eventType string, + contentType string, +) (interface{}, error) { + if data == nil { + return nil, nil + } + + targetEventPointer := typeMapper.InstanceByTypeName(eventType) + + if targetEventPointer == nil { + return nil, errors.Errorf("event type `%s` can't be instansiated", eventType) + } + + if contentType != s.ContentType() { + return nil, errors.Errorf("contentType: %s is not supported", contentType) + } + + if err := s.serializer.Unmarshal(data, targetEventPointer); err != nil { + return nil, errors.WrapIff(err, "error in Unmarshaling: `%s`", eventType) + } + + return targetEventPointer, nil +} + +func (s *DefaultEventJsonSerializer) DeserializeType( + data []byte, + eventType reflect.Type, + contentType string, +) (domain.IDomainEvent, error) { + if data == nil { + return nil, nil + } + + // we use event short type name instead of full type name because this event in other receiver packages could have different package name + eventTypeName := typeMapper.GetTypeName(eventType) + + return s.Deserialize(data, eventTypeName, contentType) +} + +func (s *DefaultEventJsonSerializer) ContentType() string { + return "application/json" +} + +func (s *DefaultEventJsonSerializer) Serializer() serializer.Serializer { + return s.serializer +} diff --git a/internal/pkg/core/serializer/json/json_serializer.go b/internal/pkg/core/serializer/json/json_serializer.go index 7afabcb0..f0b98d06 100644 --- a/internal/pkg/core/serializer/json/json_serializer.go +++ b/internal/pkg/core/serializer/json/json_serializer.go @@ -3,85 +3,85 @@ package json import ( "log" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/TylerBrock/colorjson" "github.com/goccy/go-json" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" "github.com/mitchellh/mapstructure" ) type jsonSerializer struct{} -func NewDefaultSerializer() serializer.Serializer { +func NewDefaultJsonSerializer() serializer.Serializer { return &jsonSerializer{} } // https://www.sohamkamani.com/golang/json/#decoding-json-to-maps---unstructured-data // https://developpaper.com/mapstructure-of-go/ // https://github.com/goccy/go-json + func (s *jsonSerializer) Marshal(v interface{}) ([]byte, error) { - return json.Marshal(v) + return Marshal(v) } // Unmarshal is a wrapper around json.Unmarshal. // To unmarshal JSON into an interface value, Unmarshal stores in a map[string]interface{} func (s *jsonSerializer) Unmarshal(data []byte, v interface{}) error { - // https://pkg.go.dev/encoding/json#Unmarshal - err := json.Unmarshal(data, v) - if err != nil { - return err - } - log.Printf("deserialize structure object") - - return nil + return Unmarshal(data, v) } // UnmarshalFromJson is a wrapper around json.Unmarshal. func (s *jsonSerializer) UnmarshalFromJson(data string, v interface{}) error { - err := s.Unmarshal([]byte(data), v) - if err != nil { - return err - } - - return nil + return UnmarshalFromJSON(data, v) } // DecodeWithMapStructure is a wrapper around mapstructure.Decode. // Decode takes an input structure or map[string]interface{} and uses reflection to translate it to the output structure. output must be a pointer to a map or struct. // https://pkg.go.dev/github.com/mitchellh/mapstructure#section-readme -func (s *jsonSerializer) DecodeWithMapStructure(input interface{}, output interface{}) error { - // https://developpaper.com/mapstructure-of-go/ - return mapstructure.Decode(input, output) +func (s *jsonSerializer) DecodeWithMapStructure( + input interface{}, + output interface{}, +) error { + return DecodeWithMapStructure(input, output) } -func (s *jsonSerializer) UnmarshalToMap(data []byte, v *map[string]interface{}) error { - // https://developpaper.com/mapstructure-of-go/ - err := json.Unmarshal(data, v) - if err != nil { - return err - } - return nil +func (s *jsonSerializer) UnmarshalToMap( + data []byte, + v *map[string]interface{}, +) error { + return UnmarshalToMap(data, v) } -func (s *jsonSerializer) UnmarshalToMapFromJson(data string, v *map[string]interface{}) error { - return s.UnmarshalToMap([]byte(data), v) +func (s *jsonSerializer) UnmarshalToMapFromJson( + data string, + v *map[string]interface{}, +) error { + return UnmarshalToMapFromJson(data, v) } // PrettyPrint print input object as a formatted json string func (s *jsonSerializer) PrettyPrint(data interface{}) string { + return PrettyPrint(data) +} + +// ColoredPrettyPrint print input object as a formatted json string with color +func (s *jsonSerializer) ColoredPrettyPrint(data interface{}) string { + return ColoredPrettyPrint(data) +} + +func PrettyPrint(data interface{}) string { // https://gosamples.dev/pretty-print-json/ val, err := json.MarshalIndent(data, "", " ") if err != nil { return "" } + return string(val) } -// ColoredPrettyPrint print input object as a formatted json string with color -func (s *jsonSerializer) ColoredPrettyPrint(data interface{}) string { +func ColoredPrettyPrint(data interface{}) string { // https://github.com/TylerBrock/colorjson var obj map[string]interface{} - err := json.Unmarshal([]byte(s.PrettyPrint(data)), &obj) + err := json.Unmarshal([]byte(PrettyPrint(data)), &obj) if err != nil { return "" } @@ -92,5 +92,65 @@ func (s *jsonSerializer) ColoredPrettyPrint(data interface{}) string { if err != nil { return "" } + return string(val) } + +func Marshal(v interface{}) ([]byte, error) { + return json.Marshal(v) +} + +// Unmarshal is a wrapper around json.Unmarshal. +// To unmarshal JSON into an interface value, Unmarshal stores in a map[string]interface{} +func Unmarshal(data []byte, v interface{}) error { + // https://pkg.go.dev/encoding/json#Unmarshal + err := json.Unmarshal(data, v) + if err != nil { + return err + } + + log.Printf("deserialize structure object") + + return nil +} + +// UnmarshalFromJSON is a wrapper around json.Unmarshal. +func UnmarshalFromJSON(data string, v interface{}) error { + err := Unmarshal([]byte(data), v) + if err != nil { + return err + } + + return nil +} + +// DecodeWithMapStructure is a wrapper around mapstructure.Decode. +// Decode takes an input structure or map[string]interface{} and uses reflection to translate it to the output structure. output must be a pointer to a map or struct. +// https://pkg.go.dev/github.com/mitchellh/mapstructure#section-readme +func DecodeWithMapStructure( + input interface{}, + output interface{}, +) error { + // https://developpaper.com/mapstructure-of-go/ + return mapstructure.Decode(input, output) +} + +func UnmarshalToMap( + data []byte, + v *map[string]interface{}, +) error { + // https://developpaper.com/mapstructure-of-go/ + err := json.Unmarshal(data, v) + if err != nil { + return err + } + + return nil +} + +func UnmarshalToMapFromJson( + data string, + v *map[string]interface{}, +) error { + return UnmarshalToMap([]byte(data), v) +} diff --git a/internal/pkg/core/serializer/json/json_serilizer_test.go b/internal/pkg/core/serializer/json/json_serilizer_test.go index 6c35f431..d74342bb 100644 --- a/internal/pkg/core/serializer/json/json_serilizer_test.go +++ b/internal/pkg/core/serializer/json/json_serilizer_test.go @@ -1,3 +1,6 @@ +//go:build unit +// +build unit + package json import ( @@ -12,7 +15,7 @@ type person struct { Age int } -var currentSerializer = NewDefaultSerializer() +var currentSerializer = NewDefaultJsonSerializer() func Test_Deserialize_Unstructured_Data_Into_Empty_Interface(t *testing.T) { // https://www.sohamkamani.com/golang/json/#decoding-json-to-maps---unstructured-data @@ -41,7 +44,7 @@ func Test_Deserialize_Unstructured_Data_Into_Empty_Interface(t *testing.T) { assert.True(t, reflect.TypeOf(jsonMap).Kind() == reflect.Map) assert.True(t, reflect.TypeOf(jsonMap) == reflect.TypeOf(map[string]interface{}(nil))) - assert.True(t, jsonMap.(map[string]interface{})["Name"] == "John") + assert.True(t, jsonMap.(map[string]interface{})["ShortTypeName"] == "John") assert.True(t, jsonMap.(map[string]interface{})["Age"] == float64(30)) } @@ -72,7 +75,7 @@ func Test_Deserialize_Unstructured_Data_Into_Map(t *testing.T) { assert.True(t, reflect.TypeOf(jsonMap).Kind() == reflect.Map) assert.True(t, reflect.TypeOf(jsonMap) == reflect.TypeOf(map[string]interface{}(nil))) - assert.True(t, jsonMap["Name"] == "John") + assert.True(t, jsonMap["ShortTypeName"] == "John") assert.True(t, jsonMap["Age"] == float64(30)) } @@ -169,6 +172,6 @@ func Test_Decode_To_Map(t *testing.T) { panic(err) } - assert.True(t, jsonMap["Name"] == "John") + assert.True(t, jsonMap["ShortTypeName"] == "John") assert.True(t, jsonMap["Age"] == float64(30)) } diff --git a/internal/pkg/core/serializer/json/message_serializer.go b/internal/pkg/core/serializer/json/message_serializer.go new file mode 100644 index 00000000..d5ff698b --- /dev/null +++ b/internal/pkg/core/serializer/json/message_serializer.go @@ -0,0 +1,127 @@ +package json + +import ( + "reflect" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "emperror.dev/errors" +) + +type DefaultMessageJsonSerializer struct { + serializer serializer.Serializer +} + +func NewDefaultMessageJsonSerializer(s serializer.Serializer) serializer.MessageSerializer { + return &DefaultMessageJsonSerializer{serializer: s} +} + +func (m *DefaultMessageJsonSerializer) Serialize(message types.IMessage) (*serializer.EventSerializationResult, error) { + return m.SerializeObject(message) +} + +func (m *DefaultMessageJsonSerializer) SerializeObject( + message interface{}, +) (*serializer.EventSerializationResult, error) { + if message == nil { + return &serializer.EventSerializationResult{Data: nil, ContentType: m.ContentType()}, nil + } + + // we use message short type name instead of full type name because this message in other receiver packages could have different package name + eventType := typeMapper.GetTypeName(message) + + data, err := m.serializer.Marshal(message) + if err != nil { + return nil, errors.WrapIff(err, "error in Marshaling: `%s`", eventType) + } + + result := &serializer.EventSerializationResult{Data: data, ContentType: m.ContentType()} + + return result, nil +} + +func (m *DefaultMessageJsonSerializer) SerializeEnvelop( + messageEnvelop types.MessageEnvelope, +) (*serializer.EventSerializationResult, error) { + // TODO implement me + panic("implement me") +} + +func (m *DefaultMessageJsonSerializer) Deserialize( + data []byte, + messageType string, + contentType string, +) (types.IMessage, error) { + if data == nil { + return nil, nil + } + + targetMessagePointer := typeMapper.EmptyInstanceByTypeNameAndImplementedInterface[types.IMessage]( + messageType, + ) + + if targetMessagePointer == nil { + return nil, errors.Errorf("message type `%s` is not impelemted IMessage or can't be instansiated", messageType) + } + + if contentType != m.ContentType() { + return nil, errors.Errorf("contentType: %s is not supported", contentType) + } + + if err := m.serializer.Unmarshal(data, targetMessagePointer); err != nil { + return nil, errors.WrapIff(err, "error in Unmarshaling: `%s`", messageType) + } + + return targetMessagePointer.(types.IMessage), nil +} + +func (m *DefaultMessageJsonSerializer) DeserializeObject( + data []byte, + messageType string, + contentType string, +) (interface{}, error) { + if data == nil { + return nil, nil + } + + targetMessagePointer := typeMapper.InstanceByTypeName(messageType) + + if targetMessagePointer == nil { + return nil, errors.Errorf("message type `%s` can't be instansiated", messageType) + } + + if contentType != m.ContentType() { + return nil, errors.Errorf("contentType: %s is not supported", contentType) + } + + if err := m.serializer.Unmarshal(data, targetMessagePointer); err != nil { + return nil, errors.WrapIff(err, "error in Unmarshaling: `%s`", messageType) + } + + return targetMessagePointer, nil +} + +func (m *DefaultMessageJsonSerializer) DeserializeType( + data []byte, + messageType reflect.Type, + contentType string, +) (types.IMessage, error) { + if data == nil { + return nil, nil + } + + // we use message short type name instead of full type name because this message in other receiver packages could have different package name + messageTypeName := typeMapper.GetTypeName(messageType) + + return m.Deserialize(data, messageTypeName, contentType) +} + +func (m *DefaultMessageJsonSerializer) ContentType() string { + return "application/json" +} + +func (m *DefaultMessageJsonSerializer) Serializer() serializer.Serializer { + return m.serializer +} diff --git a/internal/pkg/core/serializer/json/metadata_serializer.go b/internal/pkg/core/serializer/json/metadata_serializer.go new file mode 100644 index 00000000..ffc80afa --- /dev/null +++ b/internal/pkg/core/serializer/json/metadata_serializer.go @@ -0,0 +1,44 @@ +package json + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" + + "emperror.dev/errors" +) + +type DefaultMetadataJsonSerializer struct { + serializer serializer.Serializer +} + +func NewDefaultMetadataJsonSerializer(serializer serializer.Serializer) serializer.MetadataSerializer { + return &DefaultMetadataJsonSerializer{serializer: serializer} +} + +func (s *DefaultMetadataJsonSerializer) Serialize(meta metadata.Metadata) ([]byte, error) { + if meta == nil { + return nil, nil + } + + marshal, err := s.serializer.Marshal(meta) + if err != nil { + return nil, errors.WrapIf(err, "failed to marshal metadata") + } + + return marshal, nil +} + +func (s *DefaultMetadataJsonSerializer) Deserialize(bytes []byte) (metadata.Metadata, error) { + if bytes == nil { + return nil, nil + } + + var meta metadata.Metadata + + err := s.serializer.Unmarshal(bytes, &meta) + if err != nil { + return nil, errors.WrapIf(err, "failed to unmarshal metadata") + } + + return meta, nil +} diff --git a/internal/pkg/core/serializer/message_serializer.go b/internal/pkg/core/serializer/message_serializer.go new file mode 100644 index 00000000..c93053f8 --- /dev/null +++ b/internal/pkg/core/serializer/message_serializer.go @@ -0,0 +1,18 @@ +package serializer + +import ( + "reflect" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" +) + +type MessageSerializer interface { + Serialize(message types.IMessage) (*EventSerializationResult, error) + SerializeObject(message interface{}) (*EventSerializationResult, error) + SerializeEnvelop(messageEnvelop types.MessageEnvelope) (*EventSerializationResult, error) + Deserialize(data []byte, messageType string, contentType string) (types.IMessage, error) + DeserializeObject(data []byte, messageType string, contentType string) (interface{}, error) + DeserializeType(data []byte, messageType reflect.Type, contentType string) (types.IMessage, error) + ContentType() string + Serializer() Serializer +} diff --git a/internal/pkg/core/serializer/metadata_serializer.go b/internal/pkg/core/serializer/metadata_serializer.go index 4c75ee8a..d6be2b50 100644 --- a/internal/pkg/core/serializer/metadata_serializer.go +++ b/internal/pkg/core/serializer/metadata_serializer.go @@ -1,48 +1,8 @@ package serializer -import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - - "emperror.dev/errors" -) +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" type MetadataSerializer interface { Serialize(meta metadata.Metadata) ([]byte, error) Deserialize(bytes []byte) (metadata.Metadata, error) } - -type DefaultMetadataSerializer struct { - serializer Serializer -} - -func NewDefaultMetadataSerializer(serializer Serializer) MetadataSerializer { - return &DefaultMetadataSerializer{serializer: serializer} -} - -func (s *DefaultMetadataSerializer) Serialize(meta metadata.Metadata) ([]byte, error) { - if meta == nil { - return nil, nil - } - - marshal, err := s.serializer.Marshal(meta) - if err != nil { - return nil, errors.WrapIf(err, "failed to marshal metadata") - } - - return marshal, nil -} - -func (s *DefaultMetadataSerializer) Deserialize(bytes []byte) (metadata.Metadata, error) { - if bytes == nil { - return nil, nil - } - - var meta metadata.Metadata - - err := s.serializer.Unmarshal(bytes, &meta) - if err != nil { - return nil, errors.WrapIf(err, "failed to unmarshal metadata") - } - - return meta, nil -} diff --git a/internal/pkg/core/mocks/EventSerializer.go b/internal/pkg/core/serializer/mocks/EventSerializer.go similarity index 63% rename from internal/pkg/core/mocks/EventSerializer.go rename to internal/pkg/core/serializer/mocks/EventSerializer.go index 4cf217fa..4aa68bb8 100644 --- a/internal/pkg/core/mocks/EventSerializer.go +++ b/internal/pkg/core/serializer/mocks/EventSerializer.go @@ -1,13 +1,14 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - reflect "reflect" - + domain "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" mock "github.com/stretchr/testify/mock" - serializer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" + reflect "reflect" + + serializer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" ) // EventSerializer is an autogenerated mock type for the EventSerializer type @@ -27,6 +28,10 @@ func (_m *EventSerializer) EXPECT() *EventSerializer_Expecter { func (_m *EventSerializer) ContentType() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for ContentType") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -65,19 +70,23 @@ func (_c *EventSerializer_ContentType_Call) RunAndReturn(run func() string) *Eve } // Deserialize provides a mock function with given fields: data, eventType, contentType -func (_m *EventSerializer) Deserialize(data []byte, eventType string, contentType string) (interface{}, error) { +func (_m *EventSerializer) Deserialize(data []byte, eventType string, contentType string) (domain.IDomainEvent, error) { ret := _m.Called(data, eventType, contentType) - var r0 interface{} + if len(ret) == 0 { + panic("no return value specified for Deserialize") + } + + var r0 domain.IDomainEvent var r1 error - if rf, ok := ret.Get(0).(func([]byte, string, string) (interface{}, error)); ok { + if rf, ok := ret.Get(0).(func([]byte, string, string) (domain.IDomainEvent, error)); ok { return rf(data, eventType, contentType) } - if rf, ok := ret.Get(0).(func([]byte, string, string) interface{}); ok { + if rf, ok := ret.Get(0).(func([]byte, string, string) domain.IDomainEvent); ok { r0 = rf(data, eventType, contentType) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) + r0 = ret.Get(0).(domain.IDomainEvent) } } @@ -110,20 +119,24 @@ func (_c *EventSerializer_Deserialize_Call) Run(run func(data []byte, eventType return _c } -func (_c *EventSerializer_Deserialize_Call) Return(_a0 interface{}, _a1 error) *EventSerializer_Deserialize_Call { +func (_c *EventSerializer_Deserialize_Call) Return(_a0 domain.IDomainEvent, _a1 error) *EventSerializer_Deserialize_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *EventSerializer_Deserialize_Call) RunAndReturn(run func([]byte, string, string) (interface{}, error)) *EventSerializer_Deserialize_Call { +func (_c *EventSerializer_Deserialize_Call) RunAndReturn(run func([]byte, string, string) (domain.IDomainEvent, error)) *EventSerializer_Deserialize_Call { _c.Call.Return(run) return _c } -// DeserializeEvent provides a mock function with given fields: data, eventType, contentType -func (_m *EventSerializer) DeserializeEvent(data []byte, eventType string, contentType string) (interface{}, error) { +// DeserializeObject provides a mock function with given fields: data, eventType, contentType +func (_m *EventSerializer) DeserializeObject(data []byte, eventType string, contentType string) (interface{}, error) { ret := _m.Called(data, eventType, contentType) + if len(ret) == 0 { + panic("no return value specified for DeserializeObject") + } + var r0 interface{} var r1 error if rf, ok := ret.Get(0).(func([]byte, string, string) (interface{}, error)); ok { @@ -146,54 +159,58 @@ func (_m *EventSerializer) DeserializeEvent(data []byte, eventType string, conte return r0, r1 } -// EventSerializer_DeserializeEvent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeserializeEvent' -type EventSerializer_DeserializeEvent_Call struct { +// EventSerializer_DeserializeObject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeserializeObject' +type EventSerializer_DeserializeObject_Call struct { *mock.Call } -// DeserializeEvent is a helper method to define mock.On call +// DeserializeObject is a helper method to define mock.On call // - data []byte // - eventType string // - contentType string -func (_e *EventSerializer_Expecter) DeserializeEvent(data interface{}, eventType interface{}, contentType interface{}) *EventSerializer_DeserializeEvent_Call { - return &EventSerializer_DeserializeEvent_Call{Call: _e.mock.On("DeserializeEvent", data, eventType, contentType)} +func (_e *EventSerializer_Expecter) DeserializeObject(data interface{}, eventType interface{}, contentType interface{}) *EventSerializer_DeserializeObject_Call { + return &EventSerializer_DeserializeObject_Call{Call: _e.mock.On("DeserializeObject", data, eventType, contentType)} } -func (_c *EventSerializer_DeserializeEvent_Call) Run(run func(data []byte, eventType string, contentType string)) *EventSerializer_DeserializeEvent_Call { +func (_c *EventSerializer_DeserializeObject_Call) Run(run func(data []byte, eventType string, contentType string)) *EventSerializer_DeserializeObject_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].([]byte), args[1].(string), args[2].(string)) }) return _c } -func (_c *EventSerializer_DeserializeEvent_Call) Return(_a0 interface{}, _a1 error) *EventSerializer_DeserializeEvent_Call { +func (_c *EventSerializer_DeserializeObject_Call) Return(_a0 interface{}, _a1 error) *EventSerializer_DeserializeObject_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *EventSerializer_DeserializeEvent_Call) RunAndReturn(run func([]byte, string, string) (interface{}, error)) *EventSerializer_DeserializeEvent_Call { +func (_c *EventSerializer_DeserializeObject_Call) RunAndReturn(run func([]byte, string, string) (interface{}, error)) *EventSerializer_DeserializeObject_Call { _c.Call.Return(run) return _c } -// DeserializeMessage provides a mock function with given fields: data, eventType, contentType -func (_m *EventSerializer) DeserializeMessage(data []byte, eventType string, contentType string) (interface{}, error) { +// DeserializeType provides a mock function with given fields: data, eventType, contentType +func (_m *EventSerializer) DeserializeType(data []byte, eventType reflect.Type, contentType string) (domain.IDomainEvent, error) { ret := _m.Called(data, eventType, contentType) - var r0 interface{} + if len(ret) == 0 { + panic("no return value specified for DeserializeType") + } + + var r0 domain.IDomainEvent var r1 error - if rf, ok := ret.Get(0).(func([]byte, string, string) (interface{}, error)); ok { + if rf, ok := ret.Get(0).(func([]byte, reflect.Type, string) (domain.IDomainEvent, error)); ok { return rf(data, eventType, contentType) } - if rf, ok := ret.Get(0).(func([]byte, string, string) interface{}); ok { + if rf, ok := ret.Get(0).(func([]byte, reflect.Type, string) domain.IDomainEvent); ok { r0 = rf(data, eventType, contentType) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) + r0 = ret.Get(0).(domain.IDomainEvent) } } - if rf, ok := ret.Get(1).(func([]byte, string, string) error); ok { + if rf, ok := ret.Get(1).(func([]byte, reflect.Type, string) error); ok { r1 = rf(data, eventType, contentType) } else { r1 = ret.Error(1) @@ -202,55 +219,59 @@ func (_m *EventSerializer) DeserializeMessage(data []byte, eventType string, con return r0, r1 } -// EventSerializer_DeserializeMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeserializeMessage' -type EventSerializer_DeserializeMessage_Call struct { +// EventSerializer_DeserializeType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeserializeType' +type EventSerializer_DeserializeType_Call struct { *mock.Call } -// DeserializeMessage is a helper method to define mock.On call +// DeserializeType is a helper method to define mock.On call // - data []byte -// - eventType string +// - eventType reflect.Type // - contentType string -func (_e *EventSerializer_Expecter) DeserializeMessage(data interface{}, eventType interface{}, contentType interface{}) *EventSerializer_DeserializeMessage_Call { - return &EventSerializer_DeserializeMessage_Call{Call: _e.mock.On("DeserializeMessage", data, eventType, contentType)} +func (_e *EventSerializer_Expecter) DeserializeType(data interface{}, eventType interface{}, contentType interface{}) *EventSerializer_DeserializeType_Call { + return &EventSerializer_DeserializeType_Call{Call: _e.mock.On("DeserializeType", data, eventType, contentType)} } -func (_c *EventSerializer_DeserializeMessage_Call) Run(run func(data []byte, eventType string, contentType string)) *EventSerializer_DeserializeMessage_Call { +func (_c *EventSerializer_DeserializeType_Call) Run(run func(data []byte, eventType reflect.Type, contentType string)) *EventSerializer_DeserializeType_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].([]byte), args[1].(string), args[2].(string)) + run(args[0].([]byte), args[1].(reflect.Type), args[2].(string)) }) return _c } -func (_c *EventSerializer_DeserializeMessage_Call) Return(_a0 interface{}, _a1 error) *EventSerializer_DeserializeMessage_Call { +func (_c *EventSerializer_DeserializeType_Call) Return(_a0 domain.IDomainEvent, _a1 error) *EventSerializer_DeserializeType_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *EventSerializer_DeserializeMessage_Call) RunAndReturn(run func([]byte, string, string) (interface{}, error)) *EventSerializer_DeserializeMessage_Call { +func (_c *EventSerializer_DeserializeType_Call) RunAndReturn(run func([]byte, reflect.Type, string) (domain.IDomainEvent, error)) *EventSerializer_DeserializeType_Call { _c.Call.Return(run) return _c } -// DeserializeType provides a mock function with given fields: data, eventType, contentType -func (_m *EventSerializer) DeserializeType(data []byte, eventType reflect.Type, contentType string) (interface{}, error) { - ret := _m.Called(data, eventType, contentType) +// Serialize provides a mock function with given fields: event +func (_m *EventSerializer) Serialize(event domain.IDomainEvent) (*serializer.EventSerializationResult, error) { + ret := _m.Called(event) - var r0 interface{} + if len(ret) == 0 { + panic("no return value specified for Serialize") + } + + var r0 *serializer.EventSerializationResult var r1 error - if rf, ok := ret.Get(0).(func([]byte, reflect.Type, string) (interface{}, error)); ok { - return rf(data, eventType, contentType) + if rf, ok := ret.Get(0).(func(domain.IDomainEvent) (*serializer.EventSerializationResult, error)); ok { + return rf(event) } - if rf, ok := ret.Get(0).(func([]byte, reflect.Type, string) interface{}); ok { - r0 = rf(data, eventType, contentType) + if rf, ok := ret.Get(0).(func(domain.IDomainEvent) *serializer.EventSerializationResult); ok { + r0 = rf(event) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) + r0 = ret.Get(0).(*serializer.EventSerializationResult) } } - if rf, ok := ret.Get(1).(func([]byte, reflect.Type, string) error); ok { - r1 = rf(data, eventType, contentType) + if rf, ok := ret.Get(1).(func(domain.IDomainEvent) error); ok { + r1 = rf(event) } else { r1 = ret.Error(1) } @@ -258,40 +279,42 @@ func (_m *EventSerializer) DeserializeType(data []byte, eventType reflect.Type, return r0, r1 } -// EventSerializer_DeserializeType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeserializeType' -type EventSerializer_DeserializeType_Call struct { +// EventSerializer_Serialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Serialize' +type EventSerializer_Serialize_Call struct { *mock.Call } -// DeserializeType is a helper method to define mock.On call -// - data []byte -// - eventType reflect.Type -// - contentType string -func (_e *EventSerializer_Expecter) DeserializeType(data interface{}, eventType interface{}, contentType interface{}) *EventSerializer_DeserializeType_Call { - return &EventSerializer_DeserializeType_Call{Call: _e.mock.On("DeserializeType", data, eventType, contentType)} +// Serialize is a helper method to define mock.On call +// - event domain.IDomainEvent +func (_e *EventSerializer_Expecter) Serialize(event interface{}) *EventSerializer_Serialize_Call { + return &EventSerializer_Serialize_Call{Call: _e.mock.On("Serialize", event)} } -func (_c *EventSerializer_DeserializeType_Call) Run(run func(data []byte, eventType reflect.Type, contentType string)) *EventSerializer_DeserializeType_Call { +func (_c *EventSerializer_Serialize_Call) Run(run func(event domain.IDomainEvent)) *EventSerializer_Serialize_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].([]byte), args[1].(reflect.Type), args[2].(string)) + run(args[0].(domain.IDomainEvent)) }) return _c } -func (_c *EventSerializer_DeserializeType_Call) Return(_a0 interface{}, _a1 error) *EventSerializer_DeserializeType_Call { +func (_c *EventSerializer_Serialize_Call) Return(_a0 *serializer.EventSerializationResult, _a1 error) *EventSerializer_Serialize_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *EventSerializer_DeserializeType_Call) RunAndReturn(run func([]byte, reflect.Type, string) (interface{}, error)) *EventSerializer_DeserializeType_Call { +func (_c *EventSerializer_Serialize_Call) RunAndReturn(run func(domain.IDomainEvent) (*serializer.EventSerializationResult, error)) *EventSerializer_Serialize_Call { _c.Call.Return(run) return _c } -// Serialize provides a mock function with given fields: event -func (_m *EventSerializer) Serialize(event interface{}) (*serializer.EventSerializationResult, error) { +// SerializeObject provides a mock function with given fields: event +func (_m *EventSerializer) SerializeObject(event interface{}) (*serializer.EventSerializationResult, error) { ret := _m.Called(event) + if len(ret) == 0 { + panic("no return value specified for SerializeObject") + } + var r0 *serializer.EventSerializationResult var r1 error if rf, ok := ret.Get(0).(func(interface{}) (*serializer.EventSerializationResult, error)); ok { @@ -314,30 +337,30 @@ func (_m *EventSerializer) Serialize(event interface{}) (*serializer.EventSerial return r0, r1 } -// EventSerializer_Serialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Serialize' -type EventSerializer_Serialize_Call struct { +// EventSerializer_SerializeObject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SerializeObject' +type EventSerializer_SerializeObject_Call struct { *mock.Call } -// Serialize is a helper method to define mock.On call +// SerializeObject is a helper method to define mock.On call // - event interface{} -func (_e *EventSerializer_Expecter) Serialize(event interface{}) *EventSerializer_Serialize_Call { - return &EventSerializer_Serialize_Call{Call: _e.mock.On("Serialize", event)} +func (_e *EventSerializer_Expecter) SerializeObject(event interface{}) *EventSerializer_SerializeObject_Call { + return &EventSerializer_SerializeObject_Call{Call: _e.mock.On("SerializeObject", event)} } -func (_c *EventSerializer_Serialize_Call) Run(run func(event interface{})) *EventSerializer_Serialize_Call { +func (_c *EventSerializer_SerializeObject_Call) Run(run func(event interface{})) *EventSerializer_SerializeObject_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(interface{})) }) return _c } -func (_c *EventSerializer_Serialize_Call) Return(_a0 *serializer.EventSerializationResult, _a1 error) *EventSerializer_Serialize_Call { +func (_c *EventSerializer_SerializeObject_Call) Return(_a0 *serializer.EventSerializationResult, _a1 error) *EventSerializer_SerializeObject_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *EventSerializer_Serialize_Call) RunAndReturn(run func(interface{}) (*serializer.EventSerializationResult, error)) *EventSerializer_Serialize_Call { +func (_c *EventSerializer_SerializeObject_Call) RunAndReturn(run func(interface{}) (*serializer.EventSerializationResult, error)) *EventSerializer_SerializeObject_Call { _c.Call.Return(run) return _c } @@ -346,6 +369,10 @@ func (_c *EventSerializer_Serialize_Call) RunAndReturn(run func(interface{}) (*s func (_m *EventSerializer) Serializer() serializer.Serializer { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Serializer") + } + var r0 serializer.Serializer if rf, ok := ret.Get(0).(func() serializer.Serializer); ok { r0 = rf() diff --git a/internal/pkg/core/serializer/mocks/MessageSerializer.go b/internal/pkg/core/serializer/mocks/MessageSerializer.go new file mode 100644 index 00000000..10397c35 --- /dev/null +++ b/internal/pkg/core/serializer/mocks/MessageSerializer.go @@ -0,0 +1,485 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + reflect "reflect" + + mock "github.com/stretchr/testify/mock" + + types "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + serializer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" +) + +// MessageSerializer is an autogenerated mock type for the MessageSerializer type +type MessageSerializer struct { + mock.Mock +} + +type MessageSerializer_Expecter struct { + mock *mock.Mock +} + +func (_m *MessageSerializer) EXPECT() *MessageSerializer_Expecter { + return &MessageSerializer_Expecter{mock: &_m.Mock} +} + +// ContentType provides a mock function with given fields: +func (_m *MessageSerializer) ContentType() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ContentType") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// MessageSerializer_ContentType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContentType' +type MessageSerializer_ContentType_Call struct { + *mock.Call +} + +// ContentType is a helper method to define mock.On call +func (_e *MessageSerializer_Expecter) ContentType() *MessageSerializer_ContentType_Call { + return &MessageSerializer_ContentType_Call{Call: _e.mock.On("ContentType")} +} + +func (_c *MessageSerializer_ContentType_Call) Run(run func()) *MessageSerializer_ContentType_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MessageSerializer_ContentType_Call) Return(_a0 string) *MessageSerializer_ContentType_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessageSerializer_ContentType_Call) RunAndReturn(run func() string) *MessageSerializer_ContentType_Call { + _c.Call.Return(run) + return _c +} + +// Deserialize provides a mock function with given fields: data, messageType, contentType +func (_m *MessageSerializer) Deserialize(data []byte, messageType string, contentType string) (types.IMessage, error) { + ret := _m.Called(data, messageType, contentType) + + if len(ret) == 0 { + panic("no return value specified for Deserialize") + } + + var r0 types.IMessage + var r1 error + if rf, ok := ret.Get(0).(func([]byte, string, string) (types.IMessage, error)); ok { + return rf(data, messageType, contentType) + } + if rf, ok := ret.Get(0).(func([]byte, string, string) types.IMessage); ok { + r0 = rf(data, messageType, contentType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.IMessage) + } + } + + if rf, ok := ret.Get(1).(func([]byte, string, string) error); ok { + r1 = rf(data, messageType, contentType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessageSerializer_Deserialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Deserialize' +type MessageSerializer_Deserialize_Call struct { + *mock.Call +} + +// Deserialize is a helper method to define mock.On call +// - data []byte +// - messageType string +// - contentType string +func (_e *MessageSerializer_Expecter) Deserialize(data interface{}, messageType interface{}, contentType interface{}) *MessageSerializer_Deserialize_Call { + return &MessageSerializer_Deserialize_Call{Call: _e.mock.On("Deserialize", data, messageType, contentType)} +} + +func (_c *MessageSerializer_Deserialize_Call) Run(run func(data []byte, messageType string, contentType string)) *MessageSerializer_Deserialize_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]byte), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MessageSerializer_Deserialize_Call) Return(_a0 types.IMessage, _a1 error) *MessageSerializer_Deserialize_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessageSerializer_Deserialize_Call) RunAndReturn(run func([]byte, string, string) (types.IMessage, error)) *MessageSerializer_Deserialize_Call { + _c.Call.Return(run) + return _c +} + +// DeserializeObject provides a mock function with given fields: data, messageType, contentType +func (_m *MessageSerializer) DeserializeObject(data []byte, messageType string, contentType string) (interface{}, error) { + ret := _m.Called(data, messageType, contentType) + + if len(ret) == 0 { + panic("no return value specified for DeserializeObject") + } + + var r0 interface{} + var r1 error + if rf, ok := ret.Get(0).(func([]byte, string, string) (interface{}, error)); ok { + return rf(data, messageType, contentType) + } + if rf, ok := ret.Get(0).(func([]byte, string, string) interface{}); ok { + r0 = rf(data, messageType, contentType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + if rf, ok := ret.Get(1).(func([]byte, string, string) error); ok { + r1 = rf(data, messageType, contentType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessageSerializer_DeserializeObject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeserializeObject' +type MessageSerializer_DeserializeObject_Call struct { + *mock.Call +} + +// DeserializeObject is a helper method to define mock.On call +// - data []byte +// - messageType string +// - contentType string +func (_e *MessageSerializer_Expecter) DeserializeObject(data interface{}, messageType interface{}, contentType interface{}) *MessageSerializer_DeserializeObject_Call { + return &MessageSerializer_DeserializeObject_Call{Call: _e.mock.On("DeserializeObject", data, messageType, contentType)} +} + +func (_c *MessageSerializer_DeserializeObject_Call) Run(run func(data []byte, messageType string, contentType string)) *MessageSerializer_DeserializeObject_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]byte), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MessageSerializer_DeserializeObject_Call) Return(_a0 interface{}, _a1 error) *MessageSerializer_DeserializeObject_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessageSerializer_DeserializeObject_Call) RunAndReturn(run func([]byte, string, string) (interface{}, error)) *MessageSerializer_DeserializeObject_Call { + _c.Call.Return(run) + return _c +} + +// DeserializeType provides a mock function with given fields: data, messageType, contentType +func (_m *MessageSerializer) DeserializeType(data []byte, messageType reflect.Type, contentType string) (types.IMessage, error) { + ret := _m.Called(data, messageType, contentType) + + if len(ret) == 0 { + panic("no return value specified for DeserializeType") + } + + var r0 types.IMessage + var r1 error + if rf, ok := ret.Get(0).(func([]byte, reflect.Type, string) (types.IMessage, error)); ok { + return rf(data, messageType, contentType) + } + if rf, ok := ret.Get(0).(func([]byte, reflect.Type, string) types.IMessage); ok { + r0 = rf(data, messageType, contentType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.IMessage) + } + } + + if rf, ok := ret.Get(1).(func([]byte, reflect.Type, string) error); ok { + r1 = rf(data, messageType, contentType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessageSerializer_DeserializeType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeserializeType' +type MessageSerializer_DeserializeType_Call struct { + *mock.Call +} + +// DeserializeType is a helper method to define mock.On call +// - data []byte +// - messageType reflect.Type +// - contentType string +func (_e *MessageSerializer_Expecter) DeserializeType(data interface{}, messageType interface{}, contentType interface{}) *MessageSerializer_DeserializeType_Call { + return &MessageSerializer_DeserializeType_Call{Call: _e.mock.On("DeserializeType", data, messageType, contentType)} +} + +func (_c *MessageSerializer_DeserializeType_Call) Run(run func(data []byte, messageType reflect.Type, contentType string)) *MessageSerializer_DeserializeType_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]byte), args[1].(reflect.Type), args[2].(string)) + }) + return _c +} + +func (_c *MessageSerializer_DeserializeType_Call) Return(_a0 types.IMessage, _a1 error) *MessageSerializer_DeserializeType_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessageSerializer_DeserializeType_Call) RunAndReturn(run func([]byte, reflect.Type, string) (types.IMessage, error)) *MessageSerializer_DeserializeType_Call { + _c.Call.Return(run) + return _c +} + +// Serialize provides a mock function with given fields: message +func (_m *MessageSerializer) Serialize(message types.IMessage) (*serializer.EventSerializationResult, error) { + ret := _m.Called(message) + + if len(ret) == 0 { + panic("no return value specified for Serialize") + } + + var r0 *serializer.EventSerializationResult + var r1 error + if rf, ok := ret.Get(0).(func(types.IMessage) (*serializer.EventSerializationResult, error)); ok { + return rf(message) + } + if rf, ok := ret.Get(0).(func(types.IMessage) *serializer.EventSerializationResult); ok { + r0 = rf(message) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*serializer.EventSerializationResult) + } + } + + if rf, ok := ret.Get(1).(func(types.IMessage) error); ok { + r1 = rf(message) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessageSerializer_Serialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Serialize' +type MessageSerializer_Serialize_Call struct { + *mock.Call +} + +// Serialize is a helper method to define mock.On call +// - message types.IMessage +func (_e *MessageSerializer_Expecter) Serialize(message interface{}) *MessageSerializer_Serialize_Call { + return &MessageSerializer_Serialize_Call{Call: _e.mock.On("Serialize", message)} +} + +func (_c *MessageSerializer_Serialize_Call) Run(run func(message types.IMessage)) *MessageSerializer_Serialize_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(types.IMessage)) + }) + return _c +} + +func (_c *MessageSerializer_Serialize_Call) Return(_a0 *serializer.EventSerializationResult, _a1 error) *MessageSerializer_Serialize_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessageSerializer_Serialize_Call) RunAndReturn(run func(types.IMessage) (*serializer.EventSerializationResult, error)) *MessageSerializer_Serialize_Call { + _c.Call.Return(run) + return _c +} + +// SerializeEnvelop provides a mock function with given fields: messageEnvelop +func (_m *MessageSerializer) SerializeEnvelop(messageEnvelop types.MessageEnvelope) (*serializer.EventSerializationResult, error) { + ret := _m.Called(messageEnvelop) + + if len(ret) == 0 { + panic("no return value specified for SerializeEnvelop") + } + + var r0 *serializer.EventSerializationResult + var r1 error + if rf, ok := ret.Get(0).(func(types.MessageEnvelope) (*serializer.EventSerializationResult, error)); ok { + return rf(messageEnvelop) + } + if rf, ok := ret.Get(0).(func(types.MessageEnvelope) *serializer.EventSerializationResult); ok { + r0 = rf(messageEnvelop) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*serializer.EventSerializationResult) + } + } + + if rf, ok := ret.Get(1).(func(types.MessageEnvelope) error); ok { + r1 = rf(messageEnvelop) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessageSerializer_SerializeEnvelop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SerializeEnvelop' +type MessageSerializer_SerializeEnvelop_Call struct { + *mock.Call +} + +// SerializeEnvelop is a helper method to define mock.On call +// - messageEnvelop types.MessageEnvelope +func (_e *MessageSerializer_Expecter) SerializeEnvelop(messageEnvelop interface{}) *MessageSerializer_SerializeEnvelop_Call { + return &MessageSerializer_SerializeEnvelop_Call{Call: _e.mock.On("SerializeEnvelop", messageEnvelop)} +} + +func (_c *MessageSerializer_SerializeEnvelop_Call) Run(run func(messageEnvelop types.MessageEnvelope)) *MessageSerializer_SerializeEnvelop_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(types.MessageEnvelope)) + }) + return _c +} + +func (_c *MessageSerializer_SerializeEnvelop_Call) Return(_a0 *serializer.EventSerializationResult, _a1 error) *MessageSerializer_SerializeEnvelop_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessageSerializer_SerializeEnvelop_Call) RunAndReturn(run func(types.MessageEnvelope) (*serializer.EventSerializationResult, error)) *MessageSerializer_SerializeEnvelop_Call { + _c.Call.Return(run) + return _c +} + +// SerializeObject provides a mock function with given fields: message +func (_m *MessageSerializer) SerializeObject(message interface{}) (*serializer.EventSerializationResult, error) { + ret := _m.Called(message) + + if len(ret) == 0 { + panic("no return value specified for SerializeObject") + } + + var r0 *serializer.EventSerializationResult + var r1 error + if rf, ok := ret.Get(0).(func(interface{}) (*serializer.EventSerializationResult, error)); ok { + return rf(message) + } + if rf, ok := ret.Get(0).(func(interface{}) *serializer.EventSerializationResult); ok { + r0 = rf(message) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*serializer.EventSerializationResult) + } + } + + if rf, ok := ret.Get(1).(func(interface{}) error); ok { + r1 = rf(message) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MessageSerializer_SerializeObject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SerializeObject' +type MessageSerializer_SerializeObject_Call struct { + *mock.Call +} + +// SerializeObject is a helper method to define mock.On call +// - message interface{} +func (_e *MessageSerializer_Expecter) SerializeObject(message interface{}) *MessageSerializer_SerializeObject_Call { + return &MessageSerializer_SerializeObject_Call{Call: _e.mock.On("SerializeObject", message)} +} + +func (_c *MessageSerializer_SerializeObject_Call) Run(run func(message interface{})) *MessageSerializer_SerializeObject_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(interface{})) + }) + return _c +} + +func (_c *MessageSerializer_SerializeObject_Call) Return(_a0 *serializer.EventSerializationResult, _a1 error) *MessageSerializer_SerializeObject_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MessageSerializer_SerializeObject_Call) RunAndReturn(run func(interface{}) (*serializer.EventSerializationResult, error)) *MessageSerializer_SerializeObject_Call { + _c.Call.Return(run) + return _c +} + +// Serializer provides a mock function with given fields: +func (_m *MessageSerializer) Serializer() serializer.Serializer { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Serializer") + } + + var r0 serializer.Serializer + if rf, ok := ret.Get(0).(func() serializer.Serializer); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(serializer.Serializer) + } + } + + return r0 +} + +// MessageSerializer_Serializer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Serializer' +type MessageSerializer_Serializer_Call struct { + *mock.Call +} + +// Serializer is a helper method to define mock.On call +func (_e *MessageSerializer_Expecter) Serializer() *MessageSerializer_Serializer_Call { + return &MessageSerializer_Serializer_Call{Call: _e.mock.On("Serializer")} +} + +func (_c *MessageSerializer_Serializer_Call) Run(run func()) *MessageSerializer_Serializer_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MessageSerializer_Serializer_Call) Return(_a0 serializer.Serializer) *MessageSerializer_Serializer_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MessageSerializer_Serializer_Call) RunAndReturn(run func() serializer.Serializer) *MessageSerializer_Serializer_Call { + _c.Call.Return(run) + return _c +} + +// NewMessageSerializer creates a new instance of MessageSerializer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMessageSerializer(t interface { + mock.TestingT + Cleanup(func()) +}) *MessageSerializer { + mock := &MessageSerializer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/pkg/core/mocks/MetadataSerializer.go b/internal/pkg/core/serializer/mocks/MetadataSerializer.go similarity index 93% rename from internal/pkg/core/mocks/MetadataSerializer.go rename to internal/pkg/core/serializer/mocks/MetadataSerializer.go index 81ebc72f..75c46a15 100644 --- a/internal/pkg/core/mocks/MetadataSerializer.go +++ b/internal/pkg/core/serializer/mocks/MetadataSerializer.go @@ -1,9 +1,9 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + metadata "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" mock "github.com/stretchr/testify/mock" ) @@ -24,6 +24,10 @@ func (_m *MetadataSerializer) EXPECT() *MetadataSerializer_Expecter { func (_m *MetadataSerializer) Deserialize(bytes []byte) (metadata.Metadata, error) { ret := _m.Called(bytes) + if len(ret) == 0 { + panic("no return value specified for Deserialize") + } + var r0 metadata.Metadata var r1 error if rf, ok := ret.Get(0).(func([]byte) (metadata.Metadata, error)); ok { @@ -78,6 +82,10 @@ func (_c *MetadataSerializer_Deserialize_Call) RunAndReturn(run func([]byte) (me func (_m *MetadataSerializer) Serialize(meta metadata.Metadata) ([]byte, error) { ret := _m.Called(meta) + if len(ret) == 0 { + panic("no return value specified for Serialize") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(metadata.Metadata) ([]byte, error)); ok { diff --git a/internal/pkg/core/mocks/Serializer.go b/internal/pkg/core/serializer/mocks/Serializer.go similarity index 94% rename from internal/pkg/core/mocks/Serializer.go rename to internal/pkg/core/serializer/mocks/Serializer.go index bf086702..53bb0989 100644 --- a/internal/pkg/core/mocks/Serializer.go +++ b/internal/pkg/core/serializer/mocks/Serializer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks @@ -21,6 +21,10 @@ func (_m *Serializer) EXPECT() *Serializer_Expecter { func (_m *Serializer) ColoredPrettyPrint(data interface{}) string { ret := _m.Called(data) + if len(ret) == 0 { + panic("no return value specified for ColoredPrettyPrint") + } + var r0 string if rf, ok := ret.Get(0).(func(interface{}) string); ok { r0 = rf(data) @@ -63,6 +67,10 @@ func (_c *Serializer_ColoredPrettyPrint_Call) RunAndReturn(run func(interface{}) func (_m *Serializer) DecodeWithMapStructure(input interface{}, output interface{}) error { ret := _m.Called(input, output) + if len(ret) == 0 { + panic("no return value specified for DecodeWithMapStructure") + } + var r0 error if rf, ok := ret.Get(0).(func(interface{}, interface{}) error); ok { r0 = rf(input, output) @@ -106,6 +114,10 @@ func (_c *Serializer_DecodeWithMapStructure_Call) RunAndReturn(run func(interfac func (_m *Serializer) Marshal(v interface{}) ([]byte, error) { ret := _m.Called(v) + if len(ret) == 0 { + panic("no return value specified for Marshal") + } + var r0 []byte var r1 error if rf, ok := ret.Get(0).(func(interface{}) ([]byte, error)); ok { @@ -160,6 +172,10 @@ func (_c *Serializer_Marshal_Call) RunAndReturn(run func(interface{}) ([]byte, e func (_m *Serializer) PrettyPrint(data interface{}) string { ret := _m.Called(data) + if len(ret) == 0 { + panic("no return value specified for PrettyPrint") + } + var r0 string if rf, ok := ret.Get(0).(func(interface{}) string); ok { r0 = rf(data) @@ -202,6 +218,10 @@ func (_c *Serializer_PrettyPrint_Call) RunAndReturn(run func(interface{}) string func (_m *Serializer) Unmarshal(data []byte, v interface{}) error { ret := _m.Called(data, v) + if len(ret) == 0 { + panic("no return value specified for Unmarshal") + } + var r0 error if rf, ok := ret.Get(0).(func([]byte, interface{}) error); ok { r0 = rf(data, v) @@ -245,6 +265,10 @@ func (_c *Serializer_Unmarshal_Call) RunAndReturn(run func([]byte, interface{}) func (_m *Serializer) UnmarshalFromJson(data string, v interface{}) error { ret := _m.Called(data, v) + if len(ret) == 0 { + panic("no return value specified for UnmarshalFromJson") + } + var r0 error if rf, ok := ret.Get(0).(func(string, interface{}) error); ok { r0 = rf(data, v) @@ -288,6 +312,10 @@ func (_c *Serializer_UnmarshalFromJson_Call) RunAndReturn(run func(string, inter func (_m *Serializer) UnmarshalToMap(data []byte, v *map[string]interface{}) error { ret := _m.Called(data, v) + if len(ret) == 0 { + panic("no return value specified for UnmarshalToMap") + } + var r0 error if rf, ok := ret.Get(0).(func([]byte, *map[string]interface{}) error); ok { r0 = rf(data, v) @@ -331,6 +359,10 @@ func (_c *Serializer_UnmarshalToMap_Call) RunAndReturn(run func([]byte, *map[str func (_m *Serializer) UnmarshalToMapFromJson(data string, v *map[string]interface{}) error { ret := _m.Called(data, v) + if len(ret) == 0 { + panic("no return value specified for UnmarshalToMapFromJson") + } + var r0 error if rf, ok := ret.Get(0).(func(string, *map[string]interface{}) error); ok { r0 = rf(data, v) diff --git a/internal/pkg/core/serializer/mocks/event_serializer.go b/internal/pkg/core/serializer/mocks/event_serializer.go deleted file mode 100644 index ac920994..00000000 --- a/internal/pkg/core/serializer/mocks/event_serializer.go +++ /dev/null @@ -1,160 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import ( - reflect "reflect" - - mock "github.com/stretchr/testify/mock" - - serializer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" -) - -// EventSerializer is an autogenerated mock type for the EventSerializer type -type EventSerializer struct { - mock.Mock -} - -// ContentType provides a mock function with given fields: -func (_m *EventSerializer) ContentType() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Deserialize provides a mock function with given fields: data, eventType, contentType -func (_m *EventSerializer) Deserialize(data []byte, eventType string, contentType string) (interface{}, error) { - ret := _m.Called(data, eventType, contentType) - - var r0 interface{} - if rf, ok := ret.Get(0).(func([]byte, string, string) interface{}); ok { - r0 = rf(data, eventType, contentType) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func([]byte, string, string) error); ok { - r1 = rf(data, eventType, contentType) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// DeserializeEvent provides a mock function with given fields: data, eventType, contentType -func (_m *EventSerializer) DeserializeEvent(data []byte, eventType string, contentType string) (interface{}, error) { - ret := _m.Called(data, eventType, contentType) - - var r0 interface{} - if rf, ok := ret.Get(0).(func([]byte, string, string) interface{}); ok { - r0 = rf(data, eventType, contentType) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func([]byte, string, string) error); ok { - r1 = rf(data, eventType, contentType) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// DeserializeMessage provides a mock function with given fields: data, eventType, contentType -func (_m *EventSerializer) DeserializeMessage(data []byte, eventType string, contentType string) (interface{}, error) { - ret := _m.Called(data, eventType, contentType) - - var r0 interface{} - if rf, ok := ret.Get(0).(func([]byte, string, string) interface{}); ok { - r0 = rf(data, eventType, contentType) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func([]byte, string, string) error); ok { - r1 = rf(data, eventType, contentType) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// DeserializeType provides a mock function with given fields: data, eventType, contentType -func (_m *EventSerializer) DeserializeType(data []byte, eventType reflect.Type, contentType string) (interface{}, error) { - ret := _m.Called(data, eventType, contentType) - - var r0 interface{} - if rf, ok := ret.Get(0).(func([]byte, reflect.Type, string) interface{}); ok { - r0 = rf(data, eventType, contentType) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func([]byte, reflect.Type, string) error); ok { - r1 = rf(data, eventType, contentType) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Serialize provides a mock function with given fields: event -func (_m *EventSerializer) Serialize(event interface{}) (*serializer.EventSerializationResult, error) { - ret := _m.Called(event) - - var r0 *serializer.EventSerializationResult - if rf, ok := ret.Get(0).(func(interface{}) *serializer.EventSerializationResult); ok { - r0 = rf(event) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*serializer.EventSerializationResult) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(interface{}) error); ok { - r1 = rf(event) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -type mockConstructorTestingTNewEventSerializer interface { - mock.TestingT - Cleanup(func()) -} - -// NewEventSerializer creates a new instance of EventSerializer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewEventSerializer(t mockConstructorTestingTNewEventSerializer) *EventSerializer { - mock := &EventSerializer{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/core/serializer/mocks/metadata_serializer.go b/internal/pkg/core/serializer/mocks/metadata_serializer.go deleted file mode 100644 index b5d22187..00000000 --- a/internal/pkg/core/serializer/mocks/metadata_serializer.go +++ /dev/null @@ -1,74 +0,0 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. - -package mocks - -import ( - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - mock "github.com/stretchr/testify/mock" -) - -// MetadataSerializer is an autogenerated mock type for the MetadataSerializer type -type MetadataSerializer struct { - mock.Mock -} - -// Deserialize provides a mock function with given fields: bytes -func (_m *MetadataSerializer) Deserialize(bytes []byte) (metadata.Metadata, error) { - ret := _m.Called(bytes) - - var r0 metadata.Metadata - if rf, ok := ret.Get(0).(func([]byte) metadata.Metadata); ok { - r0 = rf(bytes) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(metadata.Metadata) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func([]byte) error); ok { - r1 = rf(bytes) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Serialize provides a mock function with given fields: meta -func (_m *MetadataSerializer) Serialize(meta metadata.Metadata) ([]byte, error) { - ret := _m.Called(meta) - - var r0 []byte - if rf, ok := ret.Get(0).(func(metadata.Metadata) []byte); ok { - r0 = rf(meta) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(metadata.Metadata) error); ok { - r1 = rf(meta) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -type mockConstructorTestingTNewMetadataSerializer interface { - mock.TestingT - Cleanup(func()) -} - -// NewMetadataSerializer creates a new instance of MetadataSerializer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewMetadataSerializer(t mockConstructorTestingTNewMetadataSerializer) *MetadataSerializer { - mock := &MetadataSerializer{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/core/serializer/serilizer.go b/internal/pkg/core/serializer/serilizer.go index 47825d26..5294b797 100644 --- a/internal/pkg/core/serializer/serilizer.go +++ b/internal/pkg/core/serializer/serilizer.go @@ -4,7 +4,10 @@ type Serializer interface { Marshal(v interface{}) ([]byte, error) Unmarshal(data []byte, v interface{}) error UnmarshalFromJson(data string, v interface{}) error - DecodeWithMapStructure(input interface{}, output interface{}) error + DecodeWithMapStructure( + input interface{}, + output interface{}, + ) error UnmarshalToMap(data []byte, v *map[string]interface{}) error UnmarshalToMapFromJson(data string, v *map[string]interface{}) error PrettyPrint(data interface{}) string diff --git a/internal/pkg/core/utils/utils.go b/internal/pkg/core/utils/utils.go index f57bde76..bc90f756 100644 --- a/internal/pkg/core/utils/utils.go +++ b/internal/pkg/core/utils/utils.go @@ -3,9 +3,9 @@ package utils import ( "reflect" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/events" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/events" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/ahmetb/go-linq/v3" ) diff --git a/internal/pkg/web/route/endpoint.go b/internal/pkg/core/web/route/endpoint.go similarity index 100% rename from internal/pkg/web/route/endpoint.go rename to internal/pkg/core/web/route/endpoint.go diff --git a/internal/pkg/web/route/helpers.go b/internal/pkg/core/web/route/helpers.go similarity index 100% rename from internal/pkg/web/route/helpers.go rename to internal/pkg/core/web/route/helpers.go diff --git a/internal/pkg/elasticsearch/elastic_options.go b/internal/pkg/elasticsearch/elastic_options.go index 6ccf3080..8d7c5177 100644 --- a/internal/pkg/elasticsearch/elastic_options.go +++ b/internal/pkg/elasticsearch/elastic_options.go @@ -1,19 +1,19 @@ package elasticsearch import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) -var optionName = strcase.ToLowerCamel(typeMapper.GetTypeNameByT[ElasticOptions]()) +var optionName = strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[ElasticOptions]()) type ElasticOptions struct { URL string `mapstructure:"url"` } -func provideConfig(environment environemnt.Environment) (*ElasticOptions, error) { +func provideConfig(environment environment.Environment) (*ElasticOptions, error) { return config.BindConfigKey[*ElasticOptions](optionName, environment) } diff --git a/internal/pkg/es/contracts/projection/projection.go b/internal/pkg/es/contracts/projection/projection.go index 6e8e43f3..410fff71 100644 --- a/internal/pkg/es/contracts/projection/projection.go +++ b/internal/pkg/es/contracts/projection/projection.go @@ -3,7 +3,7 @@ package projection import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" ) type IProjection interface { diff --git a/internal/pkg/es/contracts/projection/projection_publisher.go b/internal/pkg/es/contracts/projection/projection_publisher.go index b857e561..22af207e 100644 --- a/internal/pkg/es/contracts/projection/projection_publisher.go +++ b/internal/pkg/es/contracts/projection/projection_publisher.go @@ -3,7 +3,7 @@ package projection import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" ) type IProjectionPublisher interface { diff --git a/internal/pkg/es/contracts/store/aggregate_store.go b/internal/pkg/es/contracts/store/aggregate_store.go index 15f951bc..59e54fab 100644 --- a/internal/pkg/es/contracts/store/aggregate_store.go +++ b/internal/pkg/es/contracts/store/aggregate_store.go @@ -3,11 +3,11 @@ package store import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" - appendResult "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/append_result" - readPosition "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/read_position" - expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" + appendResult "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/append_result" + readPosition "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/read_position" + expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" uuid "github.com/satori/go.uuid" ) diff --git a/internal/pkg/es/contracts/store/event_store.go b/internal/pkg/es/contracts/store/event_store.go index f479aa61..1794012d 100644 --- a/internal/pkg/es/contracts/store/event_store.go +++ b/internal/pkg/es/contracts/store/event_store.go @@ -3,12 +3,12 @@ package store import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" - appendResult "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/append_result" - streamName "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_name" - readPosition "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/read_position" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/truncatePosition" - expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" + appendResult "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/append_result" + streamName "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_name" + readPosition "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/read_position" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/truncatePosition" + expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" ) type EventStore interface { diff --git a/internal/pkg/es/errors/errors.go b/internal/pkg/es/errors/errors.go index 4a2980c1..98bc43ec 100644 --- a/internal/pkg/es/errors/errors.go +++ b/internal/pkg/es/errors/errors.go @@ -3,7 +3,7 @@ package errors import ( "fmt" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" "emperror.dev/errors" ) diff --git a/internal/pkg/es/helpers.go b/internal/pkg/es/helpers.go index 0e03dc79..7b65b9ba 100644 --- a/internal/pkg/es/helpers.go +++ b/internal/pkg/es/helpers.go @@ -3,7 +3,7 @@ package es import ( "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts/projection" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/projection" "go.uber.org/fx" ) diff --git a/internal/pkg/es/inmemory_subscription_checkpoint_repository.go b/internal/pkg/es/inmemory_subscription_checkpoint_repository.go index 3505b95e..ac244be8 100644 --- a/internal/pkg/es/inmemory_subscription_checkpoint_repository.go +++ b/internal/pkg/es/inmemory_subscription_checkpoint_repository.go @@ -3,7 +3,7 @@ package es import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts" ) type inMemorySubscriptionCheckpointRepository struct { diff --git a/internal/pkg/es/mocks/AggregateStateProjection.go b/internal/pkg/es/mocks/AggregateStateProjection.go index 6a799337..5abe98ba 100644 --- a/internal/pkg/es/mocks/AggregateStateProjection.go +++ b/internal/pkg/es/mocks/AggregateStateProjection.go @@ -1,10 +1,10 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - domain "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + domain "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + metadata "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" mock "github.com/stretchr/testify/mock" ) @@ -26,6 +26,10 @@ func (_m *AggregateStateProjection) EXPECT() *AggregateStateProjection_Expecter func (_m *AggregateStateProjection) Apply(event domain.IDomainEvent, isNew bool) error { ret := _m.Called(event, isNew) + if len(ret) == 0 { + panic("no return value specified for Apply") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent, bool) error); ok { r0 = rf(event, isNew) @@ -69,6 +73,10 @@ func (_c *AggregateStateProjection_Apply_Call) RunAndReturn(run func(domain.IDom func (_m *AggregateStateProjection) fold(event domain.IDomainEvent, _a1 metadata.Metadata) error { ret := _m.Called(event, _a1) + if len(ret) == 0 { + panic("no return value specified for fold") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent, metadata.Metadata) error); ok { r0 = rf(event, _a1) diff --git a/internal/pkg/es/mocks/AggregateStore.go b/internal/pkg/es/mocks/AggregateStore.go index 23f9d9c0..b05feb2d 100644 --- a/internal/pkg/es/mocks/AggregateStore.go +++ b/internal/pkg/es/mocks/AggregateStore.go @@ -1,21 +1,21 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - appendResult "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/append_result" + appendResult "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/append_result" - expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" + expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + metadata "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" mock "github.com/stretchr/testify/mock" - models "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" + models "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" - readPosition "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/read_position" + readPosition "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/read_position" uuid "github.com/satori/go.uuid" ) @@ -37,6 +37,10 @@ func (_m *AggregateStore[T]) EXPECT() *AggregateStore_Expecter[T] { func (_m *AggregateStore[T]) Exists(ctx context.Context, aggregateId uuid.UUID) (bool, error) { ret := _m.Called(ctx, aggregateId) + if len(ret) == 0 { + panic("no return value specified for Exists") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (bool, error)); ok { @@ -90,6 +94,10 @@ func (_c *AggregateStore_Exists_Call[T]) RunAndReturn(run func(context.Context, func (_m *AggregateStore[T]) Load(ctx context.Context, aggregateId uuid.UUID) (T, error) { ret := _m.Called(ctx, aggregateId) + if len(ret) == 0 { + panic("no return value specified for Load") + } + var r0 T var r1 error if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (T, error)); ok { @@ -143,6 +151,10 @@ func (_c *AggregateStore_Load_Call[T]) RunAndReturn(run func(context.Context, uu func (_m *AggregateStore[T]) LoadWithReadPosition(ctx context.Context, aggregateId uuid.UUID, position readPosition.StreamReadPosition) (T, error) { ret := _m.Called(ctx, aggregateId, position) + if len(ret) == 0 { + panic("no return value specified for LoadWithReadPosition") + } + var r0 T var r1 error if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID, readPosition.StreamReadPosition) (T, error)); ok { @@ -197,6 +209,10 @@ func (_c *AggregateStore_LoadWithReadPosition_Call[T]) RunAndReturn(run func(con func (_m *AggregateStore[T]) Store(aggregate T, _a1 metadata.Metadata, ctx context.Context) (*appendResult.AppendEventsResult, error) { ret := _m.Called(aggregate, _a1, ctx) + if len(ret) == 0 { + panic("no return value specified for Store") + } + var r0 *appendResult.AppendEventsResult var r1 error if rf, ok := ret.Get(0).(func(T, metadata.Metadata, context.Context) (*appendResult.AppendEventsResult, error)); ok { @@ -253,6 +269,10 @@ func (_c *AggregateStore_Store_Call[T]) RunAndReturn(run func(T, metadata.Metada func (_m *AggregateStore[T]) StoreWithVersion(aggregate T, _a1 metadata.Metadata, expectedVersion expectedStreamVersion.ExpectedStreamVersion, ctx context.Context) (*appendResult.AppendEventsResult, error) { ret := _m.Called(aggregate, _a1, expectedVersion, ctx) + if len(ret) == 0 { + panic("no return value specified for StoreWithVersion") + } + var r0 *appendResult.AppendEventsResult var r1 error if rf, ok := ret.Get(0).(func(T, metadata.Metadata, expectedStreamVersion.ExpectedStreamVersion, context.Context) (*appendResult.AppendEventsResult, error)); ok { diff --git a/internal/pkg/es/mocks/Apply.go b/internal/pkg/es/mocks/Apply.go index 8955eeae..9e741ad0 100644 --- a/internal/pkg/es/mocks/Apply.go +++ b/internal/pkg/es/mocks/Apply.go @@ -1,9 +1,9 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - domain "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" + domain "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" mock "github.com/stretchr/testify/mock" ) @@ -24,6 +24,10 @@ func (_m *Apply) EXPECT() *Apply_Expecter { func (_m *Apply) Apply(event domain.IDomainEvent, isNew bool) error { ret := _m.Called(event, isNew) + if len(ret) == 0 { + panic("no return value specified for Apply") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent, bool) error); ok { r0 = rf(event, isNew) diff --git a/internal/pkg/es/mocks/EventStore.go b/internal/pkg/es/mocks/EventStore.go index 529ec055..f85545d8 100644 --- a/internal/pkg/es/mocks/EventStore.go +++ b/internal/pkg/es/mocks/EventStore.go @@ -1,23 +1,23 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - appendResult "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/append_result" + appendResult "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/append_result" - expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" + expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" mock "github.com/stretchr/testify/mock" - models "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" + models "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" - readPosition "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/read_position" + readPosition "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/read_position" - streamName "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_name" + streamName "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_name" - truncatePosition "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/truncatePosition" + truncatePosition "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/truncatePosition" ) // EventStore is an autogenerated mock type for the EventStore type @@ -37,6 +37,10 @@ func (_m *EventStore) EXPECT() *EventStore_Expecter { func (_m *EventStore) AppendEvents(_a0 streamName.StreamName, expectedVersion expectedStreamVersion.ExpectedStreamVersion, events []*models.StreamEvent, ctx context.Context) (*appendResult.AppendEventsResult, error) { ret := _m.Called(_a0, expectedVersion, events, ctx) + if len(ret) == 0 { + panic("no return value specified for AppendEvents") + } + var r0 *appendResult.AppendEventsResult var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, expectedStreamVersion.ExpectedStreamVersion, []*models.StreamEvent, context.Context) (*appendResult.AppendEventsResult, error)); ok { @@ -94,6 +98,10 @@ func (_c *EventStore_AppendEvents_Call) RunAndReturn(run func(streamName.StreamN func (_m *EventStore) AppendNewEvents(_a0 streamName.StreamName, events []*models.StreamEvent, ctx context.Context) (*appendResult.AppendEventsResult, error) { ret := _m.Called(_a0, events, ctx) + if len(ret) == 0 { + panic("no return value specified for AppendNewEvents") + } + var r0 *appendResult.AppendEventsResult var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, []*models.StreamEvent, context.Context) (*appendResult.AppendEventsResult, error)); ok { @@ -150,6 +158,10 @@ func (_c *EventStore_AppendNewEvents_Call) RunAndReturn(run func(streamName.Stre func (_m *EventStore) DeleteStream(_a0 streamName.StreamName, expectedVersion expectedStreamVersion.ExpectedStreamVersion, ctx context.Context) error { ret := _m.Called(_a0, expectedVersion, ctx) + if len(ret) == 0 { + panic("no return value specified for DeleteStream") + } + var r0 error if rf, ok := ret.Get(0).(func(streamName.StreamName, expectedStreamVersion.ExpectedStreamVersion, context.Context) error); ok { r0 = rf(_a0, expectedVersion, ctx) @@ -194,6 +206,10 @@ func (_c *EventStore_DeleteStream_Call) RunAndReturn(run func(streamName.StreamN func (_m *EventStore) ReadEvents(_a0 streamName.StreamName, _a1 readPosition.StreamReadPosition, count uint64, ctx context.Context) ([]*models.StreamEvent, error) { ret := _m.Called(_a0, _a1, count, ctx) + if len(ret) == 0 { + panic("no return value specified for ReadEvents") + } + var r0 []*models.StreamEvent var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, readPosition.StreamReadPosition, uint64, context.Context) ([]*models.StreamEvent, error)); ok { @@ -251,6 +267,10 @@ func (_c *EventStore_ReadEvents_Call) RunAndReturn(run func(streamName.StreamNam func (_m *EventStore) ReadEventsBackwards(_a0 streamName.StreamName, _a1 readPosition.StreamReadPosition, count uint64, ctx context.Context) ([]*models.StreamEvent, error) { ret := _m.Called(_a0, _a1, count, ctx) + if len(ret) == 0 { + panic("no return value specified for ReadEventsBackwards") + } + var r0 []*models.StreamEvent var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, readPosition.StreamReadPosition, uint64, context.Context) ([]*models.StreamEvent, error)); ok { @@ -308,6 +328,10 @@ func (_c *EventStore_ReadEventsBackwards_Call) RunAndReturn(run func(streamName. func (_m *EventStore) ReadEventsBackwardsFromEnd(_a0 streamName.StreamName, count uint64, ctx context.Context) ([]*models.StreamEvent, error) { ret := _m.Called(_a0, count, ctx) + if len(ret) == 0 { + panic("no return value specified for ReadEventsBackwardsFromEnd") + } + var r0 []*models.StreamEvent var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, uint64, context.Context) ([]*models.StreamEvent, error)); ok { @@ -364,6 +388,10 @@ func (_c *EventStore_ReadEventsBackwardsFromEnd_Call) RunAndReturn(run func(stre func (_m *EventStore) ReadEventsBackwardsWithMaxCount(stream streamName.StreamName, _a1 readPosition.StreamReadPosition, ctx context.Context) ([]*models.StreamEvent, error) { ret := _m.Called(stream, _a1, ctx) + if len(ret) == 0 { + panic("no return value specified for ReadEventsBackwardsWithMaxCount") + } + var r0 []*models.StreamEvent var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, readPosition.StreamReadPosition, context.Context) ([]*models.StreamEvent, error)); ok { @@ -420,6 +448,10 @@ func (_c *EventStore_ReadEventsBackwardsWithMaxCount_Call) RunAndReturn(run func func (_m *EventStore) ReadEventsFromStart(_a0 streamName.StreamName, count uint64, ctx context.Context) ([]*models.StreamEvent, error) { ret := _m.Called(_a0, count, ctx) + if len(ret) == 0 { + panic("no return value specified for ReadEventsFromStart") + } + var r0 []*models.StreamEvent var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, uint64, context.Context) ([]*models.StreamEvent, error)); ok { @@ -476,6 +508,10 @@ func (_c *EventStore_ReadEventsFromStart_Call) RunAndReturn(run func(streamName. func (_m *EventStore) ReadEventsWithMaxCount(_a0 streamName.StreamName, _a1 readPosition.StreamReadPosition, ctx context.Context) ([]*models.StreamEvent, error) { ret := _m.Called(_a0, _a1, ctx) + if len(ret) == 0 { + panic("no return value specified for ReadEventsWithMaxCount") + } + var r0 []*models.StreamEvent var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, readPosition.StreamReadPosition, context.Context) ([]*models.StreamEvent, error)); ok { @@ -532,6 +568,10 @@ func (_c *EventStore_ReadEventsWithMaxCount_Call) RunAndReturn(run func(streamNa func (_m *EventStore) StreamExists(_a0 streamName.StreamName, ctx context.Context) (bool, error) { ret := _m.Called(_a0, ctx) + if len(ret) == 0 { + panic("no return value specified for StreamExists") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, context.Context) (bool, error)); ok { @@ -585,6 +625,10 @@ func (_c *EventStore_StreamExists_Call) RunAndReturn(run func(streamName.StreamN func (_m *EventStore) TruncateStream(_a0 streamName.StreamName, _a1 truncatePosition.StreamTruncatePosition, expectedVersion expectedStreamVersion.ExpectedStreamVersion, ctx context.Context) (*appendResult.AppendEventsResult, error) { ret := _m.Called(_a0, _a1, expectedVersion, ctx) + if len(ret) == 0 { + panic("no return value specified for TruncateStream") + } + var r0 *appendResult.AppendEventsResult var r1 error if rf, ok := ret.Get(0).(func(streamName.StreamName, truncatePosition.StreamTruncatePosition, expectedStreamVersion.ExpectedStreamVersion, context.Context) (*appendResult.AppendEventsResult, error)); ok { diff --git a/internal/pkg/es/mocks/IEventSourcedAggregateRoot.go b/internal/pkg/es/mocks/IEventSourcedAggregateRoot.go index 4fc1e032..4f9f9207 100644 --- a/internal/pkg/es/mocks/IEventSourcedAggregateRoot.go +++ b/internal/pkg/es/mocks/IEventSourcedAggregateRoot.go @@ -1,10 +1,10 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - domain "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + domain "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + metadata "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" mock "github.com/stretchr/testify/mock" @@ -30,6 +30,10 @@ func (_m *IEventSourcedAggregateRoot) EXPECT() *IEventSourcedAggregateRoot_Expec func (_m *IEventSourcedAggregateRoot) AddDomainEvents(event domain.IDomainEvent) error { ret := _m.Called(event) + if len(ret) == 0 { + panic("no return value specified for AddDomainEvents") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent) error); ok { r0 = rf(event) @@ -72,6 +76,10 @@ func (_c *IEventSourcedAggregateRoot_AddDomainEvents_Call) RunAndReturn(run func func (_m *IEventSourcedAggregateRoot) Apply(event domain.IDomainEvent, isNew bool) error { ret := _m.Called(event, isNew) + if len(ret) == 0 { + panic("no return value specified for Apply") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent, bool) error); ok { r0 = rf(event, isNew) @@ -115,6 +123,10 @@ func (_c *IEventSourcedAggregateRoot_Apply_Call) RunAndReturn(run func(domain.ID func (_m *IEventSourcedAggregateRoot) CreatedAt() time.Time { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for CreatedAt") + } + var r0 time.Time if rf, ok := ret.Get(0).(func() time.Time); ok { r0 = rf() @@ -156,6 +168,10 @@ func (_c *IEventSourcedAggregateRoot_CreatedAt_Call) RunAndReturn(run func() tim func (_m *IEventSourcedAggregateRoot) CurrentVersion() int64 { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for CurrentVersion") + } + var r0 int64 if rf, ok := ret.Get(0).(func() int64); ok { r0 = rf() @@ -197,6 +213,10 @@ func (_c *IEventSourcedAggregateRoot_CurrentVersion_Call) RunAndReturn(run func( func (_m *IEventSourcedAggregateRoot) HasUncommittedEvents() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for HasUncommittedEvents") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -238,6 +258,10 @@ func (_c *IEventSourcedAggregateRoot_HasUncommittedEvents_Call) RunAndReturn(run func (_m *IEventSourcedAggregateRoot) Id() uuid.UUID { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Id") + } + var r0 uuid.UUID if rf, ok := ret.Get(0).(func() uuid.UUID); ok { r0 = rf() @@ -281,6 +305,10 @@ func (_c *IEventSourcedAggregateRoot_Id_Call) RunAndReturn(run func() uuid.UUID) func (_m *IEventSourcedAggregateRoot) LoadFromHistory(events []domain.IDomainEvent, _a1 metadata.Metadata) error { ret := _m.Called(events, _a1) + if len(ret) == 0 { + panic("no return value specified for LoadFromHistory") + } + var r0 error if rf, ok := ret.Get(0).(func([]domain.IDomainEvent, metadata.Metadata) error); ok { r0 = rf(events, _a1) @@ -356,6 +384,10 @@ func (_c *IEventSourcedAggregateRoot_MarkUncommittedEventAsCommitted_Call) RunAn func (_m *IEventSourcedAggregateRoot) OriginalVersion() int64 { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for OriginalVersion") + } + var r0 int64 if rf, ok := ret.Get(0).(func() int64); ok { r0 = rf() @@ -529,6 +561,10 @@ func (_c *IEventSourcedAggregateRoot_SetUpdatedAt_Call) RunAndReturn(run func(ti func (_m *IEventSourcedAggregateRoot) UncommittedEvents() []domain.IDomainEvent { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for UncommittedEvents") + } + var r0 []domain.IDomainEvent if rf, ok := ret.Get(0).(func() []domain.IDomainEvent); ok { r0 = rf() @@ -572,6 +608,10 @@ func (_c *IEventSourcedAggregateRoot_UncommittedEvents_Call) RunAndReturn(run fu func (_m *IEventSourcedAggregateRoot) UpdatedAt() time.Time { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for UpdatedAt") + } + var r0 time.Time if rf, ok := ret.Get(0).(func() time.Time); ok { r0 = rf() @@ -613,6 +653,10 @@ func (_c *IEventSourcedAggregateRoot_UpdatedAt_Call) RunAndReturn(run func() tim func (_m *IEventSourcedAggregateRoot) fold(event domain.IDomainEvent, _a1 metadata.Metadata) error { ret := _m.Called(event, _a1) + if len(ret) == 0 { + panic("no return value specified for fold") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent, metadata.Metadata) error); ok { r0 = rf(event, _a1) diff --git a/internal/pkg/es/mocks/IHaveEventSourcedAggregate.go b/internal/pkg/es/mocks/IHaveEventSourcedAggregate.go index f2304d6d..ffa3b899 100644 --- a/internal/pkg/es/mocks/IHaveEventSourcedAggregate.go +++ b/internal/pkg/es/mocks/IHaveEventSourcedAggregate.go @@ -1,10 +1,10 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - domain "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + domain "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + metadata "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" mock "github.com/stretchr/testify/mock" @@ -30,6 +30,10 @@ func (_m *IHaveEventSourcedAggregate) EXPECT() *IHaveEventSourcedAggregate_Expec func (_m *IHaveEventSourcedAggregate) AddDomainEvents(event domain.IDomainEvent) error { ret := _m.Called(event) + if len(ret) == 0 { + panic("no return value specified for AddDomainEvents") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent) error); ok { r0 = rf(event) @@ -72,6 +76,10 @@ func (_c *IHaveEventSourcedAggregate_AddDomainEvents_Call) RunAndReturn(run func func (_m *IHaveEventSourcedAggregate) Apply(event domain.IDomainEvent, isNew bool) error { ret := _m.Called(event, isNew) + if len(ret) == 0 { + panic("no return value specified for Apply") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent, bool) error); ok { r0 = rf(event, isNew) @@ -115,6 +123,10 @@ func (_c *IHaveEventSourcedAggregate_Apply_Call) RunAndReturn(run func(domain.ID func (_m *IHaveEventSourcedAggregate) CreatedAt() time.Time { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for CreatedAt") + } + var r0 time.Time if rf, ok := ret.Get(0).(func() time.Time); ok { r0 = rf() @@ -156,6 +168,10 @@ func (_c *IHaveEventSourcedAggregate_CreatedAt_Call) RunAndReturn(run func() tim func (_m *IHaveEventSourcedAggregate) CurrentVersion() int64 { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for CurrentVersion") + } + var r0 int64 if rf, ok := ret.Get(0).(func() int64); ok { r0 = rf() @@ -197,6 +213,10 @@ func (_c *IHaveEventSourcedAggregate_CurrentVersion_Call) RunAndReturn(run func( func (_m *IHaveEventSourcedAggregate) HasUncommittedEvents() bool { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for HasUncommittedEvents") + } + var r0 bool if rf, ok := ret.Get(0).(func() bool); ok { r0 = rf() @@ -238,6 +258,10 @@ func (_c *IHaveEventSourcedAggregate_HasUncommittedEvents_Call) RunAndReturn(run func (_m *IHaveEventSourcedAggregate) Id() uuid.UUID { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Id") + } + var r0 uuid.UUID if rf, ok := ret.Get(0).(func() uuid.UUID); ok { r0 = rf() @@ -281,6 +305,10 @@ func (_c *IHaveEventSourcedAggregate_Id_Call) RunAndReturn(run func() uuid.UUID) func (_m *IHaveEventSourcedAggregate) LoadFromHistory(events []domain.IDomainEvent, _a1 metadata.Metadata) error { ret := _m.Called(events, _a1) + if len(ret) == 0 { + panic("no return value specified for LoadFromHistory") + } + var r0 error if rf, ok := ret.Get(0).(func([]domain.IDomainEvent, metadata.Metadata) error); ok { r0 = rf(events, _a1) @@ -388,6 +416,10 @@ func (_c *IHaveEventSourcedAggregate_NewEmptyAggregate_Call) RunAndReturn(run fu func (_m *IHaveEventSourcedAggregate) OriginalVersion() int64 { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for OriginalVersion") + } + var r0 int64 if rf, ok := ret.Get(0).(func() int64); ok { r0 = rf() @@ -561,6 +593,10 @@ func (_c *IHaveEventSourcedAggregate_SetUpdatedAt_Call) RunAndReturn(run func(ti func (_m *IHaveEventSourcedAggregate) UncommittedEvents() []domain.IDomainEvent { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for UncommittedEvents") + } + var r0 []domain.IDomainEvent if rf, ok := ret.Get(0).(func() []domain.IDomainEvent); ok { r0 = rf() @@ -604,6 +640,10 @@ func (_c *IHaveEventSourcedAggregate_UncommittedEvents_Call) RunAndReturn(run fu func (_m *IHaveEventSourcedAggregate) UpdatedAt() time.Time { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for UpdatedAt") + } + var r0 time.Time if rf, ok := ret.Get(0).(func() time.Time); ok { r0 = rf() @@ -645,6 +685,10 @@ func (_c *IHaveEventSourcedAggregate_UpdatedAt_Call) RunAndReturn(run func() tim func (_m *IHaveEventSourcedAggregate) When(event domain.IDomainEvent) error { ret := _m.Called(event) + if len(ret) == 0 { + panic("no return value specified for When") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent) error); ok { r0 = rf(event) @@ -687,6 +731,10 @@ func (_c *IHaveEventSourcedAggregate_When_Call) RunAndReturn(run func(domain.IDo func (_m *IHaveEventSourcedAggregate) fold(event domain.IDomainEvent, _a1 metadata.Metadata) error { ret := _m.Called(event, _a1) + if len(ret) == 0 { + panic("no return value specified for fold") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent, metadata.Metadata) error); ok { r0 = rf(event, _a1) diff --git a/internal/pkg/es/mocks/IProjection.go b/internal/pkg/es/mocks/IProjection.go index 811572db..f51f7917 100644 --- a/internal/pkg/es/mocks/IProjection.go +++ b/internal/pkg/es/mocks/IProjection.go @@ -1,11 +1,11 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - models "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" + models "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" mock "github.com/stretchr/testify/mock" ) @@ -26,6 +26,10 @@ func (_m *IProjection) EXPECT() *IProjection_Expecter { func (_m *IProjection) ProcessEvent(ctx context.Context, streamEvent *models.StreamEvent) error { ret := _m.Called(ctx, streamEvent) + if len(ret) == 0 { + panic("no return value specified for ProcessEvent") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.StreamEvent) error); ok { r0 = rf(ctx, streamEvent) diff --git a/internal/pkg/es/mocks/IProjectionPublisher.go b/internal/pkg/es/mocks/IProjectionPublisher.go index df966323..77f019c0 100644 --- a/internal/pkg/es/mocks/IProjectionPublisher.go +++ b/internal/pkg/es/mocks/IProjectionPublisher.go @@ -1,11 +1,11 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( context "context" - models "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" + models "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" mock "github.com/stretchr/testify/mock" ) @@ -26,6 +26,10 @@ func (_m *IProjectionPublisher) EXPECT() *IProjectionPublisher_Expecter { func (_m *IProjectionPublisher) Publish(ctx context.Context, streamEvent *models.StreamEvent) error { ret := _m.Called(ctx, streamEvent) + if len(ret) == 0 { + panic("no return value specified for Publish") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.StreamEvent) error); ok { r0 = rf(ctx, streamEvent) diff --git a/internal/pkg/es/mocks/SubscriptionCheckpointRepository.go b/internal/pkg/es/mocks/SubscriptionCheckpointRepository.go index c4389a38..df0c93fe 100644 --- a/internal/pkg/es/mocks/SubscriptionCheckpointRepository.go +++ b/internal/pkg/es/mocks/SubscriptionCheckpointRepository.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks @@ -25,6 +25,10 @@ func (_m *SubscriptionCheckpointRepository) EXPECT() *SubscriptionCheckpointRepo func (_m *SubscriptionCheckpointRepository) Load(subscriptionId string, ctx context.Context) (uint64, error) { ret := _m.Called(subscriptionId, ctx) + if len(ret) == 0 { + panic("no return value specified for Load") + } + var r0 uint64 var r1 error if rf, ok := ret.Get(0).(func(string, context.Context) (uint64, error)); ok { @@ -78,6 +82,10 @@ func (_c *SubscriptionCheckpointRepository_Load_Call) RunAndReturn(run func(stri func (_m *SubscriptionCheckpointRepository) Store(subscriptionId string, position uint64, ctx context.Context) error { ret := _m.Called(subscriptionId, position, ctx) + if len(ret) == 0 { + panic("no return value specified for Store") + } + var r0 error if rf, ok := ret.Get(0).(func(string, uint64, context.Context) error); ok { r0 = rf(subscriptionId, position, ctx) diff --git a/internal/pkg/es/mocks/When.go b/internal/pkg/es/mocks/When.go index 123bed97..548e1f24 100644 --- a/internal/pkg/es/mocks/When.go +++ b/internal/pkg/es/mocks/When.go @@ -1,9 +1,9 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - domain "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" + domain "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" mock "github.com/stretchr/testify/mock" ) @@ -24,6 +24,10 @@ func (_m *When) EXPECT() *When_Expecter { func (_m *When) When(event domain.IDomainEvent) error { ret := _m.Called(event) + if len(ret) == 0 { + panic("no return value specified for When") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent) error); ok { r0 = rf(event) diff --git a/internal/pkg/es/mocks/WhenFunc.go b/internal/pkg/es/mocks/WhenFunc.go index 1270d4ea..3ccba22e 100644 --- a/internal/pkg/es/mocks/WhenFunc.go +++ b/internal/pkg/es/mocks/WhenFunc.go @@ -1,9 +1,9 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - domain "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" + domain "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" mock "github.com/stretchr/testify/mock" ) @@ -24,6 +24,10 @@ func (_m *WhenFunc) EXPECT() *WhenFunc_Expecter { func (_m *WhenFunc) Execute(event domain.IDomainEvent) error { ret := _m.Called(event) + if len(ret) == 0 { + panic("no return value specified for Execute") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent) error); ok { r0 = rf(event) diff --git a/internal/pkg/es/mocks/fold.go b/internal/pkg/es/mocks/fold.go index 7b361ba7..397835ed 100644 --- a/internal/pkg/es/mocks/fold.go +++ b/internal/pkg/es/mocks/fold.go @@ -1,10 +1,10 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - domain "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - metadata "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + domain "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + metadata "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" mock "github.com/stretchr/testify/mock" ) @@ -26,6 +26,10 @@ func (_m *fold) EXPECT() *fold_Expecter { func (_m *fold) fold(event domain.IDomainEvent, _a1 metadata.Metadata) error { ret := _m.Called(event, _a1) + if len(ret) == 0 { + panic("no return value specified for fold") + } + var r0 error if rf, ok := ret.Get(0).(func(domain.IDomainEvent, metadata.Metadata) error); ok { r0 = rf(event, _a1) diff --git a/internal/pkg/es/models/event_sourced_aggregate.go b/internal/pkg/es/models/event_sourced_aggregate.go index 033441dc..ece166fb 100644 --- a/internal/pkg/es/models/event_sourced_aggregate.go +++ b/internal/pkg/es/models/event_sourced_aggregate.go @@ -7,10 +7,10 @@ import ( "encoding/json" "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - errors2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/errors" - expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + errors2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/errors" + expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" "emperror.dev/errors" "github.com/ahmetb/go-linq/v3" diff --git a/internal/pkg/es/models/stream_event.go b/internal/pkg/es/models/stream_event.go index 63318e71..6cceb97a 100644 --- a/internal/pkg/es/models/stream_event.go +++ b/internal/pkg/es/models/stream_event.go @@ -1,8 +1,8 @@ package models import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" uuid "github.com/satori/go.uuid" ) diff --git a/internal/pkg/es/models/stream_name/stream_name.go b/internal/pkg/es/models/stream_name/stream_name.go index c097f31e..8b16dd9f 100644 --- a/internal/pkg/es/models/stream_name/stream_name.go +++ b/internal/pkg/es/models/stream_name/stream_name.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" "github.com/goccy/go-reflect" uuid "github.com/satori/go.uuid" diff --git a/internal/pkg/es/models/stream_position/read_position/stream_read_position.go b/internal/pkg/es/models/stream_position/read_position/stream_read_position.go index 241c579c..49bb70e4 100644 --- a/internal/pkg/es/models/stream_position/read_position/stream_read_position.go +++ b/internal/pkg/es/models/stream_position/read_position/stream_read_position.go @@ -1,6 +1,6 @@ package readPosition -import expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" +import expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" // https://github.com/EventStore/EventStore-Client-Dotnet/blob/b8beee7b97ef359316822cb2d00f120bf67bd14d/src/EventStore.Client/StreamPosition.cs // https://github.com/EventStore/EventStore-Client-Go/blob/1591d047c0c448cacc0468f9af3605572aba7970/esdb/position.go diff --git a/internal/pkg/es/projection_publisher.go b/internal/pkg/es/projection_publisher.go index 846640ae..07845dec 100644 --- a/internal/pkg/es/projection_publisher.go +++ b/internal/pkg/es/projection_publisher.go @@ -3,8 +3,8 @@ package es import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts/projection" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/projection" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" "emperror.dev/errors" ) diff --git a/internal/pkg/eventstroredb/aggregate_store.go b/internal/pkg/eventstroredb/aggregate_store.go index 657b7610..716405cc 100644 --- a/internal/pkg/eventstroredb/aggregate_store.go +++ b/internal/pkg/eventstroredb/aggregate_store.go @@ -5,19 +5,19 @@ import ( "fmt" "reflect" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts/store" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" - appendResult "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/append_result" - streamName "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_name" - readPosition "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/read_position" - expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" - esErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb/errors" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing/attribute" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/store" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" + appendResult "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/append_result" + streamName "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_name" + readPosition "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/read_position" + expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" + esErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/errors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "emperror.dev/errors" "github.com/EventStore/EventStore-Client-Go/esdb" @@ -55,7 +55,9 @@ func (a *esdbAggregateStore[T]) StoreWithVersion( ctx context.Context, ) (*appendResult.AppendEventsResult, error) { ctx, span := a.tracer.Start(ctx, "esdbAggregateStore.StoreWithVersion") - span.SetAttributes(attribute2.String("AggregateID", aggregate.Id().String())) + span.SetAttributes( + attribute2.String("AggregateID", aggregate.Id().String()), + ) defer span.End() if len(aggregate.UncommittedEvents()) == 0 { @@ -97,7 +99,7 @@ func (a *esdbAggregateStore[T]) StoreWithVersion( ctx, ) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WrapIff( err, @@ -130,11 +132,18 @@ func (a *esdbAggregateStore[T]) Store( ctx, span := a.tracer.Start(ctx, "esdbAggregateStore.Store") defer span.End() - expectedVersion := expectedStreamVersion.FromInt64(aggregate.OriginalVersion()) + expectedVersion := expectedStreamVersion.FromInt64( + aggregate.OriginalVersion(), + ) - streamAppendResult, err := a.StoreWithVersion(aggregate, metadata, expectedVersion, ctx) + streamAppendResult, err := a.StoreWithVersion( + aggregate, + metadata, + expectedVersion, + ctx, + ) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WrapIff( err, @@ -147,7 +156,10 @@ func (a *esdbAggregateStore[T]) Store( return streamAppendResult, nil } -func (a *esdbAggregateStore[T]) Load(ctx context.Context, aggregateId uuid.UUID) (T, error) { +func (a *esdbAggregateStore[T]) Load( + ctx context.Context, + aggregateId uuid.UUID, +) (T, error) { ctx, span := a.tracer.Start(ctx, "esdbAggregateStore.Load") defer span.End() @@ -181,7 +193,7 @@ func (a *esdbAggregateStore[T]) LoadWithReadPosition( method := reflect.ValueOf(aggregate).MethodByName("NewEmptyAggregate") if !method.IsValid() { - return *new(T), tracing.TraceErrFromSpan( + return *new(T), utils.TraceErrStatusFromSpan( span, errors.New( "[esdbAggregateStore_LoadWithReadPosition:MethodByName] aggregate does not have a `NewEmptyAggregate` method", @@ -196,7 +208,7 @@ func (a *esdbAggregateStore[T]) LoadWithReadPosition( streamEvents, err := a.getStreamEvents(streamId, position, ctx) if errors.Is(err, esdb.ErrStreamNotFound) || len(streamEvents) == 0 { - return *new(T), tracing.TraceErrFromSpan( + return *new(T), utils.TraceErrStatusFromSpan( span, errors.WithMessage( esErrors.NewAggregateNotFoundError(err, aggregateId), @@ -204,8 +216,9 @@ func (a *esdbAggregateStore[T]) LoadWithReadPosition( ), ) } + if err != nil { - return *new(T), tracing.TraceErrFromSpan( + return *new(T), utils.TraceErrStatusFromSpan( span, errors.WrapIff( err, @@ -228,20 +241,25 @@ func (a *esdbAggregateStore[T]) LoadWithReadPosition( err = aggregate.LoadFromHistory(domainEvents, meta) if err != nil { - return *new(T), tracing.TraceErrFromSpan(span, err) + return *new(T), utils.TraceStatusFromSpan(span, err) } - a.log.Infow(fmt.Sprintf("Loaded aggregate with streamId {%s} and aggregateId {%s}", - streamId.String(), - aggregateId.String()), - logger.Fields{"Aggregate": aggregate, "StreamId": streamId.String()}) + a.log.Infow( + fmt.Sprintf("Loaded aggregate with streamId {%s} and aggregateId {%s}", + streamId.String(), + aggregateId.String()), + logger.Fields{"Aggregate": aggregate, "StreamId": streamId.String()}, + ) span.SetAttributes(attribute.Object("Aggregate", aggregate)) return aggregate, nil } -func (a *esdbAggregateStore[T]) Exists(ctx context.Context, aggregateId uuid.UUID) (bool, error) { +func (a *esdbAggregateStore[T]) Exists( + ctx context.Context, + aggregateId uuid.UUID, +) (bool, error) { ctx, span := a.tracer.Start(ctx, "esdbAggregateStore.Exists") span.SetAttributes(attribute2.String("AggregateID", aggregateId.String())) defer span.End() @@ -261,7 +279,12 @@ func (a *esdbAggregateStore[T]) getStreamEvents( var streamEvents []*models.StreamEvent for true { - events, err := a.eventStore.ReadEvents(streamId, position, uint64(pageSize), ctx) + events, err := a.eventStore.ReadEvents( + streamId, + position, + uint64(pageSize), + ctx, + ) if err != nil { return nil, errors.WrapIff( err, diff --git a/internal/pkg/eventstroredb/config/eventstoredb_options.go b/internal/pkg/eventstroredb/config/eventstoredb_options.go index ca1e9a71..4aaad754 100644 --- a/internal/pkg/eventstroredb/config/eventstoredb_options.go +++ b/internal/pkg/eventstroredb/config/eventstoredb_options.go @@ -3,9 +3,9 @@ package config import ( "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) @@ -49,7 +49,7 @@ type Subscription struct { SubscriptionId string `mapstructure:"subscriptionId" validate:"required"` } -func ProvideConfig(environment environemnt.Environment) (*EventStoreDbOptions, error) { - optionName := strcase.ToLowerCamel(typeMapper.GetTypeNameByT[EventStoreDbOptions]()) +func ProvideConfig(environment environment.Environment) (*EventStoreDbOptions, error) { + optionName := strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[EventStoreDbOptions]()) return config.BindConfigKey[*EventStoreDbOptions](optionName, environment) } diff --git a/internal/pkg/eventstroredb/errors/aggregate_not_found_error.go b/internal/pkg/eventstroredb/errors/aggregate_not_found_error.go index 54f4da05..5b8cb8df 100644 --- a/internal/pkg/eventstroredb/errors/aggregate_not_found_error.go +++ b/internal/pkg/eventstroredb/errors/aggregate_not_found_error.go @@ -3,7 +3,7 @@ package errors import ( "fmt" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" "emperror.dev/errors" uuid "github.com/satori/go.uuid" diff --git a/internal/pkg/eventstroredb/errors/append_to_stream_error.go b/internal/pkg/eventstroredb/errors/append_to_stream_error.go index bb40ca1f..aefe6eee 100644 --- a/internal/pkg/eventstroredb/errors/append_to_stream_error.go +++ b/internal/pkg/eventstroredb/errors/append_to_stream_error.go @@ -3,7 +3,7 @@ package errors import ( "fmt" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" "emperror.dev/errors" ) diff --git a/internal/pkg/eventstroredb/errors/delete_stream_error.go b/internal/pkg/eventstroredb/errors/delete_stream_error.go index 5e840f62..09287142 100644 --- a/internal/pkg/eventstroredb/errors/delete_stream_error.go +++ b/internal/pkg/eventstroredb/errors/delete_stream_error.go @@ -3,7 +3,7 @@ package errors import ( "fmt" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" "emperror.dev/errors" ) diff --git a/internal/pkg/eventstroredb/errors/read_stream_error.go b/internal/pkg/eventstroredb/errors/read_stream_error.go index 3ea35e12..a1120d3d 100644 --- a/internal/pkg/eventstroredb/errors/read_stream_error.go +++ b/internal/pkg/eventstroredb/errors/read_stream_error.go @@ -1,7 +1,7 @@ package errors import ( - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" "emperror.dev/errors" ) diff --git a/internal/pkg/eventstroredb/errors/stream_not_found_error.go b/internal/pkg/eventstroredb/errors/stream_not_found_error.go index e9d2d45c..84018583 100644 --- a/internal/pkg/eventstroredb/errors/stream_not_found_error.go +++ b/internal/pkg/eventstroredb/errors/stream_not_found_error.go @@ -3,7 +3,7 @@ package errors import ( "fmt" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" "emperror.dev/errors" ) diff --git a/internal/pkg/eventstroredb/errors/truncate_stream_error.go b/internal/pkg/eventstroredb/errors/truncate_stream_error.go index 479f7a39..79a3dc38 100644 --- a/internal/pkg/eventstroredb/errors/truncate_stream_error.go +++ b/internal/pkg/eventstroredb/errors/truncate_stream_error.go @@ -3,7 +3,7 @@ package errors import ( "fmt" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" "emperror.dev/errors" ) diff --git a/internal/pkg/eventstroredb/esdb_serilizer.go b/internal/pkg/eventstroredb/esdb_serilizer.go index 6e955dc0..4f22469b 100644 --- a/internal/pkg/eventstroredb/esdb_serilizer.go +++ b/internal/pkg/eventstroredb/esdb_serilizer.go @@ -4,16 +4,16 @@ import ( "io" "strings" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/domain" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" - appendResult "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/append_result" - readPosition "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/read_position" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/truncatePosition" - expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" - esErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb/errors" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" + appendResult "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/append_result" + readPosition "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/read_position" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/truncatePosition" + expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" + esErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/errors" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "emperror.dev/errors" "github.com/EventStore/EventStore-Client-Go/esdb" @@ -139,7 +139,7 @@ func (e *EsdbSerializer) EsdbPositionToStreamReadPosition( func (e *EsdbSerializer) ResolvedEventToStreamEvent( resolveEvent *esdb.ResolvedEvent, ) (*models.StreamEvent, error) { - deserializedEvent, err := e.eventSerializer.DeserializeEvent( + deserializedEvent, err := e.eventSerializer.Deserialize( resolveEvent.Event.Data, resolveEvent.Event.EventType, resolveEvent.Event.ContentType, @@ -160,7 +160,7 @@ func (e *EsdbSerializer) ResolvedEventToStreamEvent( return &models.StreamEvent{ EventID: id, - Event: deserializedEvent.(domain.IDomainEvent), + Event: deserializedEvent, Metadata: deserializedMeta, Version: int64(resolveEvent.Event.EventNumber), Position: e.EsdbPositionToStreamReadPosition(resolveEvent.OriginalEvent().Position).Value(), @@ -192,7 +192,7 @@ func (e *EsdbSerializer) EsdbWriteResultToAppendEventResult( } func (e *EsdbSerializer) Serialize( - data interface{}, + data domain.IDomainEvent, meta metadata.Metadata, ) (*esdb.EventData, error) { serializedData, err := e.eventSerializer.Serialize(data) @@ -215,14 +215,62 @@ func (e *EsdbSerializer) Serialize( }, nil } +func (e *EsdbSerializer) SerializeObject( + data interface{}, + meta metadata.Metadata, +) (*esdb.EventData, error) { + serializedData, err := e.eventSerializer.SerializeObject(data) + if err != nil { + return nil, err + } + + serializedMeta, err := e.metadataSerializer.Serialize(meta) + if err != nil { + return nil, err + } + + id, err := uuid.NewV4() + return &esdb.EventData{ + EventID: id, + EventType: typeMapper.GetTypeName(data), + Data: serializedData.Data, + ContentType: esdb.JsonContentType, + Metadata: serializedMeta, + }, nil +} + func (e *EsdbSerializer) Deserialize( resolveEvent *esdb.ResolvedEvent, +) (domain.IDomainEvent, metadata.Metadata, error) { + eventType := resolveEvent.Event.EventType + data := resolveEvent.Event.Data + userMeta := resolveEvent.Event.UserMetadata + + payload, err := e.eventSerializer.Deserialize( + data, + eventType, + resolveEvent.Event.ContentType, + ) + if err != nil { + return nil, nil, err + } + + meta, err := e.metadataSerializer.Deserialize(userMeta) + if err != nil { + return nil, nil, err + } + + return payload, meta, nil +} + +func (e *EsdbSerializer) DeserializeObject( + resolveEvent *esdb.ResolvedEvent, ) (interface{}, metadata.Metadata, error) { eventType := resolveEvent.Event.EventType data := resolveEvent.Event.Data userMeta := resolveEvent.Event.UserMetadata - payload, err := e.eventSerializer.DeserializeEvent( + payload, err := e.eventSerializer.Deserialize( data, eventType, resolveEvent.Event.ContentType, diff --git a/internal/pkg/eventstroredb/esdb_subscription_check_point_repository.go b/internal/pkg/eventstroredb/esdb_subscription_check_point_repository.go index 5293ebc8..4665627a 100644 --- a/internal/pkg/eventstroredb/esdb_subscription_check_point_repository.go +++ b/internal/pkg/eventstroredb/esdb_subscription_check_point_repository.go @@ -6,10 +6,10 @@ import ( "io" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/events" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/events" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "emperror.dev/errors" "github.com/EventStore/EventStore-Client-Go/esdb" @@ -71,7 +71,7 @@ func (e *esdbSubscriptionCheckpointRepository) Load( return 0, errors.WrapIf(err, "stream.Recv") } - deserialized, _, err := e.esdbSerilizer.Deserialize(event) + deserialized, _, err := e.esdbSerilizer.DeserializeObject(event) if err != nil { return 0, err } @@ -98,7 +98,7 @@ func (e *esdbSubscriptionCheckpointRepository) Store( Event: events.NewEvent(typeMapper.GetTypeName(&CheckpointStored{})), } streamName := getCheckpointStreamName(subscriptionId) - eventData, err := e.esdbSerilizer.Serialize(checkpoint, nil) + eventData, err := e.esdbSerilizer.SerializeObject(checkpoint, nil) if err != nil { return errors.WrapIf(err, "esdbSerilizer.Serialize") } diff --git a/internal/pkg/eventstroredb/event_store.go b/internal/pkg/eventstroredb/event_store.go index b83fd283..b54488d2 100644 --- a/internal/pkg/eventstroredb/event_store.go +++ b/internal/pkg/eventstroredb/event_store.go @@ -5,17 +5,17 @@ import ( "fmt" "math" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts/store" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models" - appendResult "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/append_result" - streamName "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_name" - readPosition "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/read_position" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_position/truncatePosition" - expectedStreamVersion "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/models/stream_version" - esErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb/errors" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/store" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" + appendResult "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/append_result" + streamName "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_name" + readPosition "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/read_position" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_position/truncatePosition" + expectedStreamVersion "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models/stream_version" + esErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/errors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" "emperror.dev/errors" "github.com/EventStore/EventStore-Client-Go/esdb" @@ -39,7 +39,12 @@ func NewEventStoreDbEventStore( serializer *EsdbSerializer, tracer trace.Tracer, ) store.EventStore { - return &eventStoreDbEventStore{log: log, client: client, serializer: serializer, tracer: tracer} + return &eventStoreDbEventStore{ + log: log, + client: client, + serializer: serializer, + tracer: tracer, + } } func (e *eventStoreDbEventStore) StreamExists( @@ -59,11 +64,11 @@ func (e *eventStoreDbEventStore) StreamExists( }, 1) if err != nil { - return false, tracing.TraceErrFromSpan( + return false, utils.TraceErrStatusFromSpan( span, errors.WithMessage( esErrors.NewReadStreamError(err), - "[eventStoreDbEventStore_StreamExists:ReadStream] error in reading stream", + "error in reading stream", ), ) } @@ -104,22 +109,27 @@ func (e *eventStoreDbEventStore) AppendEvents( }, eventsData...) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WithMessage( esErrors.NewAppendToStreamError(err, streamName.String()), - "[eventStoreDbEventStore_AppendEvents:AppendToStream] error in appending to stream", + "error in appending to stream", ), ) } appendEventsResult = e.serializer.EsdbWriteResultToAppendEventResult(res) - span.SetAttributes(attribute.Object("AppendEventsResult", appendEventsResult)) + span.SetAttributes( + attribute.Object("AppendEventsResult", appendEventsResult), + ) e.log.Infow( - "[eventStoreDbEventStore_AppendEvents] events append to stream successfully", - logger.Fields{"AppendEventsResult": appendEventsResult, "StreamId": streamName.String()}, + "events append to stream successfully", + logger.Fields{ + "AppendEventsResult": appendEventsResult, + "StreamId": streamName.String(), + }, ) return appendEventsResult, nil @@ -141,11 +151,11 @@ func (e *eventStoreDbEventStore) AppendNewEvents( ctx, ) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WithMessage( esErrors.NewAppendToStreamError(err, streamName.String()), - "[eventStoreDbEventStore_AppendNewEvents:AppendEvents] error in appending to stream", + "error in appending to stream", ), ) } @@ -153,8 +163,11 @@ func (e *eventStoreDbEventStore) AppendNewEvents( span.SetAttributes(attribute.Object("AppendNewEvents", appendEventsResult)) e.log.Infow( - "[eventStoreDbEventStore_AppendNewEvents] events append to stream successfully", - logger.Fields{"AppendEventsResult": appendEventsResult, "StreamId": streamName.String()}, + "events append to stream successfully", + logger.Fields{ + "AppendEventsResult": appendEventsResult, + "StreamId": streamName.String(), + }, ) return appendEventsResult, nil @@ -174,41 +187,45 @@ func (e *eventStoreDbEventStore) ReadEvents( ctx, streamName.String(), esdb.ReadStreamOptions{ - Direction: esdb.Forwards, - From: e.serializer.StreamReadPositionToStreamPosition(readPosition), + Direction: esdb.Forwards, + From: e.serializer.StreamReadPositionToStreamPosition( + readPosition, + ), ResolveLinkTos: true, }, count) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WithMessage( esErrors.NewReadStreamError(err), - "[eventStoreDbEventStore_ReadEvents:ReadStream] error in reading stream", + "error in reading stream", ), ) } defer readStream.Close() - resolvedEvents, err := e.serializer.EsdbReadStreamToResolvedEvents(readStream) + resolvedEvents, err := e.serializer.EsdbReadStreamToResolvedEvents( + readStream, + ) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WrapIf( err, - "[eventStoreDbEventStore_ReadEvents.EsdbReadStreamToResolvedEvents] error in converting to resolved events", + "error in converting to resolved events", ), ) } events, err := e.serializer.ResolvedEventsToStreamEvents(resolvedEvents) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WrapIf( err, - "[eventStoreDbEventStore_ReadEvents.ResolvedEventsToStreamEvents] error in converting to stream events", + "error in converting to stream events", ), ) } @@ -221,7 +238,10 @@ func (e *eventStoreDbEventStore) ReadEventsWithMaxCount( readPosition readPosition.StreamReadPosition, ctx context.Context, ) ([]*models.StreamEvent, error) { - ctx, span := e.tracer.Start(ctx, "eventStoreDbEventStore.ReadEventsWithMaxCount") + ctx, span := e.tracer.Start( + ctx, + "eventStoreDbEventStore.ReadEventsWithMaxCount", + ) span.SetAttributes(attribute2.String("StreamName", streamName.String())) defer span.End() @@ -233,7 +253,10 @@ func (e *eventStoreDbEventStore) ReadEventsFromStart( count uint64, ctx context.Context, ) ([]*models.StreamEvent, error) { - ctx, span := e.tracer.Start(ctx, "eventStoreDbEventStore.ReadEventsFromStart") + ctx, span := e.tracer.Start( + ctx, + "eventStoreDbEventStore.ReadEventsFromStart", + ) span.SetAttributes(attribute2.String("StreamName", streamName.String())) defer span.End() @@ -246,7 +269,10 @@ func (e *eventStoreDbEventStore) ReadEventsBackwards( count uint64, ctx context.Context, ) ([]*models.StreamEvent, error) { - ctx, span := e.tracer.Start(ctx, "eventStoreDbEventStore.ReadEventsBackwards") + ctx, span := e.tracer.Start( + ctx, + "eventStoreDbEventStore.ReadEventsBackwards", + ) span.SetAttributes(attribute2.String("StreamName", streamName.String())) defer span.End() @@ -254,13 +280,15 @@ func (e *eventStoreDbEventStore) ReadEventsBackwards( ctx, streamName.String(), esdb.ReadStreamOptions{ - Direction: esdb.Backwards, - From: e.serializer.StreamReadPositionToStreamPosition(readPosition), + Direction: esdb.Backwards, + From: e.serializer.StreamReadPositionToStreamPosition( + readPosition, + ), ResolveLinkTos: true, }, count) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WithMessage( esErrors.NewReadStreamError(err), @@ -271,9 +299,11 @@ func (e *eventStoreDbEventStore) ReadEventsBackwards( defer readStream.Close() - resolvedEvents, err := e.serializer.EsdbReadStreamToResolvedEvents(readStream) + resolvedEvents, err := e.serializer.EsdbReadStreamToResolvedEvents( + readStream, + ) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WrapIf( err, @@ -284,7 +314,7 @@ func (e *eventStoreDbEventStore) ReadEventsBackwards( events, err := e.serializer.ResolvedEventsToStreamEvents(resolvedEvents) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WrapIf( err, @@ -301,11 +331,19 @@ func (e *eventStoreDbEventStore) ReadEventsBackwardsWithMaxCount( readPosition readPosition.StreamReadPosition, ctx context.Context, ) ([]*models.StreamEvent, error) { - ctx, span := e.tracer.Start(ctx, "eventStoreDbEventStore.ReadEventsBackwardsWithMaxCount") + ctx, span := e.tracer.Start( + ctx, + "eventStoreDbEventStore.ReadEventsBackwardsWithMaxCount", + ) span.SetAttributes(attribute2.String("StreamName", streamName.String())) defer span.End() - return e.ReadEventsBackwards(streamName, readPosition, uint64(math.MaxUint64), ctx) + return e.ReadEventsBackwards( + streamName, + readPosition, + uint64(math.MaxUint64), + ctx, + ) } func (e *eventStoreDbEventStore) ReadEventsBackwardsFromEnd( @@ -313,7 +351,10 @@ func (e *eventStoreDbEventStore) ReadEventsBackwardsFromEnd( count uint64, ctx context.Context, ) ([]*models.StreamEvent, error) { - ctx, span := e.tracer.Start(ctx, "eventStoreDbEventStore.ReadEventsBackwardsWithMaxCount") + ctx, span := e.tracer.Start( + ctx, + "eventStoreDbEventStore.ReadEventsBackwardsWithMaxCount", + ) span.SetAttributes(attribute2.String("StreamName", streamName.String())) defer span.End() @@ -331,7 +372,9 @@ func (e *eventStoreDbEventStore) TruncateStream( defer span.End() streamMetadata := esdb.StreamMetadata{} - streamMetadata.SetTruncateBefore(e.serializer.StreamTruncatePositionToInt64(truncatePosition)) + streamMetadata.SetTruncateBefore( + e.serializer.StreamTruncatePositionToInt64(truncatePosition), + ) writeResult, err := e.client.SetStreamMetadata( ctx, streamName.String(), @@ -342,11 +385,11 @@ func (e *eventStoreDbEventStore) TruncateStream( }, streamMetadata) if err != nil { - return nil, tracing.TraceErrFromSpan( + return nil, utils.TraceErrStatusFromSpan( span, errors.WithMessage( esErrors.NewTruncateStreamError(err, streamName.String()), - "[eventStoreDbEventStore_TruncateStream:SetStreamMetadata] error in truncating stream", + "error in truncating stream", ), ) } @@ -355,10 +398,13 @@ func (e *eventStoreDbEventStore) TruncateStream( e.log.Infow( fmt.Sprintf( - "[eventStoreDbEventStore.TruncateStream] stream with id %s truncated successfully", + "stream with id %s truncated successfully", streamName.String(), ), - logger.Fields{"WriteResult": writeResult, "StreamId": streamName.String()}, + logger.Fields{ + "WriteResult": writeResult, + "StreamId": streamName.String(), + }, ) return e.serializer.EsdbWriteResultToAppendEventResult(writeResult), nil @@ -382,11 +428,11 @@ func (e *eventStoreDbEventStore) DeleteStream( ), }) if err != nil { - return tracing.TraceErrFromSpan( + return utils.TraceErrStatusFromSpan( span, errors.WithMessage( esErrors.NewDeleteStreamError(err, streamName.String()), - "[eventStoreDbEventStore_DeleteStream:DeleteStream] error in deleting stream", + "error in deleting stream", ), ) } @@ -395,10 +441,13 @@ func (e *eventStoreDbEventStore) DeleteStream( e.log.Infow( fmt.Sprintf( - "[eventStoreDbEventStore.DeleteStream] stream with id %s deleted successfully", + "stream with id %s deleted successfully", streamName.String(), ), - logger.Fields{"DeleteResult": deleteResult, "StreamId": streamName.String()}, + logger.Fields{ + "DeleteResult": deleteResult, + "StreamId": streamName.String(), + }, ) return nil diff --git a/internal/pkg/eventstroredb/eventoredb_fx.go b/internal/pkg/eventstroredb/eventoredb_fx.go index 3e3e2d32..ac2cfdfe 100644 --- a/internal/pkg/eventstroredb/eventoredb_fx.go +++ b/internal/pkg/eventstroredb/eventoredb_fx.go @@ -4,8 +4,8 @@ import ( "context" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "github.com/EventStore/EventStore-Client-Go/esdb" "go.uber.org/fx" diff --git a/internal/pkg/eventstroredb/eventstroredb.go b/internal/pkg/eventstroredb/eventstroredb.go index 6d342215..a7dfbf51 100644 --- a/internal/pkg/eventstroredb/eventstroredb.go +++ b/internal/pkg/eventstroredb/eventstroredb.go @@ -1,7 +1,7 @@ package eventstroredb import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/config" "github.com/EventStore/EventStore-Client-Go/esdb" ) diff --git a/internal/pkg/eventstroredb/projection_builder.go b/internal/pkg/eventstroredb/projection_builder.go index f524db9d..df50f782 100644 --- a/internal/pkg/eventstroredb/projection_builder.go +++ b/internal/pkg/eventstroredb/projection_builder.go @@ -1,7 +1,7 @@ package eventstroredb import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts/projection" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/projection" ) type projectionsBuilder struct { diff --git a/internal/pkg/eventstroredb/projection_configurations.go b/internal/pkg/eventstroredb/projection_configurations.go index 3e39eea4..c5a9b93c 100644 --- a/internal/pkg/eventstroredb/projection_configurations.go +++ b/internal/pkg/eventstroredb/projection_configurations.go @@ -1,7 +1,7 @@ package eventstroredb import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts/projection" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/projection" ) type ProjectionsConfigurations struct { diff --git a/internal/pkg/eventstroredb/projections_builder.go b/internal/pkg/eventstroredb/projections_builder.go index fef482e5..ee63077b 100644 --- a/internal/pkg/eventstroredb/projections_builder.go +++ b/internal/pkg/eventstroredb/projections_builder.go @@ -1,7 +1,7 @@ package eventstroredb import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts/projection" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/projection" ) type ProjectionsBuilder interface { diff --git a/internal/pkg/eventstroredb/subscription_all_worker.go b/internal/pkg/eventstroredb/subscription_all_worker.go index c5382a44..1f3ad08d 100644 --- a/internal/pkg/eventstroredb/subscription_all_worker.go +++ b/internal/pkg/eventstroredb/subscription_all_worker.go @@ -5,12 +5,12 @@ import ( "fmt" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/es/contracts/projection" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/projection" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "emperror.dev/errors" "github.com/EventStore/EventStore-Client-Go/esdb" diff --git a/internal/pkg/fxapp/app_fx.go b/internal/pkg/fxapp/app_fx.go index a0822ba9..72ccb553 100644 --- a/internal/pkg/fxapp/app_fx.go +++ b/internal/pkg/fxapp/app_fx.go @@ -3,12 +3,12 @@ package fxapp import ( "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - logConfig "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/external/fxlog" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/logrous" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/models" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + logConfig "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/logrous" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" "go.uber.org/fx" ) diff --git a/internal/pkg/fxapp/application.go b/internal/pkg/fxapp/application.go index 8cdcc063..bf97fd9a 100644 --- a/internal/pkg/fxapp/application.go +++ b/internal/pkg/fxapp/application.go @@ -3,9 +3,9 @@ package fxapp import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/fxapp/contracts" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "go.uber.org/fx" ) @@ -17,7 +17,7 @@ type application struct { options []fx.Option logger logger.Logger fxapp *fx.App - environment environemnt.Environment + environment environment.Environment } func NewApplication( @@ -25,7 +25,7 @@ func NewApplication( decorates []interface{}, options []fx.Option, logger logger.Logger, - env environemnt.Environment, + env environment.Environment, ) contracts.Application { return &application{ provides: providers, @@ -109,6 +109,6 @@ func (a *application) Logger() logger.Logger { return a.logger } -func (a *application) Environment() environemnt.Environment { +func (a *application) Environment() environment.Environment { return a.environment } diff --git a/internal/pkg/fxapp/application_builder.go b/internal/pkg/fxapp/application_builder.go index af0ca4c8..1ab2a9f3 100644 --- a/internal/pkg/fxapp/application_builder.go +++ b/internal/pkg/fxapp/application_builder.go @@ -1,13 +1,13 @@ package fxapp import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/fxapp/contracts" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - loggerConfig "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/logrous" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/models" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + loggerConfig "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/logrous" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" "go.uber.org/fx" ) @@ -17,11 +17,11 @@ type applicationBuilder struct { decorates []interface{} options []fx.Option logger logger.Logger - environment environemnt.Environment + environment environment.Environment } -func NewApplicationBuilder(environments ...environemnt.Environment) contracts.ApplicationBuilder { - env := environemnt.ConfigAppEnv(environments...) +func NewApplicationBuilder(environments ...environment.Environment) contracts.ApplicationBuilder { + env := environment.ConfigAppEnv(environments...) var logger logger.Logger logoption, err := loggerConfig.ProvideLogConfig(env) @@ -70,6 +70,6 @@ func (a *applicationBuilder) Logger() logger.Logger { return a.logger } -func (a *applicationBuilder) Environment() environemnt.Environment { +func (a *applicationBuilder) Environment() environment.Environment { return a.environment } diff --git a/internal/pkg/fxapp/contracts/application.go b/internal/pkg/fxapp/contracts/application.go index e4036b6f..8b8713f7 100644 --- a/internal/pkg/fxapp/contracts/application.go +++ b/internal/pkg/fxapp/contracts/application.go @@ -3,8 +3,8 @@ package contracts import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "go.uber.org/fx" ) @@ -17,5 +17,5 @@ type Application interface { Stop(ctx context.Context) error Wait() <-chan fx.ShutdownSignal Logger() logger.Logger - Environment() environemnt.Environment + Environment() environment.Environment } diff --git a/internal/pkg/fxapp/contracts/application_builder.go b/internal/pkg/fxapp/contracts/application_builder.go index c138a8a2..de434d15 100644 --- a/internal/pkg/fxapp/contracts/application_builder.go +++ b/internal/pkg/fxapp/contracts/application_builder.go @@ -1,8 +1,8 @@ package contracts import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "go.uber.org/fx" ) @@ -19,5 +19,5 @@ type ApplicationBuilder interface { GetDecorates() []interface{} Options() []fx.Option Logger() logger.Logger - Environment() environemnt.Environment + Environment() environment.Environment } diff --git a/internal/pkg/fxapp/error_handler.go b/internal/pkg/fxapp/error_handler.go index 507f53ae..7618545c 100644 --- a/internal/pkg/fxapp/error_handler.go +++ b/internal/pkg/fxapp/error_handler.go @@ -1,7 +1,7 @@ package fxapp import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" ) type FxErrorHandler struct { diff --git a/internal/pkg/fxapp/test/test_app_fx.go b/internal/pkg/fxapp/test/test_app_fx.go index 74b61770..4107dfb8 100644 --- a/internal/pkg/fxapp/test/test_app_fx.go +++ b/internal/pkg/fxapp/test/test_app_fx.go @@ -3,10 +3,10 @@ package test import ( "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" "go.uber.org/fx" "go.uber.org/fx/fxtest" @@ -19,7 +19,7 @@ func CreateFxTestApp( invokes []interface{}, options []fx.Option, logger logger.Logger, - environment environemnt.Environment, + environment environment.Environment, ) *fxtest.App { var opts []fx.Option diff --git a/internal/pkg/fxapp/test/test_application.go b/internal/pkg/fxapp/test/test_application.go index 706de421..75cc4604 100644 --- a/internal/pkg/fxapp/test/test_application.go +++ b/internal/pkg/fxapp/test/test_application.go @@ -2,14 +2,11 @@ package test import ( "context" - "os" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/fxapp/contracts" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" - "github.com/spf13/viper" "go.uber.org/fx" "go.uber.org/fx/fxtest" ) @@ -20,7 +17,7 @@ type testApplication struct { options []fx.Option invokes []interface{} logger logger.Logger - env environemnt.Environment + env environment.Environment tb fxtest.TB fxtestApp *fxtest.App } @@ -29,7 +26,7 @@ func (a *testApplication) Logger() logger.Logger { return a.logger } -func (a *testApplication) Environment() environemnt.Environment { +func (a *testApplication) Environment() environment.Environment { return a.env } @@ -39,7 +36,7 @@ func NewTestApplication( decorates []interface{}, options []fx.Option, logger logger.Logger, - env environemnt.Environment, + env environment.Environment, ) contracts.Application { return &testApplication{ tb: tb, @@ -94,7 +91,7 @@ func (a *testApplication) Wait() <-chan fx.ShutdownSignal { } func (a *testApplication) createFxTest() *fxtest.App { - a.fixTestEnvironmentWorkingDirectory() + // a.fixTestEnvironmentWorkingDirectory() // build phase of container will do in this stage, containing provides and invokes but app not started yet and will be started in the future with `fxApp.Register` fxTestApp := CreateFxTestApp( @@ -111,15 +108,15 @@ func (a *testApplication) createFxTest() *fxtest.App { return fxTestApp } -func (a *testApplication) fixTestEnvironmentWorkingDirectory() { - currentWD, _ := os.Getwd() - a.logger.Infof("Current test working directory is: %s", currentWD) - - rootDir := viper.GetString(constants.AppRootPath) - if rootDir != "" { - _ = os.Chdir(rootDir) - - newWD, _ := os.Getwd() - a.logger.Infof("New test working directory is: %s", newWD) - } -} +//func (a *testApplication) fixTestEnvironmentWorkingDirectory() { +// currentWD, _ := os.Getwd() +// a.logger.Infof("Current test working directory is: %s", currentWD) +// +// rootDir := viper.GetString(constants.AppRootPath) +// if rootDir != "" { +// _ = os.Chdir(rootDir) +// +// newWD, _ := os.Getwd() +// a.logger.Infof("New test working directory is: %s", newWD) +// } +//} diff --git a/internal/pkg/fxapp/test/test_application_builder.go b/internal/pkg/fxapp/test/test_application_builder.go index 5213caa2..b2b5b9ec 100644 --- a/internal/pkg/fxapp/test/test_application_builder.go +++ b/internal/pkg/fxapp/test/test_application_builder.go @@ -1,9 +1,9 @@ package test import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/fxapp" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" "go.uber.org/fx/fxtest" ) @@ -16,7 +16,7 @@ type TestApplicationBuilder struct { func NewTestApplicationBuilder(tb fxtest.TB) *TestApplicationBuilder { return &TestApplicationBuilder{ TB: tb, - ApplicationBuilder: fxapp.NewApplicationBuilder(environemnt.Test), + ApplicationBuilder: fxapp.NewApplicationBuilder(environment.Test), } } @@ -27,7 +27,7 @@ func (a *TestApplicationBuilder) Build() contracts.Application { a.GetDecorates(), a.Options(), a.Logger(), - environemnt.Test, + environment.Test, ) return app diff --git a/internal/pkg/go.mod b/internal/pkg/go.mod index ffb1bcb7..c98c54de 100644 --- a/internal/pkg/go.mod +++ b/internal/pkg/go.mod @@ -1,6 +1,6 @@ -module github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg +module github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg -go 1.21 +go 1.22 require ( emperror.dev/errors v0.8.1 @@ -10,13 +10,15 @@ require ( github.com/ahmetb/go-linq/v3 v3.2.0 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/avast/retry-go v3.0.0+incompatible + github.com/brianvoe/gofakeit/v6 v6.25.0 github.com/caarlos0/env/v8 v8.0.0 - github.com/docker/docker v24.0.5+incompatible + github.com/docker/docker v24.0.6+incompatible github.com/docker/go-connections v0.4.0 github.com/doug-martin/goqu/v9 v9.18.0 - github.com/elastic/go-elasticsearch/v8 v8.9.0 + github.com/elastic/go-elasticsearch/v8 v8.10.0 + github.com/glebarez/sqlite v1.10.0 github.com/go-playground/validator v9.31.0+incompatible - github.com/go-resty/resty/v2 v2.7.0 + github.com/go-resty/resty/v2 v2.9.1 github.com/go-testfixtures/testfixtures/v3 v3.9.0 github.com/goccy/go-json v0.10.2 github.com/goccy/go-reflect v1.2.0 @@ -33,83 +35,100 @@ require ( github.com/labstack/echo/v4 v4.11.1 github.com/lib/pq v1.10.9 github.com/mcuadros/go-defaults v1.2.0 - github.com/mehdihadeli/go-mediatr v1.1.10 + github.com/mehdihadeli/go-mediatr v1.3.0 github.com/michaelklishin/rabbit-hole v1.5.0 github.com/mitchellh/mapstructure v1.5.0 github.com/nolleh/caption_json_formatter v0.2.2 + github.com/onsi/ginkgo/v2 v2.12.1 + github.com/onsi/gomega v1.28.0 github.com/orlangure/gnomock v0.30.0 github.com/ory/dockertest/v3 v3.10.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/pressly/goose/v3 v3.15.0 - github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_golang v1.17.0 github.com/rabbitmq/amqp091-go v1.8.1 github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 - github.com/redis/go-redis/v9 v9.0.5 + github.com/redis/go-redis/v9 v9.2.1 github.com/samber/lo v1.38.1 github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 - github.com/testcontainers/testcontainers-go v0.23.0 - github.com/testcontainers/testcontainers-go/modules/postgres v0.23.0 - github.com/uptrace/bun v1.1.14 - github.com/uptrace/bun/dialect/pgdialect v1.1.14 - github.com/uptrace/bun/driver/pgdriver v1.1.14 + github.com/testcontainers/testcontainers-go v0.25.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.25.0 + github.com/ulule/limiter/v3 v3.11.2 + github.com/uptrace/bun v1.1.16 + github.com/uptrace/bun/dialect/pgdialect v1.1.16 + github.com/uptrace/bun/driver/pgdriver v1.1.16 + github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3 + github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3 go.mongodb.org/mongo-driver v1.12.1 - go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.43.0 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.43.0 - go.opentelemetry.io/contrib/propagators/ot v1.18.0 - go.opentelemetry.io/otel v1.17.0 - go.opentelemetry.io/otel/exporters/jaeger v1.17.0 - go.opentelemetry.io/otel/exporters/prometheus v0.40.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.17.0 - go.opentelemetry.io/otel/exporters/zipkin v1.17.0 - go.opentelemetry.io/otel/metric v1.17.0 - go.opentelemetry.io/otel/sdk v1.17.0 - go.opentelemetry.io/otel/sdk/metric v0.40.0 - go.opentelemetry.io/otel/trace v1.17.0 + go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 + go.opentelemetry.io/contrib/instrumentation/host v0.45.0 + go.opentelemetry.io/contrib/propagators/ot v1.20.0 + go.opentelemetry.io/otel v1.19.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 + go.opentelemetry.io/otel/exporters/prometheus v0.42.0 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 + go.opentelemetry.io/otel/exporters/zipkin v1.19.0 + go.opentelemetry.io/otel/metric v1.19.0 + go.opentelemetry.io/otel/sdk v1.19.0 + go.opentelemetry.io/otel/sdk/metric v1.19.0 + go.opentelemetry.io/otel/trace v1.19.0 go.uber.org/fx v1.20.0 - go.uber.org/zap v1.25.0 - google.golang.org/grpc v1.57.0 + go.uber.org/zap v1.26.0 + google.golang.org/grpc v1.58.2 gorm.io/driver/postgres v1.5.2 - gorm.io/gorm v1.25.4 + gorm.io/gorm v1.25.5 + gorm.io/plugin/opentelemetry v0.1.4 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/ClickHouse/ch-go v0.58.2 // indirect - github.com/ClickHouse/clickhouse-go/v2 v2.13.4 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.14.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/hcsshim v0.11.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/containerd/containerd v1.7.5 // indirect + github.com/containerd/containerd v1.7.6 // indirect github.com/containerd/continuity v0.4.2 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/docker/cli v24.0.5+incompatible // indirect + github.com/docker/cli v24.0.6+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/elastic-transport-go/v8 v8.3.0 // indirect github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.6.1 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -125,11 +144,12 @@ require ( github.com/jackc/puddle v1.3.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.0 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.4 // indirect + github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect @@ -141,7 +161,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc4 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/opencontainers/runc v1.1.9 // indirect github.com/openzipkin/zipkin-go v0.4.2 // indirect github.com/paulmach/orb v0.10.0 // indirect @@ -149,24 +169,31 @@ require ( github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect + github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/segmentio/asm v1.2.0 // indirect + github.com/shirou/gopsutil/v3 v3.23.9 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/streadway/amqp v1.1.0 // indirect github.com/stretchr/objx v0.5.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect @@ -175,6 +202,10 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -187,10 +218,16 @@ require ( golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mellium.im/sasl v0.3.1 // indirect + modernc.org/libc v1.24.1 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.6.0 // indirect + modernc.org/sqlite v1.25.0 // indirect ) diff --git a/internal/pkg/go.sum b/internal/pkg/go.sum index fb555b42..d740df7a 100644 --- a/internal/pkg/go.sum +++ b/internal/pkg/go.sum @@ -25,8 +25,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= -cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= +cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -55,8 +55,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= -github.com/ClickHouse/clickhouse-go/v2 v2.13.4 h1:NcvYN9ONZn3vlPMfQVUBSG5LKz+1y2wk4vaaz5QZXIg= -github.com/ClickHouse/clickhouse-go/v2 v2.13.4/go.mod h1:u1AUh8E0XqN1sU1EDzbiGLTI4KWOd+lOHimNSsdyJec= +github.com/ClickHouse/clickhouse-go/v2 v2.14.1 h1:5C2hhmZEGUVdy8CPpY3iPpfBv2kRbx5iOcflU49Rzws= +github.com/ClickHouse/clickhouse-go/v2 v2.14.1/go.mod h1:PHqbMvJTQ0EI4a1vJhmbmL/Ajr+Cin2O+WJjnYctJvg= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/EventStore/EventStore-Client-Go v1.0.2 h1:onM2TIInLhWUJwUQ/5a/8blNrrbhwrtm7Tpmg13ohiw= @@ -68,8 +68,8 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= -github.com/Microsoft/hcsshim v0.10.0-rc.8/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM= +github.com/Microsoft/hcsshim v0.11.1 h1:hJ3s7GbWlGK4YVV92sO88BQSyF4ZLVy7/awqOlPxFbA= +github.com/Microsoft/hcsshim v0.11.1/go.mod h1:nFJmaO4Zr5Y7eADdFOpYswDDlNVbvcIJJNJLECr5JQg= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= @@ -87,10 +87,14 @@ github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= +github.com/brianvoe/gofakeit/v6 v6.25.0 h1:ZpFjktOpLZUeF8q223o0rUuXtA+m5qW5srjvVi+JkXk= +github.com/brianvoe/gofakeit/v6 v6.25.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= @@ -113,8 +117,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/containerd v1.7.5 h1:i9T9XpAWMe11BHMN7pu1BZqOGjXaKTPyz2v+KYOZgkY= -github.com/containerd/containerd v1.7.5/go.mod h1:ieJNCSzASw2shSGYLHx8NAE7WsZ/gEigo5fQ78W5Zvw= +github.com/containerd/containerd v1.7.6 h1:oNAVsnhPoy4BTPQivLgTzI9Oleml9l/+eYIDYXRCYo8= +github.com/containerd/containerd v1.7.6/go.mod h1:SY6lrkkuJT40BVNO37tlYTSnKJnP5AXBc0fhx0q+TJ4= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= @@ -140,12 +144,12 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= github.com/dhui/dktest v0.3.16/go.mod h1:gYaA3LRmM8Z4vJl2MA0THIigJoZrwOansEOsp+kqxp0= -github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc= -github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= +github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= -github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= +github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -159,16 +163,16 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/elastic/elastic-transport-go/v8 v8.0.0-20230329154755-1a3c63de0db6/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI= github.com/elastic/elastic-transport-go/v8 v8.3.0 h1:DJGxovyQLXGr62e9nDMPSxRyWION0Bh6d9eCFBriiHo= github.com/elastic/elastic-transport-go/v8 v8.3.0/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI= -github.com/elastic/go-elasticsearch/v8 v8.9.0 h1:8xtmYjUkqtahl50E0Bg/wjKI7K63krJrrLipbNj/fCU= -github.com/elastic/go-elasticsearch/v8 v8.9.0/go.mod h1:NGmpvohKiRHXI0Sw4fuUGn6hYOmAXlyCphKpzVBiqDE= +github.com/elastic/go-elasticsearch/v8 v8.10.0 h1:ALg3DMxSrx07YmeMNcfPf7cFh1Ep2+Qa19EOXTbwr2k= +github.com/elastic/go-elasticsearch/v8 v8.10.0/go.mod h1:NGmpvohKiRHXI0Sw4fuUGn6hYOmAXlyCphKpzVBiqDE= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.10.1 h1:c0g45+xCJhdgFGw7a5QAfdS4byAbud7miNWJ1WwEVf8= -github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= @@ -178,6 +182,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= +github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= @@ -192,18 +200,23 @@ 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/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM= +github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY= github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -227,6 +240,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -288,6 +303,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -302,6 +319,8 @@ github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e h1:XmA6L9IP github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e/go.mod h1:AFIo+02s+12CEg8Gzz9kzhCbmbq6JcKNrhHffCGA9z4= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -392,8 +411,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -425,6 +444,9 @@ github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -448,8 +470,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc= github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY= -github.com/mehdihadeli/go-mediatr v1.1.10 h1:NAzg4065c90lgYeb+Vzbd2WKH0tUFpxzL0mpx6hkU/A= -github.com/mehdihadeli/go-mediatr v1.1.10/go.mod h1:lwgZl7qVL/RKomObBblhG3uEte/r4nJDV95Vd+nGrMw= +github.com/mehdihadeli/go-mediatr v1.3.0 h1:hrb5Scp/nsiR3Y62mjZ0Tc5UX/dRJl4nDFkINBEIESA= +github.com/mehdihadeli/go-mediatr v1.3.0/go.mod h1:lsG+hyH+pEOhmZiZl0KPO72BcZiEReF03CBk4GVJB0k= github.com/michaelklishin/rabbit-hole v1.5.0 h1:Bex27BiFDsijCM9D0ezSHqyy0kehpYHuNKaPqq/a4RM= github.com/michaelklishin/rabbit-hole v1.5.0/go.mod h1:vvI1uOitYZi0O5HEGXhaWC1XT80Gy+HvFheJ+5Krlhk= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -477,15 +499,17 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA= +github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= -github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc95/go.mod h1:z+bZxa/+Tz/FmYVWkhUajJdzFeOqjc5vrqskhVyHGUM= github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM= @@ -516,17 +540,20 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pressly/goose/v3 v3.15.0 h1:6tY5aDqFknY6VZkorFGgZtWygodZQxfmmEF4rqyJW9k= github.com/pressly/goose/v3 v3.15.0/go.mod h1:LlIo3zGccjb/YUgG+Svdb9Er14vefRdlDI7URCDrwYo= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= @@ -534,8 +561,9 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJu github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o= github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -556,6 +584,12 @@ github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7 github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= +github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= @@ -567,8 +601,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= @@ -605,19 +639,31 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/testcontainers/testcontainers-go v0.23.0 h1:ERYTSikX01QczBLPZpqsETTBO7lInqEP349phDOVJVs= -github.com/testcontainers/testcontainers-go v0.23.0/go.mod h1:3gzuZfb7T9qfcH2pHpV4RLlWrPjeWNQah6XlYQ32c4I= -github.com/testcontainers/testcontainers-go/modules/postgres v0.23.0 h1:OEGUC1YTN1RyS4xqsHmlyYkBWm9lMJcswoV4JSHJQOM= -github.com/testcontainers/testcontainers-go/modules/postgres v0.23.0/go.mod h1:YnqIhPwhjqVbJBuvSRJS6pa9Cy1PDRJcrM6T63Uw2ms= +github.com/testcontainers/testcontainers-go v0.25.0 h1:erH6cQjsaJrH+rJDU9qIf89KFdhK0Bft0aEZHlYC3Vs= +github.com/testcontainers/testcontainers-go v0.25.0/go.mod h1:4sC9SiJyzD1XFi59q8umTQYWxnkweEc5OjVtTUlJzqQ= +github.com/testcontainers/testcontainers-go/modules/postgres v0.25.0 h1:8WNK1Edo9ohRYPrDCXWdoVY2cbg/oFh9y5uWZGSBESo= +github.com/testcontainers/testcontainers-go/modules/postgres v0.25.0/go.mod h1:XpwOhyUXheL31hz73L8be8maW1rQq8H48x5qZeHtYr0= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun v1.1.14 h1:S5vvNnjEynJ0CvnrBOD7MIRW7q/WbtvFXrdfy0lddAM= -github.com/uptrace/bun v1.1.14/go.mod h1:RHk6DrIisO62dv10pUOJCz5MphXThuOTpVNYEYv7NI8= -github.com/uptrace/bun/dialect/pgdialect v1.1.14 h1:b7+V1KDJPQSFYgkG/6YLXCl2uvwEY3kf/GSM7hTHRDY= -github.com/uptrace/bun/dialect/pgdialect v1.1.14/go.mod h1:v6YiaXmnKQ2FlhRD2c0ZfKd+QXH09pYn4H8ojaavkKk= -github.com/uptrace/bun/driver/pgdriver v1.1.14 h1:V2Etm7mLGS3mhx8ddxZcUnwZLX02Jmq9JTlo0sNVDhA= -github.com/uptrace/bun/driver/pgdriver v1.1.14/go.mod h1:D4FjWV9arDYct6sjMJhFoyU71SpllZRHXFRRP2Kd0Kw= +github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA= +github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI= +github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A= +github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8= +github.com/uptrace/bun/dialect/pgdialect v1.1.16 h1:eUPZ+YCJ69BA+W1X1ZmpOJSkv1oYtinr0zCXf7zCo5g= +github.com/uptrace/bun/dialect/pgdialect v1.1.16/go.mod h1:KQjfx/r6JM0OXfbv0rFrxAbdkPD7idK8VitnjIV9fZI= +github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7QiNOCPLqPFJjK4= +github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= +github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3 h1:m5eNyOhch/7tyK6aN6eRRpNoD1vM8PNh64dA05X22Js= +github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3/go.mod h1:APPUXm9BbpH7NFkfpbw04raZSitzl19/3NOCu0rbI4E= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3 h1:LyGS9cIZV0YVhE81zwfMhIE2l2flcj3wn5IoK4VkbWA= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3/go.mod h1:RvCYhPchLhvQ9l9C9goblbgO7BaKt597kBMf5mgKyo0= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3 h1:2na5W81H38Z4qXCQCuzlcdSMiTWgPJ6XeZIArq6VIJE= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3/go.mod h1:9IVEh9mPv3NwFf99dVLX15FqVgdpZJ8RMDo/Cr0vK74= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -626,8 +672,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/msgpack/v5 v5.4.0 h1:hRM0digJwyR6vll33NNAwCFguy5JuBD6jxDmQP3l608= +github.com/vmihailenco/msgpack/v5 v5.4.0/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= @@ -657,6 +703,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= @@ -668,32 +716,42 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.43.0 h1:pcWR7mkuO5XMK3f0KeGpr70OTR9/ikPk0D1hHEd5dp4= -go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.43.0/go.mod h1:azTPpa9PvRDSNU/lQbe2CRDQoTcau5moOjS4EzhpyAY= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.43.0 h1:7XZai4VhA473clBrOqqHdjHBImGfyEtv0qW4nnn/kAo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.43.0/go.mod h1:1WpsUwjQrUJSNugfMlPn0rPRJ9Do7wwBgTBPK7MLiS4= -go.opentelemetry.io/contrib/propagators/b3 v1.18.0 h1:hhSlPVi9AQwOmbMmptPNLfRZOLgENdRM2kb7z9LFe1A= -go.opentelemetry.io/contrib/propagators/b3 v1.18.0/go.mod h1:qtt+pEu23D7UVP+j33G4i7LopmVu8/6/IwGu3hEm100= -go.opentelemetry.io/contrib/propagators/ot v1.18.0 h1:VmzxO7BjUU6oo0ChcKuGdKaSR0vchPxwahHZl64zVUM= -go.opentelemetry.io/contrib/propagators/ot v1.18.0/go.mod h1:5VwcOJ7OjS0uPxaxuwKHwJtkt+EAC+cgjXleXMe51z4= -go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= -go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= -go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= -go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= -go.opentelemetry.io/otel/exporters/prometheus v0.40.0 h1:9h6lCssr1j5aYVvWT6oc+ERB6R034zmsHjBRLyxrAR8= -go.opentelemetry.io/otel/exporters/prometheus v0.40.0/go.mod h1:5USWZ0ovyQB5CIM3IO3bGRSoDPMXiT3t+15gu8Zo9HQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.17.0 h1:Ut6hgtYcASHwCzRHkXEtSsM251cXJPW+Z9DyLwEn6iI= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.17.0/go.mod h1:TYeE+8d5CjrgBa0ZuRaDeMpIC1xZ7atg4g+nInjuSjc= -go.opentelemetry.io/otel/exporters/zipkin v1.17.0 h1:oi5+xMN3pflqWSd4EX6FiO+Cn3KbFBBzeQmD5LMIf0c= -go.opentelemetry.io/otel/exporters/zipkin v1.17.0/go.mod h1:pNir+S6/f0HFGfbXhobXLTFu60KtAzw8aGSUpt9A6VU= -go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc= -go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= -go.opentelemetry.io/otel/sdk v1.17.0 h1:FLN2X66Ke/k5Sg3V623Q7h7nt3cHXaW1FOvKKrW0IpE= -go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZFNUBlSgmDFQ= -go.opentelemetry.io/otel/sdk/metric v0.40.0 h1:qOM29YaGcxipWjL5FzpyZDpCYrDREvX0mVlmXdOjCHU= -go.opentelemetry.io/otel/sdk/metric v0.40.0/go.mod h1:dWxHtdzdJvg+ciJUKLTKwrMe5P6Dv3FyDbh8UkfgkVs= -go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= -go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= +go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0 h1:bldpPC7XAv7f7LKTwNfRkNdzRhjtXaWybZFFa16dAb8= +go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0/go.mod h1:xhkNpJG3D+kmuaciNTco7cdK27Fb77J9Iqcq5CMe4Y8= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= +go.opentelemetry.io/contrib/instrumentation/host v0.45.0 h1:1uzNKJDqZ6y6F5J6aKWgJjRREpKiGhBvKHlWon/bqB4= +go.opentelemetry.io/contrib/instrumentation/host v0.45.0/go.mod h1:vlqPvzDsmB4+jlERxBRXsdLCD6Q0LoBzxHqNXp3qvG4= +go.opentelemetry.io/contrib/propagators/ot v1.20.0 h1:duH7mgL6VGQH7e7QEAVOFkCQXWpCb4PjTtrhdrYrJRQ= +go.opentelemetry.io/contrib/propagators/ot v1.20.0/go.mod h1:gijQzxOq0JLj9lyZhTvqjDddGV/zaNagpPIn+2r8CEI= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 h1:ZtfnDL+tUrs1F0Pzfwbg2d59Gru9NCH3bgSHBM6LDwU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 h1:NmnYCiR0qNufkldjVvyQfZTHSdzeHoZ41zggMsdMcLM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0/go.mod h1:UVAO61+umUsHLtYb8KXXRoHtxUkdOPkYidzW3gipRLQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +go.opentelemetry.io/otel/exporters/prometheus v0.42.0 h1:jwV9iQdvp38fxXi8ZC+lNpxjK16MRcZlpDYvbuO1FiA= +go.opentelemetry.io/otel/exporters/prometheus v0.42.0/go.mod h1:f3bYiqNqhoPxkvI2LrXqQVC546K7BuRDL/kKuxkujhA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0 h1:4jJuoeOo9W6hZnz+r046fyoH5kykZPRvKfUXJVfMpB0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0/go.mod h1:/MtYTE1SfC2QIcE0bDot6fIX+h+WvXjgTqgn9P0LNPE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0 h1:EGY0h5mGliP9o/nIkVuLI0vRiQqmsYOcbwCuotksO1o= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0/go.mod h1:JQgTGJP11yi3o4GHzIWYodhPisxANdqxF1eHwDSnJrI= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= +go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -720,8 +778,8 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -780,6 +838,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -817,10 +876,10 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -832,8 +891,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -846,6 +905,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -864,6 +924,7 @@ golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -890,6 +951,7 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -911,14 +973,19 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -930,11 +997,13 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -997,6 +1066,7 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1070,8 +1140,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1088,8 +1162,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1135,8 +1209,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= -gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw= -gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c= +gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/plugin/opentelemetry v0.1.4 h1:7p0ocWELjSSRI7NCKPW2mVe6h43YPini99sNJcbsTuc= +gorm.io/plugin/opentelemetry v0.1.4/go.mod h1:tndJHOdvPT0pyGhOb8E2209eXJCUxhC5UpKw7bGVWeI= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/internal/pkg/gorm_postgres/db.go b/internal/pkg/gorm_postgres/db.go deleted file mode 100644 index 0966a505..00000000 --- a/internal/pkg/gorm_postgres/db.go +++ /dev/null @@ -1,87 +0,0 @@ -package gormPostgres - -import ( - "database/sql" - "fmt" - - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/external/gromlog" - - "emperror.dev/errors" - "github.com/uptrace/bun/driver/pgdriver" - gormPostgres "gorm.io/driver/postgres" - "gorm.io/gorm" -) - -func NewGorm(cfg *GormOptions) (*gorm.DB, error) { - if cfg.DBName == "" { - return nil, errors.New("DBName is required in the config.") - } - - err := createDB(cfg) - if err != nil { - return nil, err - } - - var dataSourceName string - dataSourceName = fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s", - cfg.Host, - cfg.Port, - cfg.User, - cfg.DBName, - cfg.Password, - ) - - gormDb, err := gorm.Open( - gormPostgres.Open(dataSourceName), - &gorm.Config{Logger: gromlog.NewGormCustomLogger(defaultLogger.Logger)}, - ) - if err != nil { - return nil, err - } - - return gormDb, nil -} - -func NewSQLDB(orm *gorm.DB) (*sql.DB, error) { return orm.DB() } - -func createDB(cfg *GormOptions) error { - // we should choose a default database in the connection, but because we don't have a database yet we specify postgres default database 'postgres' - datasource := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", - cfg.User, - cfg.Password, - cfg.Host, - cfg.Port, - "postgres", - ) - - sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(datasource))) - - var exists int - rows, err := sqldb.Query( - fmt.Sprintf("SELECT 1 FROM pg_catalog.pg_database WHERE datname='%s'", cfg.DBName), - ) - if err != nil { - return err - } - - if rows.Next() { - err = rows.Scan(&exists) - if err != nil { - return err - } - } - - if exists == 1 { - return nil - } - - _, err = sqldb.Exec(fmt.Sprintf("CREATE DATABASE %s", cfg.DBName)) - if err != nil { - return err - } - - defer sqldb.Close() - - return nil -} diff --git a/internal/pkg/gorm_postgres/gorm_options.go b/internal/pkg/gorm_postgres/gorm_options.go deleted file mode 100644 index 7b78b78b..00000000 --- a/internal/pkg/gorm_postgres/gorm_options.go +++ /dev/null @@ -1,38 +0,0 @@ -package gormPostgres - -import ( - "fmt" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" - - "github.com/iancoleman/strcase" -) - -var optionName = strcase.ToLowerCamel(typeMapper.GetTypeNameByT[GormOptions]()) - -type GormOptions struct { - Host string `mapstructure:"host"` - Port int `mapstructure:"port"` - User string `mapstructure:"user"` - DBName string `mapstructure:"dbName"` - SSLMode bool `mapstructure:"sslMode"` - Password string `mapstructure:"password"` -} - -func (h *GormOptions) Dns() string { - datasource := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", - h.User, - h.Password, - h.Host, - h.Port, - h.DBName, - ) - - return datasource -} - -func provideConfig(environment environemnt.Environment) (*GormOptions, error) { - return config.BindConfigKey[*GormOptions](optionName, environment) -} diff --git a/internal/pkg/gorm_postgres/helpers.go b/internal/pkg/gorm_postgres/helpers.go deleted file mode 100644 index 1e19bad2..00000000 --- a/internal/pkg/gorm_postgres/helpers.go +++ /dev/null @@ -1,61 +0,0 @@ -package gormPostgres - -import ( - "context" - "fmt" - "strings" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils" - - "emperror.dev/errors" - "gorm.io/gorm" -) - -// Ref: https://dev.to/rafaelgfirmino/pagination-using-gorm-scopes-3k5f - -func Paginate[T any]( - ctx context.Context, - listQuery *utils.ListQuery, - db *gorm.DB, -) (*utils.ListResult[T], error) { - var items []T - var totalRows int64 - db.Model(items).WithContext(ctx).Count(&totalRows) - - // generate where query - query := db.WithContext(ctx). - Offset(listQuery.GetOffset()). - Limit(listQuery.GetLimit()). - Order(listQuery.GetOrderBy()) - - if listQuery.Filters != nil { - for _, filter := range listQuery.Filters { - column := filter.Field - action := filter.Comparison - value := filter.Value - - switch action { - case "equals": - whereQuery := fmt.Sprintf("%s = ?", column) - query = query.WithContext(ctx).Where(whereQuery, value) - break - case "contains": - whereQuery := fmt.Sprintf("%s LIKE ?", column) - query = query.WithContext(ctx).Where(whereQuery, "%"+value+"%") - break - case "in": - whereQuery := fmt.Sprintf("%s IN (?)", column) - queryArray := strings.Split(value, ",") - query = query.WithContext(ctx).Where(whereQuery, queryArray) - break - - } - } - } - - if err := query.Find(&items).Error; err != nil { - return nil, errors.WrapIf(err, "error in finding products.") - } - - return utils.NewListResult[T](items, listQuery.GetSize(), listQuery.GetPage(), totalRows), nil -} diff --git a/internal/pkg/gorm_postgres/repository/gorm_generic_repository_test.go b/internal/pkg/gorm_postgres/repository/gorm_generic_repository_test.go deleted file mode 100644 index 2846bb06..00000000 --- a/internal/pkg/gorm_postgres/repository/gorm_generic_repository_test.go +++ /dev/null @@ -1,487 +0,0 @@ -package repository - -import ( - "context" - "log" - "testing" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data/specification" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mapper" - gorm2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/testcontainer/gorm" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils" - - uuid "github.com/satori/go.uuid" - "github.com/stretchr/testify/assert" - "gorm.io/gorm" - - _ "github.com/lib/pq" // postgres driver -) - -// Product is a domain_events entity -type Product struct { - ID uuid.UUID - Name string - Weight int - IsAvailable bool -} - -// ProductGorm is DTO used to map Product entity to database -type ProductGorm struct { - ID uuid.UUID `gorm:"primaryKey;column:id"` - Name string `gorm:"column:name"` - Weight int `gorm:"column:weight"` - IsAvailable bool `gorm:"column:is_available"` -} - -func init() { - err := mapper.CreateMap[*ProductGorm, *Product]() - if err != nil { - log.Fatal(err) - } - - err = mapper.CreateMap[*Product, *ProductGorm]() - if err != nil { - log.Fatal(err) - } -} - -func Test_Add(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepository(ctx, t) - - product := &ProductGorm{ - ID: uuid.NewV4(), - Name: "added_product", - Weight: 100, - IsAvailable: true, - } - - err = repository.Add(ctx, product) - if err != nil { - t.Fatal(err) - } - - p, err := repository.GetById(ctx, product.ID) - if err != nil { - return - } - - assert.NotNil(t, p) - assert.Equal(t, product.ID, p.ID) -} - -func Test_Add_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - - product := &Product{ - ID: uuid.NewV4(), - Name: "added_product", - Weight: 100, - IsAvailable: true, - } - - err = repository.Add(ctx, product) - if err != nil { - t.Fatal(err) - } - - p, err := repository.GetById(ctx, product.ID) - if err != nil { - return - } - - assert.NotNil(t, p) - assert.Equal(t, product.ID, p.ID) -} - -func Test_Get_By_Id(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - - all, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - return - } - p := all.Items[0] - - testCases := []struct { - Name string - ProductId uuid.UUID - ExpectResult *ProductGorm - }{ - { - Name: "ExistingProduct", - ProductId: p.ID, - ExpectResult: p, - }, - { - Name: "NonExistingProduct", - ProductId: uuid.NewV4(), - ExpectResult: nil, - }, - } - - for _, c := range testCases { - c := c - t.Run(c.Name, func(t *testing.T) { - t.Parallel() - res, err := repository.GetById(ctx, c.ProductId) - if c.ExpectResult == nil { - assert.Error(t, err) - assert.True(t, customErrors.IsNotFoundError(err)) - assert.Nil(t, res) - } else { - assert.NoError(t, err) - assert.NotNil(t, res) - assert.Equal(t, p.ID, res.ID) - } - }) - } -} - -func Test_Get_By_Id_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - - all, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - return - } - p := all.Items[0] - - testCases := []struct { - Name string - ProductId uuid.UUID - ExpectResult *Product - }{ - { - Name: "ExistingProduct", - ProductId: p.ID, - ExpectResult: p, - }, - { - Name: "NonExistingProduct", - ProductId: uuid.NewV4(), - ExpectResult: nil, - }, - } - - for _, c := range testCases { - c := c - t.Run(c.Name, func(t *testing.T) { - t.Parallel() - res, err := repository.GetById(ctx, c.ProductId) - if c.ExpectResult == nil { - assert.Error(t, err) - assert.True(t, customErrors.IsNotFoundError(err)) - assert.Nil(t, res) - } else { - assert.NoError(t, err) - assert.NotNil(t, res) - assert.Equal(t, p.ID, res.ID) - } - }) - } -} - -func Test_Get_All(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - - models, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } - - assert.NotEmpty(t, models.Items) -} - -func Test_Get_All_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - - models, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } - - assert.NotEmpty(t, models.Items) -} - -func Test_Search(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - - models, err := repository.Search(ctx, "seed_product1", utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } - - assert.NotEmpty(t, models.Items) - assert.Equal(t, len(models.Items), 1) -} - -func Test_Search_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) - - models, err := repository.Search(ctx, "seed_product1", utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } - - assert.NotEmpty(t, models.Items) - assert.Equal(t, len(models.Items), 1) -} - -func Test_Where(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepository(ctx, t) - - models, err := repository.GetByFilter(ctx, map[string]interface{}{"name": "seed_product1"}) - if err != nil { - t.Fatal(err) - } - - assert.NotEmpty(t, models) - assert.Equal(t, len(models), 1) -} - -func Test_Where_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) - - models, err := repository.GetByFilter(ctx, map[string]interface{}{"name": "seed_product1"}) - if err != nil { - t.Fatal(err) - } - - assert.NotEmpty(t, models) - assert.Equal(t, len(models), 1) -} - -func Test_Update(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepository(ctx, t) - - products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } - product := products.Items[0] - - product.Name = "product2_updated" - err = repository.Update(ctx, product) - if err != nil { - t.Fatal(err) - } - - single, err := repository.GetById(ctx, product.ID) - if err != nil { - t.Fatal(err) - } - assert.NotNil(t, single) - assert.Equal(t, "product2_updated", single.Name) -} - -func Test_Update_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) - - products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } - product := products.Items[0] - - product.Name = "product2_updated" - err = repository.Update(ctx, product) - if err != nil { - t.Fatal(err) - } - - single, err := repository.GetById(ctx, product.ID) - if err != nil { - t.Fatal(err) - } - assert.NotNil(t, single) - assert.Equal(t, "product2_updated", single.Name) -} - -func Test_Delete(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepository(ctx, t) - - products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } - product := products.Items[0] - - err = repository.Delete(ctx, product.ID) - if err != nil { - return - } - - single, err := repository.GetById(ctx, product.ID) - assert.Nil(t, single) -} - -func Test_Delete_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) - - products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } - product := products.Items[0] - - err = repository.Delete(ctx, product.ID) - if err != nil { - return - } - - single, err := repository.GetById(ctx, product.ID) - assert.Nil(t, single) -} - -func Test_Count(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - - count := repository.Count(ctx) - - assert.Equal(t, count, int64(2)) -} - -func Test_Count_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - - count := repository.Count(ctx) - - assert.Equal(t, count, int64(2)) -} - -func Test_Find(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - - entities, err := repository.Find( - ctx, - specification.And(specification.Equal("is_available", true), specification.Equal("name", "seed_product1")), - ) - if err != nil { - return - } - assert.Equal(t, len(entities), 1) -} - -func Test_Find_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - - entities, err := repository.Find( - ctx, - specification.And(specification.Equal("is_available", true), specification.Equal("name", "seed_product1")), - ) - if err != nil { - return - } - assert.Equal(t, len(entities), 1) -} - -func setupGenericGormRepositoryWithDataModel( - ctx context.Context, - t *testing.T, -) (data.GenericRepositoryWithDataModel[*ProductGorm, *Product], error) { - defaultLogger.SetupDefaultLogger() - - db, err := gorm2.NewGormTestContainers(defaultLogger.Logger).Start(ctx, t) - if err != nil { - return nil, err - } - - err = seedAndMigration(ctx, db) - if err != nil { - return nil, err - } - - return NewGenericGormRepositoryWithDataModel[*ProductGorm, *Product](db), nil -} - -func setupGenericGormRepository(ctx context.Context, t *testing.T) (data.GenericRepository[*ProductGorm], error) { - defaultLogger.SetupDefaultLogger() - - db, err := gorm2.NewGormTestContainers(defaultLogger.Logger).Start(ctx, t) - - err = seedAndMigration(ctx, db) - if err != nil { - return nil, err - } - - return NewGenericGormRepository[*ProductGorm](db), nil -} - -func seedAndMigration(ctx context.Context, db *gorm.DB) error { - err := db.AutoMigrate(ProductGorm{}) - if err != nil { - return err - } - - seedProducts := []*ProductGorm{ - { - ID: uuid.NewV4(), - Name: "seed_product1", - Weight: 100, - IsAvailable: true, - }, - { - ID: uuid.NewV4(), - Name: "seed_product2", - Weight: 100, - IsAvailable: true, - }, - } - - err = db.WithContext(ctx).Create(seedProducts).Error - if err != nil { - return err - } - return nil -} diff --git a/internal/pkg/grpc/client.go b/internal/pkg/grpc/client.go index 54fc3ecd..05d93b45 100644 --- a/internal/pkg/grpc/client.go +++ b/internal/pkg/grpc/client.go @@ -4,9 +4,11 @@ import ( "fmt" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/grpc/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc/handlers/otel" "emperror.dev/errors" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" "google.golang.org/grpc/connectivity" "google.golang.org/grpc/credentials/insecure" @@ -29,6 +31,10 @@ func NewGrpcClient(config *config.GrpcOptions) (GrpcClient, error) { // https://github.com/open-telemetry/opentelemetry-go-contrib/blob/df16f32df86b40077c9c90d06f33c4cdb6dd5afa/instrumentation/google.golang.org/grpc/otelgrpc/example_interceptor_test.go conn, err := grpc.Dial(fmt.Sprintf("%s%s", config.Host, config.Port), grpc.WithTransportCredentials(insecure.NewCredentials()), + // https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/google.golang.org/grpc/otelgrpc/example/client/main.go#L47C3-L47C52 + // https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/google.golang.org/grpc/otelgrpc/doc.go + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + grpc.WithStatsHandler(otel.NewClientHandler()), ) if err != nil { return nil, err @@ -57,7 +63,10 @@ func (g *grpcClient) WaitForAvailableConnection() error { return err } -func waitUntilConditionMet(conditionToMet func() bool, timeout ...time.Duration) error { +func waitUntilConditionMet( + conditionToMet func() bool, + timeout ...time.Duration, +) error { timeOutTime := 20 * time.Second if len(timeout) >= 0 && timeout != nil { timeOutTime = timeout[0] @@ -68,7 +77,9 @@ func waitUntilConditionMet(conditionToMet func() bool, timeout ...time.Duration) meet := conditionToMet() for meet == false { if timeOutExpired { - return errors.New("grpc connection could not be established in the given timeout.") + return errors.New( + "grpc connection could not be established in the given timeout.", + ) } time.Sleep(time.Second * 2) meet = conditionToMet() diff --git a/internal/pkg/grpc/config/grpc_options.go b/internal/pkg/grpc/config/grpc_options.go index 649ebed0..2ca62c8a 100644 --- a/internal/pkg/grpc/config/grpc_options.go +++ b/internal/pkg/grpc/config/grpc_options.go @@ -1,22 +1,22 @@ package config import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) -var optionName = strcase.ToLowerCamel(typeMapper.GetTypeNameByT[GrpcOptions]()) +var optionName = strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[GrpcOptions]()) type GrpcOptions struct { Port string `mapstructure:"port" env:"TcpPort"` Host string `mapstructure:"host" env:"Host"` Development bool `mapstructure:"development" env:"Development"` - Name string `mapstructure:"name" env:"Name"` + Name string `mapstructure:"name" env:"ShortTypeName"` } -func ProvideConfig(environment environemnt.Environment) (*GrpcOptions, error) { +func ProvideConfig(environment environment.Environment) (*GrpcOptions, error) { return config.BindConfigKey[*GrpcOptions](optionName, environment) } diff --git a/internal/pkg/grpc/grpcErrors/custom_grpc_errors.go b/internal/pkg/grpc/grpcErrors/custom_grpc_errors.go index f28fd4af..3fdc3c56 100644 --- a/internal/pkg/grpc/grpcErrors/custom_grpc_errors.go +++ b/internal/pkg/grpc/grpcErrors/custom_grpc_errors.go @@ -1,9 +1,9 @@ -package grpcErrors +package grpcerrors import ( "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" "google.golang.org/grpc/codes" ) diff --git a/internal/pkg/grpc/grpcErrors/grpc_error_parser.go b/internal/pkg/grpc/grpcErrors/grpc_error_parser.go index 1c5b7686..82fb914d 100644 --- a/internal/pkg/grpc/grpcErrors/grpc_error_parser.go +++ b/internal/pkg/grpc/grpcErrors/grpc_error_parser.go @@ -1,12 +1,12 @@ -package grpcErrors +package grpcerrors import ( "context" "database/sql" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + errorUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils/errorutils" "emperror.dev/errors" "github.com/go-playground/validator" @@ -21,14 +21,26 @@ func ParseError(err error) GrpcErr { var validatorErr validator.ValidationErrors stackTrace := errorUtils.ErrorsWithStack(err) - if err != nil { + if err != nil && customErr != nil { switch { case customErrors.IsDomainError(err, customErr.Status()): - return NewDomainGrpcError(codes.Code(customErr.Status()), customErr.Error(), stackTrace) + return NewDomainGrpcError( + codes.Code(customErr.Status()), + customErr.Error(), + stackTrace, + ) case customErrors.IsApplicationError(err, customErr.Status()): - return NewApplicationGrpcError(codes.Code(customErr.Status()), customErr.Error(), stackTrace) + return NewApplicationGrpcError( + codes.Code(customErr.Status()), + customErr.Error(), + stackTrace, + ) case customErrors.IsApiError(err, customErr.Status()): - return NewApiGrpcError(codes.Code(customErr.Status()), customErr.Error(), stackTrace) + return NewApiGrpcError( + codes.Code(customErr.Status()), + customErr.Error(), + stackTrace, + ) case customErrors.IsBadRequestError(err): return NewBadRequestGrpcError(customErr.Error(), stackTrace) case customErrors.IsNotFoundError(err): @@ -54,14 +66,22 @@ func ParseError(err error) GrpcErr { return NewInternalServerGrpcError(customErr.Error(), stackTrace) case customErrors.IsMarshalingError(err): return NewInternalServerGrpcError(customErr.Error(), stackTrace) + default: + return NewInternalServerGrpcError(err.Error(), stackTrace) + } + } else if err != nil && customErr == nil { + switch { case errors.Is(err, sql.ErrNoRows): return NewNotFoundErrorGrpcError(err.Error(), stackTrace) case errors.Is(err, context.DeadlineExceeded): - return NewGrpcError(codes.DeadlineExceeded, constants.ErrRequestTimeoutTitle, err.Error(), stackTrace) + return NewGrpcError( + codes.DeadlineExceeded, + constants.ErrRequestTimeoutTitle, + err.Error(), + stackTrace, + ) case errors.As(err, &validatorErr): return NewValidationGrpcError(validatorErr.Error(), stackTrace) - default: - return NewInternalServerGrpcError(err.Error(), stackTrace) } } diff --git a/internal/pkg/grpc/grpcErrors/grpc_errors.go b/internal/pkg/grpc/grpcErrors/grpc_errors.go index e2fe7427..885ef6fa 100644 --- a/internal/pkg/grpc/grpcErrors/grpc_errors.go +++ b/internal/pkg/grpc/grpcErrors/grpc_errors.go @@ -1,11 +1,11 @@ -package grpcErrors +package grpcerrors import ( "encoding/json" "fmt" "time" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -34,7 +34,12 @@ type GrpcErr interface { ToGrpcResponseErr() error } -func NewGrpcError(status codes.Code, title string, detail string, stackTrace string) GrpcErr { +func NewGrpcError( + status codes.Code, + title string, + detail string, + stackTrace string, +) GrpcErr { grpcErr := &grpcErr{ Status: status, Title: title, @@ -107,7 +112,7 @@ func (p *grpcErr) ToGrpcResponseErr() error { } func (p *grpcErr) ToJson() string { - defaultLogger.Logger.Error(p.Error()) + defaultLogger.GetLogger().Error(p.Error()) stackTrace := p.GetStackTrace() fmt.Println(stackTrace) diff --git a/internal/pkg/grpc/grpc_fx.go b/internal/pkg/grpc/grpc_fx.go index de18d57a..077b7e63 100644 --- a/internal/pkg/grpc/grpc_fx.go +++ b/internal/pkg/grpc/grpc_fx.go @@ -3,8 +3,8 @@ package grpc import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/grpc/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "go.uber.org/fx" ) @@ -12,7 +12,11 @@ import ( var ( // Module provided to fxlog // https://uber-go.github.io/fx/modules.html - Module = fx.Module("grpcfx", grpcProviders, grpcInvokes) //nolint:gochecknoglobals + Module = fx.Module( + "grpcfx", + grpcProviders, + grpcInvokes, + ) //nolint:gochecknoglobals // - order is not important in provide // - provide can have parameter and will resolve if registered @@ -23,7 +27,7 @@ var ( // https://uber-go.github.io/fx/annotate.html fx.Annotate( NewGrpcServer, - fx.ParamTags(``, ``, `optional:"true"`), + fx.ParamTags(``, ``), ), NewGrpcClient, )) @@ -52,7 +56,10 @@ func registerHooks( // if (ctx.Err() == nil), context not canceled or deadlined if err := grpcServer.RunGrpcServer(nil); err != nil { // do a fatal for going to OnStop process - logger.Fatalf("(GrpcServer.RunGrpcServer) error in running server: {%v}", err) + logger.Fatalf( + "(GrpcServer.RunGrpcServer) error in running server: {%v}", + err, + ) } }() logger.Infof( diff --git a/internal/pkg/grpc/handlers/otel/config.go b/internal/pkg/grpc/handlers/otel/config.go new file mode 100644 index 00000000..070d15eb --- /dev/null +++ b/internal/pkg/grpc/handlers/otel/config.go @@ -0,0 +1,80 @@ +package otel + +import ( + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" +) + +// ref: https://github.com/bakins/otel-grpc-statshandler/blob/main/statshandler.go + +// Option applies an option value when creating a Handler. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (f optionFunc) apply(c *config) { + f(c) +} + +type config struct { + metricsProvider metric.MeterProvider + tracerProvider trace.TracerProvider + propagator propagation.TextMapPropagator + Namespace string + serviceName string + instrumentationName string +} + +var defualtConfig = config{ + metricsProvider: otel.GetMeterProvider(), + tracerProvider: otel.GetTracerProvider(), + propagator: otel.GetTextMapPropagator(), + serviceName: "application", + instrumentationName: "grpc-otel", +} + +func WithMeterProvider(m metric.MeterProvider) Option { + return optionFunc(func(c *config) { + c.metricsProvider = m + }) +} + +func WithTraceProvider(t trace.TracerProvider) Option { + return optionFunc(func(c *config) { + c.tracerProvider = t + }) +} + +func WithPropagators(p propagation.TextMapPropagator) Option { + return optionFunc(func(c *config) { + c.propagator = p + }) +} + +func SetNamespace(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.Namespace != "" { + cfg.Namespace = v + } + }) +} + +func SetServiceName(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.serviceName != "" { + cfg.serviceName = v + } + }) +} + +func SetInstrumentationName(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.instrumentationName != "" { + cfg.instrumentationName = v + } + }) +} diff --git a/internal/pkg/grpc/handlers/otel/handler.go b/internal/pkg/grpc/handlers/otel/handler.go new file mode 100644 index 00000000..71c14707 --- /dev/null +++ b/internal/pkg/grpc/handlers/otel/handler.go @@ -0,0 +1,329 @@ +package otel + +import ( + "context" + "strings" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/stats" + "google.golang.org/grpc/status" +) + +// ref: https://github.com/bakins/otel-grpc-statshandler/blob/main/statshandler.go +// https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/google.golang.org/grpc/otelgrpc/config.go +// https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go +// https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go#L52 + +// https://opentelemetry.io/docs/specs/otel/metrics/semantic_conventions/rpc-metrics/ + +// ServerHandler implements https://pkg.go.dev/google.golang.org/grpc/stats#ServerHandler +// It records OpenTelemetry metrics and traces. +type ServerHandler struct { + handler +} + +// ClientHandler implements https://pkg.go.dev/google.golang.org/grpc/stats#ServerHandler +// It records OpenTelemetry metrics and traces. +type ClientHandler struct { + handler +} + +type gRPCContextKey struct{} + +type gRPCContext struct { + messagesReceived int64 + messagesSent int64 + attributes []attribute.KeyValue + startTime time.Time +} + +type handler struct { + tracer trace.Tracer + meter metric.Meter + propagator propagation.TextMapPropagator + rpcDuration metric.Int64Histogram + rpcRequestSize metric.Int64Histogram + rpcResponseSize metric.Int64Histogram + rpcRequestsPerRPC metric.Int64Histogram + rpcResponsesPerRPC metric.Int64Histogram + rpcTotalFailed metric.Int64Counter + rpcTotalSuccess metric.Int64Counter + spanKind trace.SpanKind + config config +} + +func newHandler(spanKind trace.SpanKind, options []Option) (handler, error) { + c := defualtConfig + + for _, o := range options { + o.apply(&c) + } + + meter := c.metricsProvider.Meter(c.instrumentationName) + + prefix := "rpc.server" + if spanKind == trace.SpanKindClient { + prefix = "rpc.client" + } + + // https://opentelemetry.io/docs/specs/otel/metrics/semantic_conventions/rpc-metrics/#rpc-server + rpcServerDuration, err := meter.Int64Histogram(prefix+".duration", + metric.WithDescription("Measures the duration of inbound RPC."), + metric.WithUnit("ms")) + if err != nil { + otel.Handle(err) + } + + rpcRequestSize, err := meter.Int64Histogram( + prefix+".request.size", + metric.WithDescription( + "Measures size of RPC request messages (uncompressed).", + ), + metric.WithUnit("bytes"), + ) + if err != nil { + return handler{}, err + } + + rpcResponseSize, err := meter.Int64Histogram( + prefix+".response.size", + metric.WithDescription( + "Measures size of RPC response messages (uncompressed)", + ), + metric.WithUnit("bytes"), + ) + if err != nil { + return handler{}, err + } + + rpcRequestsPerRPC, err := meter.Int64Histogram( + prefix+".requests_per_rpc", + metric.WithDescription( + "Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs", + ), + metric.WithUnit("count"), + ) + if err != nil { + return handler{}, err + } + + rpcResponsesPerRPC, err := meter.Int64Histogram( + prefix+".responses_per_rpc", + metric.WithDescription( + "Measures the number of messages sent per RPC. Should be 1 for all non-streaming RPCs", + ), + metric.WithUnit("count")) + if err != nil { + return handler{}, err + } + + rpcTotalFailed, err := meter.Int64Counter( + prefix+".rpc_error_total", + metric.WithDescription("The total number of error grpc requests"), + metric.WithUnit("count"), + ) + if err != nil { + return handler{}, err + } + + rpcTotalSuccess, err := meter.Int64Counter( + prefix+".rpc_success_total", + metric.WithDescription("The total number of success grpc requests"), + metric.WithUnit("count"), + ) + if err != nil { + return handler{}, err + } + + h := handler{ + tracer: c.tracerProvider.Tracer(c.instrumentationName), + meter: meter, + spanKind: spanKind, + config: c, + rpcDuration: rpcServerDuration, + rpcRequestSize: rpcRequestSize, + rpcResponseSize: rpcResponseSize, + rpcRequestsPerRPC: rpcRequestsPerRPC, + rpcResponsesPerRPC: rpcResponsesPerRPC, + rpcTotalFailed: rpcTotalFailed, + rpcTotalSuccess: rpcTotalSuccess, + } + + return h, nil +} + +func (h *handler) tagRPC( + ctx context.Context, + info *stats.RPCTagInfo, +) context.Context { + ctx = extract(ctx, h.config.propagator) + + var attributes []attribute.KeyValue + attributes = append(attributes, semconv.RPCSystemGRPC) + + parts := strings.Split(info.FullMethodName, "/") + if len(parts) == 3 { + attributes = append(attributes, semconv.RPCServiceKey.String(parts[1])) + attributes = append(attributes, semconv.RPCMethodKey.String(parts[2])) + } + + gctx := gRPCContext{attributes: attributes, startTime: time.Now()} + + return inject( + context.WithValue(ctx, gRPCContextKey{}, &gctx), + h.config.propagator, + ) +} + +func (h *handler) handleRPC(ctx context.Context, rs stats.RPCStats) { + _ = trace.SpanFromContext(ctx) + gctx, _ := ctx.Value(gRPCContextKey{}).(*gRPCContext) + + switch rs := rs.(type) { + case *stats.Begin: + case *stats.InPayload: + if gctx != nil { + // https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go#L52 + opt := metric.WithAttributes(gctx.attributes...) + h.rpcRequestSize.Record(ctx, int64(rs.Length), opt) + } + + case *stats.OutPayload: + if gctx != nil { + // https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go#L52 + opt := metric.WithAttributes(gctx.attributes...) + h.rpcResponseSize.Record(ctx, int64(rs.Length), opt) + } + case *stats.End: + if rs.Error != nil { + s, _ := status.FromError(rs.Error) + gctx.attributes = append(gctx.attributes, statusCodeAttr(s.Code())) + opt := metric.WithAttributes(gctx.attributes...) + + h.rpcTotalFailed.Add(ctx, 1, opt) + } else { + gctx.attributes = append(gctx.attributes, statusCodeAttr(codes.OK)) + opt := metric.WithAttributes(gctx.attributes...) + + h.rpcTotalSuccess.Add(ctx, 1, opt) + } + + if gctx != nil { + duration := time.Since(gctx.startTime).Milliseconds() + opt := metric.WithAttributes(gctx.attributes...) + + h.rpcDuration.Record( + ctx, + duration, + opt, + ) + + h.rpcRequestsPerRPC.Record( + ctx, + gctx.messagesReceived, + opt, + ) + + h.rpcResponsesPerRPC.Record( + ctx, + gctx.messagesSent, + opt, + ) + } + + default: + return + } +} + +func statusCodeAttr(c codes.Code) attribute.KeyValue { + return semconv.RPCGRPCStatusCodeKey.Int(int(c)) +} + +func NewServerHandler(options ...Option) stats.Handler { + h, err := newHandler(trace.SpanKindServer, options) + if err != nil { + otel.Handle(err) + } + + s := &ServerHandler{ + handler: h, + } + + return s +} + +func NewClientHandler(options ...Option) stats.Handler { + h, err := newHandler(trace.SpanKindClient, options) + if err != nil { + otel.Handle(err) + } + + c := &ClientHandler{ + handler: h, + } + + return c +} + +func (s *ServerHandler) TagRPC( + ctx context.Context, + info *stats.RPCTagInfo, +) context.Context { + return s.handler.tagRPC(ctx, info) +} + +// HandleRPC processes the RPC stats. +func (s *ServerHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { + s.handler.handleRPC(ctx, rs) +} + +// TagConn can attach some information to the given context. +func (s *ServerHandler) TagConn( + ctx context.Context, + _ *stats.ConnTagInfo, +) context.Context { + // no-op + return ctx +} + +// HandleConn processes the Conn stats. +func (s *ServerHandler) HandleConn(_ context.Context, _ stats.ConnStats) { + // no-op +} + +func (c *ClientHandler) TagRPC( + ctx context.Context, + info *stats.RPCTagInfo, +) context.Context { + return c.handler.tagRPC(ctx, info) +} + +func (c *ClientHandler) HandleRPC( + ctx context.Context, + rpcStats stats.RPCStats, +) { + c.handler.handleRPC(ctx, rpcStats) +} + +func (c *ClientHandler) TagConn( + ctx context.Context, + _ *stats.ConnTagInfo, +) context.Context { + // no-op + return ctx +} + +func (c *ClientHandler) HandleConn( + _ context.Context, + _ stats.ConnStats, +) { + // no-op +} diff --git a/internal/pkg/grpc/handlers/otel/supplier.go b/internal/pkg/grpc/handlers/otel/supplier.go new file mode 100644 index 00000000..1a9b996d --- /dev/null +++ b/internal/pkg/grpc/handlers/otel/supplier.go @@ -0,0 +1,69 @@ +package otel + +import ( + "context" + + "go.opentelemetry.io/otel/propagation" + "google.golang.org/grpc/metadata" +) + +// https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/google.golang.org/grpc/otelgrpc/metadata_supplier.go#L27 +type metadataSupplier struct { + metadata *metadata.MD +} + +var _ propagation.TextMapCarrier = &metadataSupplier{} + +func (s *metadataSupplier) Get(key string) string { + values := s.metadata.Get(key) + if len(values) == 0 { + return "" + } + + return values[0] +} + +func (s *metadataSupplier) Set(key string, value string) { + if s.metadata != nil { + s.metadata.Set(key, value) + } +} + +func (s *metadataSupplier) Keys() []string { + out := make([]string, 0, len(*s.metadata)) + for key := range *s.metadata { + out = append(out, key) + } + + return out +} + +func extract( + ctx context.Context, + propagators propagation.TextMapPropagator, +) context.Context { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + md = metadata.MD{} + } + + return propagators.Extract(ctx, &metadataSupplier{ + metadata: &md, + }) +} + +func inject( + ctx context.Context, + propagators propagation.TextMapPropagator, +) context.Context { + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.MD{} + } + + propagators.Inject(ctx, &metadataSupplier{ + metadata: &md, + }) + + return metadata.NewOutgoingContext(ctx, md) +} diff --git a/internal/pkg/grpc/interceptors/grpc_error/error_interceptor.go b/internal/pkg/grpc/interceptors/grpcerror/error_interceptor.go similarity index 53% rename from internal/pkg/grpc/interceptors/grpc_error/error_interceptor.go rename to internal/pkg/grpc/interceptors/grpcerror/error_interceptor.go index 128c0ad2..a836815e 100644 --- a/internal/pkg/grpc/interceptors/grpc_error/error_interceptor.go +++ b/internal/pkg/grpc/interceptors/grpcerror/error_interceptor.go @@ -2,10 +2,10 @@ package grpcError import ( "context" - "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/grpc/grpcErrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc/grpcerrors" + "emperror.dev/errors" "google.golang.org/grpc" ) @@ -18,15 +18,16 @@ func UnaryServerInterceptor() grpc.UnaryServerInterceptor { handler grpc.UnaryHandler, ) (interface{}, error) { resp, err := handler(ctx, req) - if err != nil { - grpcErr := grpcErrors.ParseError(err) - - if grpcErr != nil { - return nil, grpcErr.ToGrpcResponseErr() - } else { - prb := grpcErrors.NewInternalServerGrpcError(err.Error(), fmt.Sprintf("%+v\n", err)) - return nil, prb.ToGrpcResponseErr() - } + + var grpcErr grpcerrors.GrpcErr + + // if error was not `grpcErr` we will convert the error to a `grpcErr` + if ok := errors.As(err, &grpcErr); !ok { + grpcErr = grpcerrors.ParseError(err) + } + + if grpcErr != nil { + return nil, grpcErr.ToGrpcResponseErr() } return resp, err @@ -42,16 +43,18 @@ func StreamServerInterceptor() grpc.StreamServerInterceptor { handler grpc.StreamHandler, ) error { err := handler(srv, ss) - if err != nil { - grpcErr := grpcErrors.ParseError(err) - - if grpcErr != nil { - return grpcErr.ToGrpcResponseErr() - } else { - prb := grpcErrors.NewInternalServerGrpcError(err.Error(), fmt.Sprintf("%+v\n", err)) - return prb.ToGrpcResponseErr() - } + + var grpcErr grpcerrors.GrpcErr + + // if error was not `grpcErr` we will convert the error to a `grpcErr` + if ok := errors.As(err, &grpcErr); !ok { + grpcErr = grpcerrors.ParseError(err) + } + + if grpcErr != nil { + return grpcErr.ToGrpcResponseErr() } + return err } } diff --git a/internal/pkg/grpc/interceptors/otel_metrics/request_status_interceptor.go b/internal/pkg/grpc/interceptors/otel_metrics/request_status_interceptor.go deleted file mode 100644 index c46e1a3d..00000000 --- a/internal/pkg/grpc/interceptors/otel_metrics/request_status_interceptor.go +++ /dev/null @@ -1,84 +0,0 @@ -package otelMetrics - -// ref:https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go - -import ( - "context" - "fmt" - - "go.opentelemetry.io/otel/attribute" - api "go.opentelemetry.io/otel/metric" - "google.golang.org/grpc" -) - -// UnaryServerInterceptor add request status metrics to the otel -func UnaryServerInterceptor(meter api.Meter, serviceName string) grpc.UnaryServerInterceptor { - return func( - ctx context.Context, - req interface{}, - info *grpc.UnaryServerInfo, - handler grpc.UnaryHandler, - ) (interface{}, error) { - resp, err := handler(ctx, req) - - attrs := api.WithAttributes( - attribute.Key("MetricsType").String("Grpc"), - ) - - if err != nil { - counter, err := meter.Float64Counter( - fmt.Sprintf("%s_error_grpc_requests_total", serviceName), - api.WithDescription("The total number of error grpc requests"), - ) - if err != nil { - return nil, err - } - counter.Add(ctx, 1, attrs) - } else { - counter, err := meter.Float64Counter(fmt.Sprintf("%s_success_grpc_requests_total", serviceName), api.WithDescription("The total number of success grpc requests")) - if err != nil { - return nil, err - } - counter.Add(ctx, 1, attrs) - } - - return resp, err - } -} - -// StreamServerInterceptor add request status metrics to the otel -func StreamServerInterceptor(meter api.Meter, serviceName string) grpc.StreamServerInterceptor { - return func( - srv interface{}, - ss grpc.ServerStream, - info *grpc.StreamServerInfo, - handler grpc.StreamHandler, - ) error { - err := handler(srv, ss) - - attrs := api.WithAttributes( - attribute.Key("MetricsType").String("Grpc"), - ) - - ctx := ss.Context() - - if err != nil { - counter, err := meter.Float64Counter( - fmt.Sprintf("%s_error_grpc_requests_total", serviceName), - api.WithDescription("The total number of error grpc requests"), - ) - if err != nil { - return err - } - counter.Add(ctx, 1, attrs) - } else { - counter, err := meter.Float64Counter(fmt.Sprintf("%s_success_grpc_requests_total", serviceName), api.WithDescription("The total number of success grpc requests")) - if err != nil { - return err - } - counter.Add(ctx, 1, attrs) - } - - return err - } -} diff --git a/internal/pkg/grpc/otel/tracing/consts.go b/internal/pkg/grpc/otel/tracing/consts.go deleted file mode 100644 index bb93bc15..00000000 --- a/internal/pkg/grpc/otel/tracing/consts.go +++ /dev/null @@ -1,5 +0,0 @@ -package tracing - -const ( - GrpcErrorMessage = "rpc.grpc.error_message" -) diff --git a/internal/pkg/grpc/otel/tracing/utils.go b/internal/pkg/grpc/otel/tracing/utils.go deleted file mode 100644 index c9dc8b0e..00000000 --- a/internal/pkg/grpc/otel/tracing/utils.go +++ /dev/null @@ -1,41 +0,0 @@ -package tracing - -import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/grpc/grpcErrors" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - semconv "go.opentelemetry.io/otel/semconv/v1.21.0" - "go.opentelemetry.io/otel/trace" -) - -// TraceGrpcErrFromSpan setting span with status error with error message -func TraceGrpcErrFromSpan(span trace.Span, err error) error { - if err != nil { - stackTraceError := errorUtils.ErrorsWithStack(err) - span.SetStatus(codes.Error, "") - span.SetAttributes(attribute.String(GrpcErrorMessage, stackTraceError)) - if customErrors.IsCustomError(err) { - grpcErr := grpcErrors.ParseError(err) - span.SetAttributes(semconv.RPCGRPCStatusCodeKey.Int(int(grpcErr.GetStatus()))) - } - span.RecordError(err) - } - - return err -} - -// TraceGrpcErrFromSpanWithCode setting span with status error with error message -func TraceGrpcErrFromSpanWithCode(span trace.Span, err error, code int) error { - if err != nil { - stackTraceError := errorUtils.ErrorsWithStack(err) - span.SetStatus(codes.Error, "") - span.SetAttributes(semconv.RPCGRPCStatusCodeKey.Int(code)) - span.SetAttributes(attribute.String(GrpcErrorMessage, stackTraceError)) - span.RecordError(err) - } - - return err -} diff --git a/internal/pkg/grpc/server.go b/internal/pkg/grpc/server.go index 6b9296f5..d1ddde42 100644 --- a/internal/pkg/grpc/server.go +++ b/internal/pkg/grpc/server.go @@ -5,17 +5,16 @@ import ( "net" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/grpc/config" - grpcError "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/grpc/interceptors/grpc_error" - otelMetrics "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/grpc/interceptors/otel_metrics" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc/handlers/otel" + grpcError "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc/interceptors/grpcerror" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "emperror.dev/errors" grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcRecovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" grpcCtxTags "github.com/grpc-ecosystem/go-grpc-middleware/tags" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - "go.opentelemetry.io/otel/metric" googleGrpc "google.golang.org/grpc" "google.golang.org/grpc/health" "google.golang.org/grpc/health/grpc_health_v1" @@ -48,31 +47,24 @@ type grpcServer struct { func NewGrpcServer( config *config.GrpcOptions, logger logger.Logger, - meter metric.Meter, ) GrpcServer { unaryServerInterceptors := []googleGrpc.UnaryServerInterceptor{ - otelgrpc.UnaryServerInterceptor(), grpcError.UnaryServerInterceptor(), grpcCtxTags.UnaryServerInterceptor(), grpcRecovery.UnaryServerInterceptor(), } streamServerInterceptors := []googleGrpc.StreamServerInterceptor{ - otelgrpc.StreamServerInterceptor(), grpcError.StreamServerInterceptor(), } - if meter != nil { - unaryServerInterceptors = append( - unaryServerInterceptors, - otelMetrics.UnaryServerInterceptor(meter, config.Name), - ) - streamServerInterceptors = append( - streamServerInterceptors, - otelMetrics.StreamServerInterceptor(meter, config.Name), - ) - } - s := googleGrpc.NewServer( + // https://github.com/open-telemetry/opentelemetry-go-contrib/issues/2840 + // https://github.com/open-telemetry/opentelemetry-go-contrib/pull/3002 + // https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/google.golang.org/grpc/otelgrpc/doc.go + // https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/google.golang.org/grpc/otelgrpc/example/server/main.go#L143C3-L143C50 + googleGrpc.StatsHandler(otelgrpc.NewServerHandler()), + googleGrpc.StatsHandler(otel.NewServerHandler()), + googleGrpc.KeepaliveParams(keepalive.ServerParameters{ MaxConnectionIdle: maxConnectionIdle * time.Minute, Timeout: gRPCTimeout * time.Second, @@ -88,10 +80,12 @@ func NewGrpcServer( unaryServerInterceptors..., )), ) - healthServer := health.NewServer() grpc_health_v1.RegisterHealthServer(s, healthServer) - healthServer.SetServingStatus(config.Name, grpc_health_v1.HealthCheckResponse_SERVING) + healthServer.SetServingStatus( + config.Name, + grpc_health_v1.HealthCheckResponse_SERVING, + ) return &grpcServer{ server: s, @@ -130,7 +124,10 @@ func (s *grpcServer) RunGrpcServer( if err != nil { s.log.Error( - fmt.Sprintf("[grpcServer_RunGrpcServer.Serve] grpc server serve error: %+v", err), + fmt.Sprintf( + "[grpcServer_RunGrpcServer.Serve] grpc server serve error: %+v", + err, + ), ) } diff --git a/internal/pkg/health/check.go b/internal/pkg/health/contracts/check.go similarity index 89% rename from internal/pkg/health/check.go rename to internal/pkg/health/contracts/check.go index e7db842a..eeb6e081 100644 --- a/internal/pkg/health/check.go +++ b/internal/pkg/health/contracts/check.go @@ -1,4 +1,4 @@ -package health +package contracts type Check map[string]Status @@ -8,5 +8,6 @@ func (check Check) AllUp() bool { return false } } + return true } diff --git a/internal/pkg/health/health.go b/internal/pkg/health/contracts/health.go similarity index 54% rename from internal/pkg/health/health.go rename to internal/pkg/health/contracts/health.go index 2545fca1..d35731a2 100644 --- a/internal/pkg/health/health.go +++ b/internal/pkg/health/contracts/health.go @@ -1,4 +1,4 @@ -package health +package contracts import "context" @@ -6,3 +6,7 @@ type Health interface { CheckHealth(ctx context.Context) error GetHealthName() string } + +type HealthService interface { + CheckHealth(ctx context.Context) Check +} diff --git a/internal/pkg/health/health_params.go b/internal/pkg/health/contracts/health_params.go similarity index 85% rename from internal/pkg/health/health_params.go rename to internal/pkg/health/contracts/health_params.go index 0141da67..a404c377 100644 --- a/internal/pkg/health/health_params.go +++ b/internal/pkg/health/contracts/health_params.go @@ -1,4 +1,4 @@ -package health +package contracts import ( "go.uber.org/fx" diff --git a/internal/pkg/health/status.go b/internal/pkg/health/contracts/status.go similarity index 94% rename from internal/pkg/health/status.go rename to internal/pkg/health/contracts/status.go index d808dfaa..127ae9a7 100644 --- a/internal/pkg/health/status.go +++ b/internal/pkg/health/contracts/status.go @@ -1,4 +1,4 @@ -package health +package contracts const ( StatusUp = "up" diff --git a/internal/pkg/health/health_endpoint.go b/internal/pkg/health/health_endpoint.go index 94d6b785..6bcc03cd 100644 --- a/internal/pkg/health/health_endpoint.go +++ b/internal/pkg/health/health_endpoint.go @@ -3,32 +3,33 @@ package health import ( "net/http" - customEcho "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/custom_echo" + contracts2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" "github.com/labstack/echo/v4" ) type HealthCheckEndpoint struct { - service HealthService - echoServer customEcho.EchoHttpServer + service contracts2.HealthService + echoServer contracts.EchoHttpServer } func NewHealthCheckEndpoint( - service HealthService, - server customEcho.EchoHttpServer, + service contracts2.HealthService, + server contracts.EchoHttpServer, ) *HealthCheckEndpoint { return &HealthCheckEndpoint{service: service, echoServer: server} } func (s *HealthCheckEndpoint) RegisterEndpoints() { - s.echoServer.GetEchoInstance().Group("").GET("health", s.CheckHealth) + s.echoServer.GetEchoInstance().GET("health", s.checkHealth) } -func (s *HealthCheckEndpoint) CheckHealth(c echo.Context) error { +func (s *HealthCheckEndpoint) checkHealth(c echo.Context) error { check := s.service.CheckHealth(c.Request().Context()) if !check.AllUp() { return c.JSON(http.StatusServiceUnavailable, check) } - err := c.JSON(http.StatusOK, check) - return err + + return c.JSON(http.StatusOK, check) } diff --git a/internal/pkg/health/health_fx.go b/internal/pkg/health/health_fx.go index 2b86b93d..54ddd25c 100644 --- a/internal/pkg/health/health_fx.go +++ b/internal/pkg/health/health_fx.go @@ -4,7 +4,7 @@ import ( "go.uber.org/fx" ) -var Module = fx.Options( +var Module = fx.Options( //nolint:gochecknoglobals fx.Provide( NewHealthService, NewHealthCheckEndpoint, diff --git a/internal/pkg/health/service.go b/internal/pkg/health/service.go index 98a568a5..0184c871 100644 --- a/internal/pkg/health/service.go +++ b/internal/pkg/health/service.go @@ -2,29 +2,29 @@ package health import ( "context" -) -type HealthService interface { - CheckHealth(ctx context.Context) Check -} + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" +) type healthService struct { - healthParams HealthParams + healthParams contracts.HealthParams } func NewHealthService( - healthParams HealthParams, -) HealthService { + healthParams contracts.HealthParams, +) contracts.HealthService { return &healthService{ healthParams: healthParams, } } -func (service *healthService) CheckHealth(ctx context.Context) Check { - checks := make(Check) +func (service *healthService) CheckHealth(ctx context.Context) contracts.Check { + checks := make(contracts.Check) for _, health := range service.healthParams.Healths { - checks[health.GetHealthName()] = NewStatus(health.CheckHealth(ctx)) + checks[health.GetHealthName()] = contracts.NewStatus( + health.CheckHealth(ctx), + ) } return checks diff --git a/internal/pkg/health/unhealthy.go b/internal/pkg/health/unhealthy.go index 75852493..7db4b068 100644 --- a/internal/pkg/health/unhealthy.go +++ b/internal/pkg/health/unhealthy.go @@ -1,16 +1,22 @@ package health -import "context" +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" +) + +type UnhealthyHealthService struct{} func NewUnhealthyHealthService() UnhealthyHealthService { return UnhealthyHealthService{} } -type UnhealthyHealthService struct{} - -func (service UnhealthyHealthService) CheckHealth(context.Context) Check { - return Check{ - "postgres": Status{Status: StatusDown}, - "redis": Status{Status: StatusDown}, +func (service UnhealthyHealthService) CheckHealth( + context.Context, +) contracts.Check { + return contracts.Check{ + "postgres": contracts.Status{Status: contracts.StatusDown}, + "redis": contracts.Status{Status: contracts.StatusDown}, } } diff --git a/internal/pkg/http/custom_echo/constants/constants.go b/internal/pkg/http/custom_echo/constants/constants.go deleted file mode 100644 index 1a04bab6..00000000 --- a/internal/pkg/http/custom_echo/constants/constants.go +++ /dev/null @@ -1,9 +0,0 @@ -package constants - -type otel struct { - HttpErrorMessage string -} - -var Otel = otel{ - HttpErrorMessage: "http.error_message", -} diff --git a/internal/pkg/http/custom_echo/echo_server.go b/internal/pkg/http/custom_echo/echo_server.go deleted file mode 100644 index fe8349e5..00000000 --- a/internal/pkg/http/custom_echo/echo_server.go +++ /dev/null @@ -1,173 +0,0 @@ -package customEcho - -import ( - "context" - "fmt" - "strings" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/custom_echo/config" - customHadnlers "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/custom_echo/hadnlers" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/custom_echo/middlewares/log" - otelMetrics "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/custom_echo/middlewares/otel_metrics" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" - "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" - "go.opentelemetry.io/otel/metric" -) - -type echoHttpServer struct { - echo *echo.Echo - config *config.EchoHttpOptions - log logger.Logger - meter metric.Meter - routeBuilder *RouteBuilder -} - -type EchoHttpServer interface { - RunHttpServer(configEcho ...func(echo *echo.Echo)) error - GracefulShutdown(ctx context.Context) error - ApplyVersioningFromHeader() - GetEchoInstance() *echo.Echo - Logger() logger.Logger - Cfg() *config.EchoHttpOptions - SetupDefaultMiddlewares() - RouteBuilder() *RouteBuilder - AddMiddlewares(middlewares ...echo.MiddlewareFunc) - ConfigGroup(groupName string, groupFunc func(group *echo.Group)) -} - -func NewEchoHttpServer( - config *config.EchoHttpOptions, - logger logger.Logger, - meter metric.Meter, -) EchoHttpServer { - e := echo.New() - e.HideBanner = false - - return &echoHttpServer{ - echo: e, - config: config, - log: logger, - meter: meter, - routeBuilder: NewRouteBuilder(e), - } -} - -func (s *echoHttpServer) RunHttpServer( - configEcho ...func(echo *echo.Echo), -) error { - s.echo.Server.ReadTimeout = constants.ReadTimeout - s.echo.Server.WriteTimeout = constants.WriteTimeout - s.echo.Server.MaxHeaderBytes = constants.MaxHeaderBytes - - if len(configEcho) > 0 { - ehcoFunc := configEcho[0] - if ehcoFunc != nil { - configEcho[0](s.echo) - } - } - - // https://echo.labstack.com/guide/http_server/ - return s.echo.Start(s.config.Port) -} - -func (s *echoHttpServer) Logger() logger.Logger { - return s.log -} - -func (s *echoHttpServer) Cfg() *config.EchoHttpOptions { - return s.config -} - -func (s *echoHttpServer) RouteBuilder() *RouteBuilder { - return s.routeBuilder -} - -func (s *echoHttpServer) ConfigGroup(groupName string, groupFunc func(group *echo.Group)) { - groupFunc(s.echo.Group(groupName)) -} - -func (s *echoHttpServer) AddMiddlewares(middlewares ...echo.MiddlewareFunc) { - if len(middlewares) > 0 { - s.echo.Use(middlewares...) - } -} - -func (s *echoHttpServer) GracefulShutdown(ctx context.Context) error { - err := s.echo.Shutdown(ctx) - if err != nil { - return err - } - - return nil -} - -func (s *echoHttpServer) SetupDefaultMiddlewares() { - // set error handler - s.echo.HTTPErrorHandler = func(err error, c echo.Context) { - // bypass notfound favicon endpoint and its error - if c.Request().URL.Path == "/favicon.ico" { - return - } - customHadnlers.ProblemHandlerFunc(err, c, s.log) - } - - // log errors and information - s.echo.Use(log.EchoLogger(s.log)) - s.echo.Use(otelecho.Middleware(s.config.Name)) - // Because we use metrics server middleware, if it is not available, our echo will not work. - if s.meter != nil { - s.echo.Use(otelMetrics.Middleware(s.meter, s.config.Name)) - } - - s.echo.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ - LogContentLength: true, - LogLatency: true, - LogError: false, - LogMethod: true, - LogRequestID: true, - LogURI: true, - LogResponseSize: true, - LogURIPath: true, - LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { - s.log.Infow( - fmt.Sprintf("[Request Middleware] REQUEST: uri: %v, status: %v\n", v.URI, v.Status), - logger.Fields{"URI": v.URI, "Status": v.Status}, - ) - return nil - }, - })) - s.echo.Use(middleware.BodyLimit(constants.BodyLimit)) - s.echo.Use(middleware.RequestID()) - s.echo.Use(middleware.GzipWithConfig(middleware.GzipConfig{ - Level: constants.GzipLevel, - Skipper: func(c echo.Context) bool { - return strings.Contains(c.Request().URL.Path, "swagger") - }, - })) -} - -func (s *echoHttpServer) ApplyVersioningFromHeader() { - s.echo.Pre(apiVersion) -} - -func (s *echoHttpServer) GetEchoInstance() *echo.Echo { - return s.echo -} - -// APIVersion Header Based Versioning -func apiVersion(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - req := c.Request() - headers := req.Header - - apiVersion := headers.Get("version") - - req.URL.Path = fmt.Sprintf("/%s%s", apiVersion, req.URL.Path) - - return next(c) - } -} diff --git a/internal/pkg/http/custom_echo/hadnlers/problem_details_handler.go b/internal/pkg/http/custom_echo/hadnlers/problem_details_handler.go deleted file mode 100644 index aa5707f3..00000000 --- a/internal/pkg/http/custom_echo/hadnlers/problem_details_handler.go +++ /dev/null @@ -1,28 +0,0 @@ -package customHadnlers - -import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/problemDetails" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" - - "github.com/labstack/echo/v4" -) - -func ProblemHandlerFunc(err error, c echo.Context, logger logger.Logger) { - prb := problemDetails.ParseError(err) - - if prb != nil { - if !c.Response().Committed { - if _, err := problemDetails.WriteTo(prb, c.Response()); err != nil { - logger.Error(err) - } - } - } else { - if !c.Response().Committed { - prb := problemDetails.NewInternalServerProblemDetail(err.Error(), errorUtils.ErrorsWithStack(err)) - if _, err := problemDetails.WriteTo(prb, c.Response()); err != nil { - logger.Error(err) - } - } - } -} diff --git a/internal/pkg/http/custom_echo/middlewares/log/log_middleware.go b/internal/pkg/http/custom_echo/middlewares/log/log_middleware.go deleted file mode 100644 index bfe739e7..00000000 --- a/internal/pkg/http/custom_echo/middlewares/log/log_middleware.go +++ /dev/null @@ -1,57 +0,0 @@ -package log - -import ( - "fmt" - "time" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - - "github.com/labstack/echo/v4" -) - -func EchoLogger(logger logger.Logger) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - start := time.Now() - - err := next(c) - if err != nil { - // handle echo error in this middleware and raise echo errorhandler func and our custom error handler (problem details handler) - c.Error(err) - } - - req := c.Request() - res := c.Response() - - fields := map[string]interface{}{ - "remote_ip": c.RealIP(), - "latency": time.Since(start).String(), - "host": req.Host, - "request": fmt.Sprintf("%s %s", req.Method, req.RequestURI), - "status": res.Status, - "size": res.Size, - "user_agent": req.UserAgent(), - } - - id := req.Header.Get(echo.HeaderXRequestID) - if id == "" { - id = res.Header().Get(echo.HeaderXRequestID) - } - fields["request_id"] = id - - n := res.Status - switch { - case n >= 500: - logger.Errorw("Echo logger middleware: Server error", fields) - case n >= 400: - logger.Errorw("Echo logger middleware: Client error", fields) - case n >= 300: - logger.Errorw("Echo logger middleware: Redirection", fields) - default: - logger.Infow("Echo logger middleware: Success", fields) - } - - return nil - } - } -} diff --git a/internal/pkg/http/custom_echo/middlewares/otel_metrics/request_status_middleware.go b/internal/pkg/http/custom_echo/middlewares/otel_metrics/request_status_middleware.go deleted file mode 100644 index 6b2139a1..00000000 --- a/internal/pkg/http/custom_echo/middlewares/otel_metrics/request_status_middleware.go +++ /dev/null @@ -1,52 +0,0 @@ -package otelMetrics - -// ref:https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go - -import ( - "fmt" - - "github.com/labstack/echo/v4" - "go.opentelemetry.io/otel/attribute" - api "go.opentelemetry.io/otel/metric" -) - -var ( - successCounter api.Float64Counter - errorCounter api.Float64Counter -) - -// Middleware adds request status metrics to the otel -// ref: https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go -func Middleware(meter api.Meter, serviceName string) echo.MiddlewareFunc { - errorCounter, _ := meter.Float64Counter( - fmt.Sprintf("%s_error_http_requests_total", serviceName), - api.WithDescription("The total number of error http requests"), - ) - successCounter, _ = meter.Float64Counter( - fmt.Sprintf("%s_success_http_requests_total", serviceName), - api.WithDescription("The total number of success http requests"), - ) - - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - err := next(c) - request := c.Request() - ctx := request.Context() - - attrs := api.WithAttributes( - attribute.Key("MetricsType").String("Http"), - ) - - if err != nil { - errorCounter.Add(ctx, 1, attrs) - } else { - successCounter.Add(ctx, 1, attrs) - } - - // update request context - c.SetRequest(request.WithContext(ctx)) - - return err - } - } -} diff --git a/internal/pkg/http/custom_echo/otel/tracing/utils.go b/internal/pkg/http/custom_echo/otel/tracing/utils.go deleted file mode 100644 index 351ff2ac..00000000 --- a/internal/pkg/http/custom_echo/otel/tracing/utils.go +++ /dev/null @@ -1,59 +0,0 @@ -package tracing - -import ( - "context" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/custom_echo/constants" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/problemDetails" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - semconv "go.opentelemetry.io/otel/semconv/v1.21.0" - "go.opentelemetry.io/otel/trace" -) - -// TraceHttpErrFromSpan setting span with status error with error message -func TraceHttpErrFromSpan(span trace.Span, err error) error { - if err != nil { - stackTraceError := errorUtils.ErrorsWithStack(err) - span.SetStatus(codes.Error, "") - span.SetAttributes(attribute.String(constants.Otel.HttpErrorMessage, stackTraceError)) - if customErrors.IsCustomError(err) { - httpError := problemDetails.ParseError(err) - span.SetAttributes(semconv.HTTPStatusCode(httpError.GetStatus())) - } - span.RecordError(err) - } - - return err -} - -// TraceHttpErrFromSpanWithCode setting span with status error with error message -func TraceHttpErrFromSpanWithCode(span trace.Span, err error, code int) error { - if err != nil { - stackTraceError := errorUtils.ErrorsWithStack(err) - span.SetStatus(codes.Error, "") - span.SetAttributes(semconv.HTTPStatusCode(code)) - span.SetAttributes(attribute.String(constants.Otel.HttpErrorMessage, stackTraceError)) - span.RecordError(err) - } - - return err -} - -func TraceHttpErrFromContext(ctx context.Context, err error) error { - // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors - span := trace.SpanFromContext(ctx) - defer span.End() - - if err != nil { - stackTraceError := errorUtils.ErrorsWithStack(err) - span.SetStatus(codes.Error, "") - span.SetAttributes(attribute.String(constants.Otel.HttpErrorMessage, stackTraceError)) - span.RecordError(err) - } - - return err -} diff --git a/internal/pkg/http/custom_echo/config/echo_http_options.go b/internal/pkg/http/customecho/config/echo_http_options.go similarity index 71% rename from internal/pkg/http/custom_echo/config/echo_http_options.go rename to internal/pkg/http/customecho/config/echo_http_options.go index fbce4deb..a1b6e924 100644 --- a/internal/pkg/http/custom_echo/config/echo_http_options.go +++ b/internal/pkg/http/customecho/config/echo_http_options.go @@ -4,14 +4,14 @@ import ( "fmt" "net/url" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) -var optionName = strcase.ToLowerCamel(typeMapper.GetTypeNameByT[EchoHttpOptions]()) +var optionName = strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[EchoHttpOptions]()) type EchoHttpOptions struct { Port string `mapstructure:"port" validate:"required" env:"TcpPort"` @@ -21,7 +21,7 @@ type EchoHttpOptions struct { IgnoreLogUrls []string `mapstructure:"ignoreLogUrls"` Timeout int `mapstructure:"timeout" env:"Timeout"` Host string `mapstructure:"host" env:"Host"` - Name string `mapstructure:"name" env:"Name"` + Name string `mapstructure:"name" env:"ShortTypeName"` } func (c *EchoHttpOptions) Address() string { @@ -36,6 +36,6 @@ func (c *EchoHttpOptions) BasePathAddress() string { return path } -func ProvideConfig(environment environemnt.Environment) (*EchoHttpOptions, error) { +func ProvideConfig(environment environment.Environment) (*EchoHttpOptions, error) { return config.BindConfigKey[*EchoHttpOptions](optionName, environment) } diff --git a/internal/pkg/http/customecho/contracts/echoserver.go b/internal/pkg/http/customecho/contracts/echoserver.go new file mode 100644 index 00000000..d58f5dd4 --- /dev/null +++ b/internal/pkg/http/customecho/contracts/echoserver.go @@ -0,0 +1,23 @@ +package contracts + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + + "github.com/labstack/echo/v4" +) + +type EchoHttpServer interface { + RunHttpServer(configEcho ...func(echo *echo.Echo)) error + GracefulShutdown(ctx context.Context) error + ApplyVersioningFromHeader() + GetEchoInstance() *echo.Echo + Logger() logger.Logger + Cfg() *config.EchoHttpOptions + SetupDefaultMiddlewares() + RouteBuilder() *RouteBuilder + AddMiddlewares(middlewares ...echo.MiddlewareFunc) + ConfigGroup(groupName string, groupFunc func(group *echo.Group)) +} diff --git a/internal/pkg/http/custom_echo/route_builder.go b/internal/pkg/http/customecho/contracts/route_builder.go similarity index 96% rename from internal/pkg/http/custom_echo/route_builder.go rename to internal/pkg/http/customecho/contracts/route_builder.go index ba9cc52a..2bfb739a 100644 --- a/internal/pkg/http/custom_echo/route_builder.go +++ b/internal/pkg/http/customecho/contracts/route_builder.go @@ -1,4 +1,4 @@ -package customEcho +package contracts import "github.com/labstack/echo/v4" diff --git a/internal/pkg/http/custom_echo/custom_echo_fx.go b/internal/pkg/http/customecho/custom_echo_fx.go similarity index 84% rename from internal/pkg/http/custom_echo/custom_echo_fx.go rename to internal/pkg/http/customecho/custom_echo_fx.go index c86ccafc..f3588728 100644 --- a/internal/pkg/http/custom_echo/custom_echo_fx.go +++ b/internal/pkg/http/customecho/custom_echo_fx.go @@ -5,8 +5,9 @@ import ( "errors" "net/http" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/custom_echo/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "go.uber.org/fx" ) @@ -42,7 +43,11 @@ var ( ) // we don't want to register any dependencies here, its func body should execute always even we don't request for that, so we should use `invoke` -func registerHooks(lc fx.Lifecycle, echoServer EchoHttpServer, logger logger.Logger) { +func registerHooks( + lc fx.Lifecycle, + echoServer contracts.EchoHttpServer, + logger logger.Logger, +) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { // https://github.com/uber-go/fx/blob/v1.20.0/app.go#L573 @@ -52,7 +57,10 @@ func registerHooks(lc fx.Lifecycle, echoServer EchoHttpServer, logger logger.Log go func() { // https://medium.com/@mokiat/proper-http-shutdown-in-go-bd3bfaade0f2 // When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return ErrServerClosed. Make sure the program doesn’t exit and waits instead for Shutdown to return. - if err := echoServer.RunHttpServer(); !errors.Is(err, http.ErrServerClosed) { + if err := echoServer.RunHttpServer(); !errors.Is( + err, + http.ErrServerClosed, + ) { // do a fatal for going to OnStop process logger.Fatalf( "(EchoHttpServer.RunHttpServer) error in running server: {%v}", @@ -75,7 +83,8 @@ func registerHooks(lc fx.Lifecycle, echoServer EchoHttpServer, logger logger.Log // https://medium.com/@mokiat/proper-http-shutdown-in-go-bd3bfaade0f2 // When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return ErrServerClosed. Make sure the program doesn’t exit and waits instead for Shutdown to return. if err := echoServer.GracefulShutdown(ctx); err != nil { - echoServer.Logger().Errorf("error shutting down echo server: %v", err) + echoServer.Logger(). + Errorf("error shutting down echo server: %v", err) } else { echoServer.Logger().Info("echo server shutdown gracefully") } diff --git a/internal/pkg/http/customecho/echo_server.go b/internal/pkg/http/customecho/echo_server.go new file mode 100644 index 00000000..76fc2c77 --- /dev/null +++ b/internal/pkg/http/customecho/echo_server.go @@ -0,0 +1,168 @@ +package customEcho + +import ( + "context" + "fmt" + "strings" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" + hadnlers "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/hadnlers" + ipratelimit "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/middlewares/ip_ratelimit" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/middlewares/log" + otelMetrics "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/middlewares/otel_metrics" + oteltracing "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/middlewares/otel_tracing" + problemdetail "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/middlewares/problem_detail" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "go.opentelemetry.io/otel/metric" +) + +type echoHttpServer struct { + echo *echo.Echo + config *config.EchoHttpOptions + log logger.Logger + meter metric.Meter + routeBuilder *contracts.RouteBuilder +} + +func NewEchoHttpServer( + config *config.EchoHttpOptions, + logger logger.Logger, + meter metric.Meter, +) contracts.EchoHttpServer { + e := echo.New() + e.HideBanner = true + + return &echoHttpServer{ + echo: e, + config: config, + log: logger, + meter: meter, + routeBuilder: contracts.NewRouteBuilder(e), + } +} + +func (s *echoHttpServer) RunHttpServer( + configEcho ...func(echo *echo.Echo), +) error { + s.echo.Server.ReadTimeout = constants.ReadTimeout + s.echo.Server.WriteTimeout = constants.WriteTimeout + s.echo.Server.MaxHeaderBytes = constants.MaxHeaderBytes + + if len(configEcho) > 0 { + ehcoFunc := configEcho[0] + if ehcoFunc != nil { + configEcho[0](s.echo) + } + } + + // https://echo.labstack.com/guide/http_server/ + return s.echo.Start(s.config.Port) +} + +func (s *echoHttpServer) Logger() logger.Logger { + return s.log +} + +func (s *echoHttpServer) Cfg() *config.EchoHttpOptions { + return s.config +} + +func (s *echoHttpServer) RouteBuilder() *contracts.RouteBuilder { + return s.routeBuilder +} + +func (s *echoHttpServer) ConfigGroup( + groupName string, + groupFunc func(group *echo.Group), +) { + groupFunc(s.echo.Group(groupName)) +} + +func (s *echoHttpServer) AddMiddlewares(middlewares ...echo.MiddlewareFunc) { + if len(middlewares) > 0 { + s.echo.Use(middlewares...) + } +} + +func (s *echoHttpServer) GracefulShutdown(ctx context.Context) error { + err := s.echo.Shutdown(ctx) + if err != nil { + return err + } + + return nil +} + +func (s *echoHttpServer) SetupDefaultMiddlewares() { + skipper := func(c echo.Context) bool { + return strings.Contains(c.Request().URL.Path, "swagger") || + strings.Contains(c.Request().URL.Path, "metrics") || + strings.Contains(c.Request().URL.Path, "health") || + strings.Contains(c.Request().URL.Path, "favicon.ico") + } + + // set error handler + s.echo.HTTPErrorHandler = func(err error, c echo.Context) { + // bypass skip endpoints and its error + if skipper(c) { + return + } + + hadnlers.ProblemDetailErrorHandlerFunc(err, c, s.log) + } + + // log errors and information + s.echo.Use( + log.EchoLogger( + s.log, + log.WithSkipper(skipper), + ), + ) + s.echo.Use( + oteltracing.HttpTrace( + oteltracing.WithSkipper(skipper), + oteltracing.WithServiceName(s.config.Name), + ), + ) + s.echo.Use( + otelMetrics.HTTPMetrics( + otelMetrics.WithServiceName(s.config.Name), + otelMetrics.WithSkipper(skipper)), + ) + s.echo.Use(middleware.BodyLimit(constants.BodyLimit)) + s.echo.Use(ipratelimit.IPRateLimit()) + s.echo.Use(middleware.RequestID()) + s.echo.Use(middleware.GzipWithConfig(middleware.GzipConfig{ + Level: constants.GzipLevel, + Skipper: skipper, + })) + // should be last middleware + s.echo.Use(problemdetail.ProblemDetail(problemdetail.WithSkipper(skipper))) +} + +func (s *echoHttpServer) ApplyVersioningFromHeader() { + s.echo.Pre(apiVersion) +} + +func (s *echoHttpServer) GetEchoInstance() *echo.Echo { + return s.echo +} + +// APIVersion Header Based Versioning +func apiVersion(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + req := c.Request() + headers := req.Header + + apiVersion := headers.Get("version") + + req.URL.Path = fmt.Sprintf("/%s%s", apiVersion, req.URL.Path) + + return next(c) + } +} diff --git a/internal/pkg/http/customecho/hadnlers/problemdetail_error_handler.go b/internal/pkg/http/customecho/hadnlers/problemdetail_error_handler.go new file mode 100644 index 00000000..8a8239a6 --- /dev/null +++ b/internal/pkg/http/customecho/hadnlers/problemdetail_error_handler.go @@ -0,0 +1,29 @@ +package handlers + +import ( + problemDetails "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/problemdetails" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" +) + +func ProblemDetailErrorHandlerFunc( + err error, + c echo.Context, + logger logger.Logger, +) { + var problem problemDetails.ProblemDetailErr + + // if error was not problem detail we will convert the error to a problem detail + if ok := errors.As(err, &problem); !ok { + problem = problemDetails.ParseError(err) + } + + if !c.Response().Committed && problem != nil { + // `WriteTo` will set `Response status code` to our problem details status + if _, err := problemDetails.WriteTo(problem, c.Response()); err != nil { + logger.Error(err) + } + } +} diff --git a/internal/pkg/http/customecho/middlewares/ip_ratelimit/config.go b/internal/pkg/http/customecho/middlewares/ip_ratelimit/config.go new file mode 100644 index 00000000..ddc919a8 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/ip_ratelimit/config.go @@ -0,0 +1,41 @@ +package ipratelimit + +import ( + "time" +) + +type config struct { + period time.Duration + limit int64 +} + +var defualtConfig = config{ + period: 1 * time.Hour, + limit: 1000, +} + +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +func WithPeriod(d time.Duration) Option { + return optionFunc(func(cfg *config) { + if cfg.period != 0 { + cfg.period = d + } + }) +} + +func WithLimit(v int64) Option { + return optionFunc(func(cfg *config) { + if cfg.limit != 0 { + cfg.limit = v + } + }) +} diff --git a/internal/pkg/http/customecho/middlewares/ip_ratelimit/ip_ratelimit.go b/internal/pkg/http/customecho/middlewares/ip_ratelimit/ip_ratelimit.go new file mode 100644 index 00000000..df833a22 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/ip_ratelimit/ip_ratelimit.go @@ -0,0 +1,79 @@ +package ipratelimit + +import ( + "log" + "net/http" + "strconv" + + "github.com/labstack/echo/v4" + "github.com/ulule/limiter/v3" + "github.com/ulule/limiter/v3/drivers/store/memory" +) + +// ref: https://github.com/ulule/limiter-examples/blob/master/echo/main.go + +func IPRateLimit(opts ...Option) echo.MiddlewareFunc { + config := defualtConfig + + for _, opt := range opts { + opt.apply(&config) + } + + rate := limiter.Rate{ + Period: config.period, + Limit: config.limit, + } + + var ( + ipRateLimiter *limiter.Limiter + store limiter.Store + ) + + store = memory.NewStore() + ipRateLimiter = limiter.New(store, rate) + + // 2. Return middleware handler + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + ip := c.RealIP() + limiterCtx, err := ipRateLimiter.Get(c.Request().Context(), ip) + if err != nil { + log.Printf( + "IPRateLimit - ipRateLimiter.Get - err: %v, %s on %s", + err, + ip, + c.Request().URL, + ) + + return c.JSON(http.StatusInternalServerError, echo.Map{ + "success": false, + "message": err, + }) + } + + h := c.Response().Header() + h.Set("X-RateLimit-Limit", strconv.FormatInt(limiterCtx.Limit, 10)) + h.Set( + "X-RateLimit-Remaining", + strconv.FormatInt(limiterCtx.Remaining, 10), + ) + h.Set("X-RateLimit-Reset", strconv.FormatInt(limiterCtx.Reset, 10)) + + if limiterCtx.Reached { + log.Printf( + "Too Many Requests from %s on %s", + ip, + c.Request().URL, + ) + + return c.JSON(http.StatusTooManyRequests, echo.Map{ + "success": false, + "message": "Too Many Requests on " + c.Request().URL.String(), + }) + } + + // log.Printf("%s request continue", c.RealIP()) + return next(c) + } + } +} diff --git a/internal/pkg/http/customecho/middlewares/log/config.go b/internal/pkg/http/customecho/middlewares/log/config.go new file mode 100644 index 00000000..5e4cfbc1 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/log/config.go @@ -0,0 +1,27 @@ +package log + +import "github.com/labstack/echo/v4/middleware" + +// config defines the config for Logger middleware. +type config struct { + // Skipper defines a function to skip middleware. + Skipper middleware.Skipper +} + +// Option specifies instrumentation configuration options. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +// WithSkipper specifies a skipper for allowing requests to skip generating spans. +func WithSkipper(skipper middleware.Skipper) Option { + return optionFunc(func(cfg *config) { + cfg.Skipper = skipper + }) +} diff --git a/internal/pkg/http/customecho/middlewares/log/log_middleware.go b/internal/pkg/http/customecho/middlewares/log/log_middleware.go new file mode 100644 index 00000000..48f0b901 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/log/log_middleware.go @@ -0,0 +1,126 @@ +package log + +import ( + "fmt" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +// EchoLogger returns echo middleware which will log incoming requests. +func EchoLogger(l logger.Logger, opts ...Option) echo.MiddlewareFunc { + cfg := config{} + for _, opt := range opts { + opt.apply(&cfg) + } + + if cfg.Skipper == nil { + cfg.Skipper = middleware.DefaultSkipper + } + + requestMiddleware := middleware.RequestLoggerWithConfig( + middleware.RequestLoggerConfig{ + Skipper: cfg.Skipper, + LogRequestID: true, + LogRemoteIP: true, + LogHost: true, + LogMethod: true, + LogURI: true, + LogUserAgent: true, + LogStatus: true, + LogError: true, + LogLatency: true, + LogContentLength: true, + LogResponseSize: true, + + LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { + l.Infow( + fmt.Sprintf( + "[Request Middleware] REQUEST: uri: %v, status: %v\n", + v.URI, + v.Status, + ), + logger.Fields{ + "uri": v.URI, + "status": v.Status, + "id": v.RequestID, + "remote_ip": v.RemoteIP, + "host": v.Host, + "method": v.Method, + "user_agent": v.UserAgent, + "error": v.Error, + "latency": v.Latency.Nanoseconds(), + "latency_human": v.Latency.String(), + "bytes_in": v.ContentLength, + "bytes_out": v.ResponseSize, + }, + ) + + return nil + }, + }, + ) + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if cfg.Skipper(c) { + return requestMiddleware(next)(c) + } + + start := time.Now() + + err := requestMiddleware(next)(c) + if err != nil { + // handle echo error in this middleware and raise echo errorhandler func and our custom error handler + // when we call c.Error more than once, `c.Response().Committed` becomes true and response doesn't write to client again in our error handler + // Error will update response status with occurred error object status code + c.Error(err) + } + + req := c.Request() + res := c.Response() + + fields := map[string]interface{}{ + "remote_ip": c.RealIP(), + "latency": time.Since(start).String(), + "host": req.Host, + "request": fmt.Sprintf("%s %s", req.Method, req.RequestURI), + "status": res.Status, + "size": res.Size, + "user_agent": req.UserAgent(), + } + + id := req.Header.Get(echo.HeaderXRequestID) + if id == "" { + id = res.Header().Get(echo.HeaderXRequestID) + } + fields["request_id"] = id + + n := res.Status + switch { + case n >= 500: + l.Errorw( + "EchoServer logger middleware: Server error", + fields, + ) + case n >= 400: + l.Errorw( + "EchoServer logger middleware: Client error", + fields, + ) + case n >= 300: + l.Errorw( + "EchoServer logger middleware: Redirection", + fields, + ) + default: + l.Infow("EchoServer logger middleware: Success", fields) + } + + return nil + } + } +} diff --git a/internal/pkg/http/customecho/middlewares/otel_metrics/config.go b/internal/pkg/http/customecho/middlewares/otel_metrics/config.go new file mode 100644 index 00000000..52bb3009 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/otel_metrics/config.go @@ -0,0 +1,110 @@ +package otelmetrics + +import ( + "github.com/labstack/echo/v4/middleware" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" +) + +type config struct { + metricsProvider metric.MeterProvider + + skipper middleware.Skipper + + namespace string + + serviceName string + + instrumentationName string + + // enableTotalMetric whether to enable a metric to count the total number of http requests. + enableTotalMetric bool + + // enableDurMetric whether to enable a metric to track the duration of each request. + enableDurMetric bool + + // enableDurMetric whether to enable a metric that tells the number of current in-flight requests. + enableInFlightMetric bool +} + +var defualtConfig = config{ + metricsProvider: otel.GetMeterProvider(), + enableTotalMetric: true, + enableDurMetric: true, + enableInFlightMetric: true, + skipper: middleware.DefaultSkipper, + serviceName: "application", + instrumentationName: "echo", +} + +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +// WithNamespace will set the metrics namespace that will be added to all metric configurations. It will be a prefix to each +// metric name. For example, if namespace is "myapp", then requests_total metric will be myapp_http_requests_total +// (after namespace there is also the subsystem prefix, "http" in this case). +func WithNamespace(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.namespace != "" { + cfg.namespace = v + } + }) +} + +func WithServiceName(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.serviceName != "" { + cfg.serviceName = v + } + }) +} + +func WithInstrumentationName(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.instrumentationName != "" { + cfg.instrumentationName = v + } + }) +} + +// WithSkipper specifies a skipper for allowing requests to skip generating spans. +func WithSkipper(skipper middleware.Skipper) Option { + return optionFunc(func(cfg *config) { + cfg.skipper = skipper + }) +} + +// WithMeterProvider specifies a meter provider to use for creating a metrics. +// If none is specified, the global provider is used. +func WithMeterProvider(provider metric.MeterProvider) Option { + return optionFunc(func(cfg *config) { + if provider != nil { + cfg.metricsProvider = provider + } + }) +} + +func WithInFlightMetric(enabled bool) Option { + return optionFunc(func(cfg *config) { + cfg.enableInFlightMetric = enabled + }) +} + +func WithTotalMetric(enabled bool) Option { + return optionFunc(func(cfg *config) { + cfg.enableTotalMetric = enabled + }) +} + +func WithDurMetric(enabled bool) Option { + return optionFunc(func(cfg *config) { + cfg.enableDurMetric = enabled + }) +} diff --git a/internal/pkg/http/customecho/middlewares/otel_metrics/metric.go b/internal/pkg/http/customecho/middlewares/otel_metrics/metric.go new file mode 100644 index 00000000..3cf76957 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/otel_metrics/metric.go @@ -0,0 +1,377 @@ +package otelmetrics + +// ref:https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go +// https://github.com/labstack/echo-contrib/blob/master/prometheus/prometheus.go +// https://github.com/worldline-go/tell/tree/main/metric/metricecho +// https://opentelemetry.io/docs/instrumentation/go/manual/#metrics +// https://opentelemetry.io/docs/specs/otel/metrics/semantic_conventions/http-metrics/ + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/labstack/echo/v4" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" +) + +// HTTPLabels will contain HTTP label values for each added metric. Not all labels apply to all metrics, read the +// documentation in each metric method to find out which labels are available for that metric. +type HTTPLabels struct { + // Method should be the HTTP method in the HTTP request. + Method string + // Code should be the HTTP status code in the HTTP response. If there is no response, the Code should be 0. + Code int + // Path is the request URL's path. Should not contain the query string, and ideally it should only be the route + // definition. For example `/users/{ID}` instead of `/users/100`. + Path string + Host string +} + +// HTTPMetricsRecorder is a recorder of HTTP metrics for prometheus. Use NewHTTPMetricsRecorder to initialize it. +type HTTPMetricsRecorder struct { + cfg config + mp metric.MeterProvider + meter metric.Meter + reqTotal metric.Int64Counter + reqDuration metric.Float64Histogram + reqInFlight metric.Int64UpDownCounter + resSize metric.Int64Histogram + reqSize metric.Int64Histogram + errorCounter metric.Float64Counter + successCounter metric.Float64Counter +} + +// NewHTTPMetricsRecorder creates a new HTTPMetricsRecorder. Calling this function will automatically register the new metrics to reg. +func NewHTTPMetricsRecorder(cfg config) *HTTPMetricsRecorder { + // Meter can be a global/package variable. + meter := cfg.metricsProvider.Meter(cfg.instrumentationName) + + m := HTTPMetricsRecorder{ + cfg: cfg, + mp: cfg.metricsProvider, + meter: meter, + } + + if err := m.register(); err != nil { + // possible errors here include duplicate metric or same metrics with inconsistent labels or help strings. It is + // unlikely that it will happen if not by mistake. Nonetheless, we would like to know if such case occurs, hence + // a panic + panic(err) + } + + return &m +} + +func (h *HTTPMetricsRecorder) namespacedValue(v string) string { + if h.cfg.namespace != "" { + return h.cfg.namespace + "_" + v + } + + return v +} + +func (h *HTTPMetricsRecorder) register() error { + // https://opentelemetry.io/docs/specs/otel/metrics/semantic_conventions/http-metrics/#http-server + errorCounter, err := h.meter.Float64Counter( + "http.server.total_error_request", + metric.WithUnit("count"), + metric.WithDescription("The total number of error http requests"), + ) + if err != nil { + return fmt.Errorf( + "meter %s cannot set; %w", + "http.server.total_error_request", + err, + ) + } + + h.errorCounter = errorCounter + + successCounter, err := h.meter.Float64Counter( + "http.server.total_success_request", + metric.WithUnit("count"), + metric.WithDescription("The total number of success http requests"), + ) + if err != nil { + return fmt.Errorf( + "meter %s cannot set; %w", + "http.server.total_success_request", + err, + ) + } + + h.successCounter = successCounter + + if h.cfg.enableTotalMetric { + reqTotal, err := h.meter.Int64Counter( + h.namespacedValue("http.server.total_request"), + metric.WithUnit("count"), + metric.WithDescription("The total number of requests"), + ) + if err != nil { + return fmt.Errorf( + "meter %s cannot set; %w", + "http.server.total_request", + err, + ) + } + + h.reqTotal = reqTotal + } + + if h.cfg.enableDurMetric { + reqDuration, err := h.meter.Float64Histogram( + h.namespacedValue("http.server.duration"), + metric.WithUnit("s"), // Specify the unit as "seconds" + metric.WithDescription( + "The total duration of a request in seconds", + ), + ) + if err != nil { + return fmt.Errorf( + "meter %s cannot set; %w", + "http.server.duration", + err, + ) + } + + h.reqDuration = reqDuration + } + + if h.cfg.enableInFlightMetric { + reqInFlight, err := h.meter.Int64UpDownCounter( + h.namespacedValue("http.server.request_inflight_total"), + metric.WithUnit("count"), + metric.WithDescription("The current number of in-flight requests"), + ) + if err != nil { + return fmt.Errorf( + "meter %s cannot set; %w", + "http.server.request_inflight_total", + err, + ) + } + + h.reqInFlight = reqInFlight + } + + resSize, err := h.meter.Int64Histogram( + h.namespacedValue("http.server.response.size"), + metric.WithUnit("bytes"), + metric.WithDescription("The HTTP response sizes in bytes."), + ) + if err != nil { + return fmt.Errorf( + "meter %s cannot set; %w", + "http.server.response.size", + err, + ) + } + + h.resSize = resSize + + reqSize, err := h.meter.Int64Histogram( + h.namespacedValue("http.server.request.size"), + metric.WithUnit("bytes"), + metric.WithDescription("The HTTP request sizes in bytes."), + ) + if err != nil { + return fmt.Errorf( + "meter %s cannot set; %w", + "http.server.request.size", + err, + ) + } + + h.reqSize = reqSize + + return nil +} + +// AddRequestToTotal adds 1 to the total number of requests. All labels should be specified. +func (h *HTTPMetricsRecorder) AddRequestToTotal( + ctx context.Context, + values HTTPLabels, +) { + if h.reqTotal == nil { + return + } + + h.reqTotal.Add(ctx, 1, + metric.WithAttributes( + attribute.String("method", values.Method), + attribute.Int("code", values.Code), + attribute.String("type", "Http"), + ), + ) +} + +// AddRequestDuration registers a request along with its duration. All labels should be specified. +func (h *HTTPMetricsRecorder) AddRequestDuration( + ctx context.Context, + duration time.Duration, + values HTTPLabels, +) { + if h.reqDuration == nil { + return + } + + h.reqDuration.Record( + ctx, duration.Seconds(), + metric.WithAttributes( + attribute.String("method", values.Method), + attribute.String("host", values.Host), + attribute.String("path", values.Path), + attribute.Int("code", values.Code), + attribute.String("type", "Http"), + ), + ) +} + +// AddInFlightRequest Adds 1 to the number of current in-flight requests. All labels should be specified except for +// `Code`, as it will just be ignored. To remove a request use RemInFlightRequest. +func (h *HTTPMetricsRecorder) AddInFlightRequest( + ctx context.Context, + values HTTPLabels, +) { + if h.reqInFlight == nil { + return + } + + h.reqInFlight.Add( + ctx, + 1, + metric.WithAttributes( + attribute.String("method", values.Method), + attribute.String("path", values.Path), + attribute.String("type", "Http"), + ), + ) +} + +func (h *HTTPMetricsRecorder) AddRequestError( + ctx context.Context, + values HTTPLabels, +) { + if h.errorCounter == nil { + return + } + + h.errorCounter.Add( + ctx, + 1, + metric.WithAttributes( + attribute.String("method", values.Method), + attribute.String("path", values.Path), + attribute.String("type", "Http"), + attribute.Int("code", values.Code), + ), + ) +} + +func (h *HTTPMetricsRecorder) AddRequestSuccess( + ctx context.Context, + values HTTPLabels, +) { + if h.successCounter == nil { + return + } + + h.successCounter.Add( + ctx, + 1, + metric.WithAttributes( + attribute.String("method", values.Method), + attribute.String("path", values.Path), + attribute.String("type", "Http"), + attribute.Int("code", values.Code), + ), + ) +} + +// RemInFlightRequest Remove 1 from the number of current in-flight requests. All labels should be specified except +// for `Code`, as it will just be ignored. Labels should match the ones passed to the equivalent AddInFlightRequest call. +func (h *HTTPMetricsRecorder) RemInFlightRequest( + ctx context.Context, + values HTTPLabels, +) { + if h.reqInFlight == nil { + return + } + + h.reqInFlight.Add( + ctx, + -1, + metric.WithAttributes( + attribute.String("method", values.Method), + attribute.String("path", values.Path), + attribute.String("type", "Http"), + ), + ) +} + +func (h *HTTPMetricsRecorder) AddRequestSize( + ctx context.Context, + request *http.Request, + values HTTPLabels, +) { + if h.reqSize == nil { + return + } + + size := computeApproximateRequestSize(request) + h.reqSize.Record(ctx, int64(size), metric.WithAttributes( + attribute.String("method", values.Method), + attribute.String("path", values.Path), + attribute.String("type", "Http"), + attribute.String("host", values.Host), + attribute.Int("code", values.Code), + )) +} + +func (h *HTTPMetricsRecorder) AddResponseSize( + ctx context.Context, + response *echo.Response, + values HTTPLabels, +) { + if h.resSize == nil { + return + } + + size := response.Size + h.resSize.Record(ctx, size, metric.WithAttributes( + attribute.String("method", values.Method), + attribute.String("path", values.Path), + attribute.String("type", "Http"), + attribute.String("host", values.Host), + attribute.Int("code", values.Code), + )) +} + +func computeApproximateRequestSize(r *http.Request) int { + s := 0 + if r.URL != nil { + s = len(r.URL.Path) + } + + s += len(r.Method) + s += len(r.Proto) + for name, values := range r.Header { + s += len(name) + for _, value := range values { + s += len(value) + } + } + s += len(r.Host) + + // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL. + + if r.ContentLength != -1 { + s += int(r.ContentLength) + } + + return s +} diff --git a/internal/pkg/http/customecho/middlewares/otel_metrics/metrics_middleware.go b/internal/pkg/http/customecho/middlewares/otel_metrics/metrics_middleware.go new file mode 100644 index 00000000..3312f1f5 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/otel_metrics/metrics_middleware.go @@ -0,0 +1,79 @@ +package otelmetrics + +// ref:https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go +// https://github.com/labstack/echo-contrib/blob/master/prometheus/prometheus.go +// https://github.com/worldline-go/tell/tree/main/metric/metricecho +// https://opentelemetry.io/docs/instrumentation/go/manual/#metrics + +// https://opentelemetry.io/docs/specs/otel/metrics/semantic_conventions/http-metrics/ + +import ( + "time" + + "github.com/labstack/echo/v4" +) + +// HTTPMetrics is a middleware for adding otel metrics for a given request +// If recorder config is nil, the middleware will use a recorder with default configuration. +func HTTPMetrics(opts ...Option) echo.MiddlewareFunc { + config := defualtConfig + + for _, opt := range opts { + opt.apply(&config) + } + + httpMetricsRecorder := NewHTTPMetricsRecorder(config) + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.skipper(c) { + return next(c) + } + + request := c.Request() + ctx := request.Context() + + values := HTTPLabels{ + Method: request.Method, + Path: c.Path(), + Host: request.URL.Host, + } + + httpMetricsRecorder.AddInFlightRequest(ctx, values) + + start := time.Now() + + defer func() { + elapsed := time.Since(start) + + values.Code = c.Response().Status + + httpMetricsRecorder.AddRequestToTotal(ctx, values) + + httpMetricsRecorder.AddRequestDuration(ctx, elapsed, values) + + httpMetricsRecorder.RemInFlightRequest(ctx, values) + + httpMetricsRecorder.AddRequestSize(ctx, request, values) + + httpMetricsRecorder.AddResponseSize(ctx, c.Response(), values) + + if err != nil { + httpMetricsRecorder.AddRequestError(ctx, values) + } else { + httpMetricsRecorder.AddRequestSuccess(ctx, values) + } + }() + + err = next(c) + if err != nil { + // handle echo error in this middleware and raise echo errorhandler func and our custom error handler + // when we call c.Error more than once, `c.Response().Committed` becomes true and response doesn't write to client again in our error handler + // Error will update response status with occurred error object status code + c.Error(err) + } + + return err + } + } +} diff --git a/internal/pkg/http/customecho/middlewares/otel_metrics/view.go b/internal/pkg/http/customecho/middlewares/otel_metrics/view.go new file mode 100644 index 00000000..c2dc5532 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/otel_metrics/view.go @@ -0,0 +1,33 @@ +package otelmetrics + +import ( + "go.opentelemetry.io/otel/sdk/metric" +) + +// https://opentelemetry.io/docs/instrumentation/go/manual/#registering-views +func GetViews() []metric.View { + customBucketView := metric.NewView( + metric.Instrument{ + Name: "*request_duration_seconds", + }, + metric.Stream{ + Aggregation: metric.AggregationExplicitBucketHistogram{ + Boundaries: []float64{ + .005, + .01, + .025, + .05, + .1, + .25, + .5, + 1, + 2.5, + 5, + 10, + }, + }, + }, + ) + + return []metric.View{customBucketView} +} diff --git a/internal/pkg/http/customecho/middlewares/otel_tracing/config.go b/internal/pkg/http/customecho/middlewares/otel_tracing/config.go new file mode 100644 index 00000000..e01d1641 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/otel_tracing/config.go @@ -0,0 +1,82 @@ +package oteltracing + +import ( + "github.com/labstack/echo/v4/middleware" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" + oteltrace "go.opentelemetry.io/otel/trace" +) + +// Ref: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/github.com/labstack/echo/otelecho/echo.go + +// config is used to configure the mux middleware. +type config struct { + tracerProvider oteltrace.TracerProvider + propagators propagation.TextMapPropagator + skipper middleware.Skipper + instrumentationName string + serviceName string +} + +// Option specifies instrumentation configuration options. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +var defualtConfig = config{ + tracerProvider: otel.GetTracerProvider(), + propagators: otel.GetTextMapPropagator(), + skipper: middleware.DefaultSkipper, + instrumentationName: "echo", + serviceName: "app", +} + +// WithPropagators specifies propagators to use for extracting +// information from the HTTP requests. If none are specified, global +// ones will be used. +func WithPropagators(propagators propagation.TextMapPropagator) Option { + return optionFunc(func(cfg *config) { + if propagators != nil { + cfg.propagators = propagators + } + }) +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// If none is specified, the global provider is used. +func WithTracerProvider(provider oteltrace.TracerProvider) Option { + return optionFunc(func(cfg *config) { + if provider != nil { + cfg.tracerProvider = provider + } + }) +} + +// WithSkipper specifies a skipper for allowing requests to skip generating spans. +func WithSkipper(skipper middleware.Skipper) Option { + return optionFunc(func(cfg *config) { + cfg.skipper = skipper + }) +} + +func WithInstrumentationName(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.instrumentationName != "" { + cfg.instrumentationName = v + } + }) +} + +func WithServiceName(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.serviceName != "" { + cfg.serviceName = v + } + }) +} diff --git a/internal/pkg/http/customecho/middlewares/otel_tracing/tracing_middleware.go b/internal/pkg/http/customecho/middlewares/otel_tracing/tracing_middleware.go new file mode 100644 index 00000000..38002ed2 --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/otel_tracing/tracing_middleware.go @@ -0,0 +1,96 @@ +package oteltracing + +// Ref: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/github.com/labstack/echo/otelecho/echo.go +// Note: for consideration of 4xx status as error in traces, I customized original echo oteltrace middleware for handing my requirements + +// https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/http/ + +import ( + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + + "github.com/labstack/echo/v4" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/semconv/v1.20.0/httpconv" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + oteltrace "go.opentelemetry.io/otel/trace" +) + +// HttpTrace returns echo middleware which will trace incoming requests. +func HttpTrace(opts ...Option) echo.MiddlewareFunc { + cfg := defualtConfig + for _, opt := range opts { + opt.apply(&cfg) + } + + tracer := cfg.tracerProvider.Tracer(cfg.instrumentationName) + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if cfg.skipper(c) { + return next(c) + } + + c.Set(cfg.instrumentationName, tracer) + request := c.Request() + // doesn't contain trace information and after completing trace on new ctx we should go back to our old savedCtx + savedCtx := request.Context() + + defer func() { + // we should go back to previous context in end of our operation because new context contains child spans, and if we don't set it back to previous context, after returning from this method all further parent spans becomes a new child for existing child span! + request = request.WithContext(savedCtx) + c.SetRequest(request) + }() + + // create new ctx from existing savedCtx + ctx := cfg.propagators.Extract( + savedCtx, + propagation.HeaderCarrier(request.Header), + ) + + // //https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md + // httpconv doesn't exist in semconv v1.21.0 we have to use v1.20.0 for that + // https://github.com/open-telemetry/opentelemetry-go/pull/4362 + opts := []oteltrace.SpanStartOption{ + oteltrace.WithAttributes( + httpconv.ServerRequest(cfg.serviceName, request)...), + oteltrace.WithSpanKind(oteltrace.SpanKindServer), + } + + if path := c.Path(); path != "" { + rAttr := semconv.HTTPRoute(path) + opts = append(opts, oteltrace.WithAttributes(rAttr)) + } + + spanName := c.Path() + if spanName == "" { + spanName = fmt.Sprintf( + "HTTP %s route not found", + request.Method, + ) + } + + ctx, span := tracer.Start(ctx, spanName, opts...) + defer span.End() + + // add the new context into the request, because new ctx contains our created span and we want all inner spans in next middlewares become child span of this span + // pass the span through the request context + c.SetRequest(request.WithContext(ctx)) + + // serve the request to the next middleware + err := next(c) + if err != nil { + // handle echo error in this middleware and raise echo errorhandler func and our custom error handler + // when we call c.Error more than once, `c.Response().Committed` becomes true and response doesn't write to client again in our error handler + // Error will update response status with occurred error object status code + c.Error(err) + } + + status := c.Response().Status + err = utils.HttpTraceStatusFromSpanWithCode(span, err, status) + + return err + } + } +} diff --git a/internal/pkg/http/customecho/middlewares/problem_detail/config.go b/internal/pkg/http/customecho/middlewares/problem_detail/config.go new file mode 100644 index 00000000..f226c1be --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/problem_detail/config.go @@ -0,0 +1,34 @@ +package problemdetail + +import ( + problemDetails "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/problemdetails" + + "github.com/labstack/echo/v4/middleware" +) + +type config struct { + Skipper middleware.Skipper + ProblemParser problemDetails.ErrorParserFunc +} + +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +func WithSkipper(skipper middleware.Skipper) Option { + return optionFunc(func(cfg *config) { + cfg.Skipper = skipper + }) +} + +func WithErrorParser(errorParser problemDetails.ErrorParserFunc) Option { + return optionFunc(func(cfg *config) { + cfg.ProblemParser = errorParser + }) +} diff --git a/internal/pkg/http/customecho/middlewares/problem_detail/problem_detail_middleware.go b/internal/pkg/http/customecho/middlewares/problem_detail/problem_detail_middleware.go new file mode 100644 index 00000000..dff642ae --- /dev/null +++ b/internal/pkg/http/customecho/middlewares/problem_detail/problem_detail_middleware.go @@ -0,0 +1,44 @@ +package problemdetail + +import ( + problemDetails "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/problemdetails" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +func ProblemDetail(opts ...Option) echo.MiddlewareFunc { + cfg := config{} + for _, opt := range opts { + opt.apply(&cfg) + } + + if cfg.Skipper == nil { + cfg.Skipper = middleware.DefaultSkipper + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if cfg.Skipper(c) { + return next(c) + } + + err := next(c) + + prbError := problemDetails.ParseError(err) + + if cfg.ProblemParser != nil { + prbError = cfg.ProblemParser(prbError) + } + + if prbError != nil { + // handle echo error in this middleware and raise echo errorhandler func and our custom error handler + // when we call c.Error more than once, `c.Response().Committed` becomes true and response doesn't write to client again in our error handler + // Error will update response status with occurred error object status code + c.Error(prbError) + } + + return prbError + } + } +} diff --git a/internal/pkg/http/http_errors/custom_errors/api_error.go b/internal/pkg/http/http_errors/custom_errors/api_error.go deleted file mode 100644 index 21232e7b..00000000 --- a/internal/pkg/http/http_errors/custom_errors/api_error.go +++ /dev/null @@ -1,46 +0,0 @@ -package customErrors - -import ( - "emperror.dev/errors" -) - -func NewApiError(message string, code int) error { - ae := &apiError{ - CustomError: NewCustomError(nil, code, message), - } - stackErr := errors.WithStackIf(ae) - - return stackErr -} - -func NewApiErrorWrap(err error, code int, message string) error { - ae := &apiError{ - CustomError: NewCustomError(err, code, message), - } - stackErr := errors.WithStackIf(ae) - - return stackErr -} - -type apiError struct { - CustomError -} - -type ApiError interface { - CustomError - IsApiError() bool -} - -func (a *apiError) IsApiError() bool { - return true -} - -func IsApiError(err error, code int) bool { - var apiError ApiError - // us, ok := grpc_errors.Cause(err).(ApiError) - if errors.As(err, &apiError) { - return apiError.IsApiError() && apiError.Status() == code - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/application_error.go b/internal/pkg/http/http_errors/custom_errors/application_error.go deleted file mode 100644 index 3d083526..00000000 --- a/internal/pkg/http/http_errors/custom_errors/application_error.go +++ /dev/null @@ -1,66 +0,0 @@ -package customErrors - -import ( - "net/http" - - "emperror.dev/errors" -) - -func NewApplicationError(message string) error { - ae := &applicationError{ - CustomError: NewCustomError(nil, http.StatusInternalServerError, message), - } - stackErr := errors.WithStackIf(ae) - - return stackErr -} - -func NewApplicationErrorWithCode(message string, code int) error { - ae := &applicationError{ - CustomError: NewCustomError(nil, code, message), - } - stackErr := errors.WithStackIf(ae) - - return stackErr -} - -func NewApplicationErrorWrap(err error, message string) error { - ae := &applicationError{ - CustomError: NewCustomError(err, http.StatusInternalServerError, message), - } - stackErr := errors.WithStackIf(ae) - - return stackErr -} - -func NewApplicationErrorWrapWithCode(err error, code int, message string) error { - ae := &applicationError{ - CustomError: NewCustomError(err, code, message), - } - stackErr := errors.WithStackIf(ae) - - return stackErr -} - -type applicationError struct { - CustomError -} - -type ApplicationError interface { - CustomError - IsApplicationError() bool -} - -func (a *applicationError) IsApplicationError() bool { - return true -} - -func IsApplicationError(err error, code int) bool { - var applicationError ApplicationError - // us, ok := grpc_errors.Cause(err).(ApplicationError) - if errors.As(err, &applicationError) { - return applicationError.IsApplicationError() && applicationError.Status() == code - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/bad_request_error.go b/internal/pkg/http/http_errors/custom_errors/bad_request_error.go deleted file mode 100644 index 20104d44..00000000 --- a/internal/pkg/http/http_errors/custom_errors/bad_request_error.go +++ /dev/null @@ -1,48 +0,0 @@ -package customErrors - -import ( - "net/http" - - "emperror.dev/errors" -) - -func NewBadRequestError(message string) error { - br := &badRequestError{ - CustomError: NewCustomError(nil, http.StatusBadRequest, message), - } - stackErr := errors.WithStackIf(br) - - return stackErr -} - -func NewBadRequestErrorWrap(err error, message string) error { - br := &badRequestError{ - CustomError: NewCustomError(err, http.StatusBadRequest, message), - } - stackErr := errors.WithStackIf(br) - - return stackErr -} - -type badRequestError struct { - CustomError -} - -type BadRequestError interface { - CustomError - IsBadRequestError() bool -} - -func (b *badRequestError) IsBadRequestError() bool { - return true -} - -func IsBadRequestError(err error) bool { - var badRequestError BadRequestError - // us, ok := grpc_errors.Cause(err).(BadRequestError) - if errors.As(err, &badRequestError) { - return badRequestError.IsBadRequestError() - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/conflict_error.go b/internal/pkg/http/http_errors/custom_errors/conflict_error.go deleted file mode 100644 index a7194368..00000000 --- a/internal/pkg/http/http_errors/custom_errors/conflict_error.go +++ /dev/null @@ -1,48 +0,0 @@ -package customErrors - -import ( - "net/http" - - "emperror.dev/errors" -) - -func NewConflictError(message string) error { - ce := &conflictError{ - CustomError: NewCustomError(nil, http.StatusConflict, message), - } - stackErr := errors.WithStackIf(ce) - - return stackErr -} - -func NewConflictErrorWrap(err error, message string) error { - ce := &conflictError{ - CustomError: NewCustomError(err, http.StatusConflict, message), - } - stackErr := errors.WithStackIf(ce) - - return stackErr -} - -type conflictError struct { - CustomError -} - -type ConflictError interface { - CustomError - IsConflictError() bool -} - -func (c *conflictError) IsConflictError() bool { - return true -} - -func IsConflictError(err error) bool { - var conflictError ConflictError - // us, ok := grpc_errors.Cause(err).(ConflictError) - if errors.As(err, &conflictError) { - return conflictError.IsConflictError() - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/custom_errors_test.go b/internal/pkg/http/http_errors/custom_errors/custom_errors_test.go deleted file mode 100644 index 1298aded..00000000 --- a/internal/pkg/http/http_errors/custom_errors/custom_errors_test.go +++ /dev/null @@ -1,348 +0,0 @@ -package customErrors - -import ( - "fmt" - "testing" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/contracts" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" - - "emperror.dev/errors" - "github.com/stretchr/testify/assert" -) - -func Test_BadRequest_Err(t *testing.T) { - rootErr2 := NewBadRequestErrorWrap( - nil, - fmt.Sprintf("domain_events event already exists in event registry"), - ) - - rootErr := errors.New("handling bad request errorUtils") - badErr := NewBadRequestErrorWrap(rootErr, "this is a bad request errorUtils") - err := errors.WithMessage(badErr, "outer errorUtils wrapper") - - assert.True(t, IsBadRequestError(err)) - assert.True(t, IsCustomError(err)) - assert.True(t, IsCustomError(rootErr2)) - assert.True(t, IsCustomError(rootErr2)) - - var customError CustomError - var customError2 CustomError - errors.As(err, &customError) - errors.As(err, &customError2) - - assert.NotNil(t, customError2) - - assert.Equal(t, 400, customError.Status()) - assert.Equal(t, "this is a bad request errorUtils", customError.Message()) - assert.Equal( - t, - "this is a bad request errorUtils: handling bad request errorUtils", - customError.Error(), - ) - assert.NotNil(t, customError.Unwrap()) - assert.NotNil(t, customError.Cause()) - - var stackErr contracts.StackTracer - if ok := errors.As(err, &stackErr); ok { - // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for - fmt.Println( - errorUtils.ErrorsWithStack(err), - ) // write errorUtils messages with stacktrace - } else { - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) - } -} - -func Test_NotFound_Err(t *testing.T) { - rootErr := errors.New("handling not found errorUtils") - notFoundErr := NewNotFoundErrorWrap(rootErr, "this is a not found errorUtils") - err := errors.WithMessage(notFoundErr, "outer errorUtils wrapper") - - assert.True(t, IsNotFoundError(err)) - assert.True(t, IsCustomError(err)) - - var notFound NotFoundError - errors.As(err, ¬Found) - - assert.Equal(t, 404, notFound.Status()) - assert.Equal(t, "this is a not found errorUtils", notFound.Message()) - assert.Equal( - t, - "this is a not found errorUtils: handling not found errorUtils", - notFound.Error(), - ) - assert.NotNil(t, notFound.Unwrap()) - assert.NotNil(t, notFound.Cause()) - - var stackErr contracts.StackTracer - if ok := errors.As(err, &stackErr); ok { - // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for - fmt.Println( - errorUtils.ErrorsWithStack(err), - ) // write errorUtils messages with stacktrace - } else { - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) - } -} - -func Test_Domain_Err(t *testing.T) { - rootErr2 := NewDomainErrorWrap( - nil, - fmt.Sprintf("domain_events event already exists in event registry"), - ) - - rootErr := errors.New("handling domain_events errorUtils") - domainErr := NewDomainErrorWithCodeWrap(rootErr, 400, "this is a domain_events errorUtils") - err := errors.WithMessage(domainErr, "outer errorUtils wrapper") - - assert.True(t, IsDomainError(err, 400)) - assert.True(t, IsDomainError(rootErr2, 400)) - assert.True(t, IsCustomError(err)) - assert.True(t, IsCustomError(rootErr2)) - - var customError CustomError - errors.As(err, &customError) - - assert.Equal(t, 400, customError.Status()) - assert.Equal(t, "this is a domain_events errorUtils", customError.Message()) - assert.Equal( - t, - "this is a domain_events errorUtils: handling domain_events errorUtils", - customError.Error(), - ) - assert.NotNil(t, customError.Unwrap()) - assert.NotNil(t, customError.Cause()) - - var stackErr contracts.StackTracer - if ok := errors.As(err, &stackErr); ok { - // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for - fmt.Println( - errorUtils.ErrorsWithStack(err), - ) // write errorUtils messages with stacktrace - } else { - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) - } -} - -func Test_Application_Err(t *testing.T) { - rootErr2 := NewApplicationErrorWrap( - nil, - fmt.Sprintf("domain_events event already exists in event registry"), - ) - - rootErr := errors.New("handling application_exceptions errorUtils") - err := NewApplicationErrorWrapWithCode( - rootErr, - 400, - "this is a application_exceptions errorUtils", - ) - err = errors.WithMessage(err, "outer errorUtils wrapper") - - assert.True(t, IsApplicationError(err, 400)) - assert.True(t, IsApplicationError(rootErr2, 500)) - assert.True(t, IsCustomError(err)) - assert.True(t, IsCustomError(rootErr2)) - - var appErr ApplicationError - errors.As(err, &appErr) - - assert.Equal(t, 400, appErr.Status()) - assert.Equal(t, "this is a application_exceptions errorUtils", appErr.Message()) - assert.Equal( - t, - "this is a application_exceptions errorUtils: handling application_exceptions errorUtils", - appErr.Error(), - ) - assert.NotNil(t, appErr.Unwrap()) - assert.NotNil(t, appErr.Cause()) - - var stackErr contracts.StackTracer - if ok := errors.As(err, &stackErr); ok { - // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for - fmt.Println(errorUtils.ErrorsWithStack(err)) - } else { - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) - } -} - -func Test_Internal_Server_Error(t *testing.T) { - rootErr := errors.New("handling internal server errorUtils") - internalServerErr := NewInternalServerErrorWrap(rootErr, "this is a internal server errorUtils") - err := errors.WithMessage(internalServerErr, "this is a internal server errorUtils") - - assert.True(t, IsInternalServerError(err)) - assert.True(t, IsCustomError(err)) - - var internalErr InternalServerError - errors.As(err, &internalErr) - - assert.Equal(t, 500, internalErr.Status()) - assert.Equal(t, "this is a internal server errorUtils", internalErr.Message()) - assert.Equal( - t, - "this is a internal server errorUtils: handling internal server errorUtils", - internalErr.Error(), - ) - assert.NotNil(t, internalErr.Unwrap()) - assert.NotNil(t, internalErr.Cause()) - - var stackErr contracts.StackTracer - if ok := errors.As(err, &stackErr); ok { - // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for - fmt.Println(errorUtils.ErrorsWithStack(err)) - } else { - fmt.Println(errorUtils.ErrorsWithStack(err)) - } -} - -func Test_Marshaling_Error(t *testing.T) { - rootErr2 := NewMarshalingErrorWrap( - nil, - fmt.Sprintf("domain_events event already exists in event registry"), - ) - - rootErr := errors.New("handling marshaling errorUtils") - marshalErr := NewMarshalingErrorWrap(rootErr, "this is a marshaling errorUtils") - err := errors.WithMessage(marshalErr, "this is a marshaling errorUtils") - - assert.True(t, IsInternalServerError(err)) - assert.True(t, IsInternalServerError(rootErr2)) - assert.True(t, IsCustomError(err)) - assert.True(t, IsCustomError(rootErr2)) - - assert.True(t, IsMarshalingError(err)) - assert.True(t, IsMarshalingError(rootErr2)) - - var customErr CustomError - var customErr2 CustomError - - errors.As(err, &customErr) - errors.As(rootErr2, &customErr2) - - assert.NotNil(t, customErr) - assert.NotNil(t, customErr2) - - assert.Equal(t, 500, customErr.Status()) - assert.Equal(t, "this is a marshaling errorUtils", customErr.Message()) - assert.Equal( - t, - "this is a marshaling errorUtils: handling marshaling errorUtils", - customErr.Error(), - ) - assert.NotNil(t, customErr.Unwrap()) - assert.NotNil(t, customErr.Cause()) - - var stackErr contracts.StackTracer - if ok := errors.As(err, &stackErr); ok { - // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for - fmt.Println(errorUtils.ErrorsWithStack(err)) - } else { - fmt.Println(errorUtils.ErrorsWithStack(err)) - } -} - -func Test_Validation_Error(t *testing.T) { - rootErr2 := NewValidationErrorWrap( - nil, - fmt.Sprintf("domain_events event already exists in event registry"), - ) - - rootErr := errors.New("handling validation errorUtils") - validationErr := NewValidationErrorWrap(rootErr, "this is a validation errorUtils") - err := errors.WithMessage(validationErr, "this is a validation errorUtils") - - assert.True(t, IsBadRequestError(err)) - assert.True(t, IsBadRequestError(rootErr2)) - assert.True(t, IsCustomError(err)) - assert.True(t, IsCustomError(rootErr2)) - - assert.True(t, IsValidationError(err)) - assert.True(t, IsValidationError(rootErr2)) - - var customErr CustomError - var customErr2 CustomError - - errors.As(err, &customErr) - errors.As(rootErr2, &customErr2) - - assert.NotNil(t, customErr) - assert.NotNil(t, customErr2) - - assert.Equal(t, 400, customErr.Status()) - assert.Equal(t, "this is a validation errorUtils", customErr.Message()) - assert.Equal( - t, - "this is a validation errorUtils: handling validation errorUtils", - customErr.Error(), - ) - assert.NotNil(t, customErr.Unwrap()) - assert.NotNil(t, customErr.Cause()) - - var stackErr contracts.StackTracer - if ok := errors.As(err, &stackErr); ok { - // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for - fmt.Println(errorUtils.ErrorsWithStack(err)) - } else { - fmt.Println(errorUtils.ErrorsWithStack(err)) - } -} - -func Test_Conflict_Error(t *testing.T) { - rootErr2 := NewConflictErrorWrap( - nil, - fmt.Sprintf("domain_events event already exists in event registry"), - ) - - rootErr := errors.New("handling conflict errorUtils") - conflictErr := NewConflictErrorWrap(rootErr, "this is a conflict errorUtils") - err := errors.WithMessage(conflictErr, "this is a conflict errorUtils") - - assert.True(t, IsCustomError(err)) - assert.True(t, IsConflictError(err)) - assert.True(t, IsCustomError(rootErr2)) - assert.True(t, IsConflictError(rootErr2)) - - var customErr CustomError - var customErr2 CustomError - errors.As(err, &customErr) - errors.As(rootErr2, &customErr2) - - assert.NotNil(t, customErr2) - - assert.Equal(t, 409, customErr.Status()) - assert.Equal(t, "this is a conflict errorUtils", customErr.Message()) - assert.Equal( - t, - "this is a conflict errorUtils: handling conflict errorUtils", - customErr.Error(), - ) - assert.NotNil(t, customErr.Unwrap()) - assert.NotNil(t, customErr.Cause()) - - var stackErr contracts.StackTracer - if ok := errors.As(err, &stackErr); ok { - // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package - fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for - fmt.Println(errorUtils.ErrorsWithStack(err)) - } else { - fmt.Println(errorUtils.ErrorsWithStack(err)) - } -} - -func myfoo(e error) error { - // https://itnext.io/golang-error-handling-best-practice-a36f47b0b94c - // Note: Do not repeat Wrap, it will record redundancy call stacks, we usually care about root stack trace - return errors.WithMessage(e, "foo failed") // or grpc_errors.WrapIf() -} - -func mybar(e error) error { - return errors.WithMessage(myfoo(e), "bar failed") // or grpc_errors.WrapIf() -} diff --git a/internal/pkg/http/http_errors/custom_errors/domain_error.go b/internal/pkg/http/http_errors/custom_errors/domain_error.go deleted file mode 100644 index 38720d6b..00000000 --- a/internal/pkg/http/http_errors/custom_errors/domain_error.go +++ /dev/null @@ -1,66 +0,0 @@ -package customErrors - -import ( - "net/http" - - "emperror.dev/errors" -) - -func NewDomainError(message string) error { - de := &domainError{ - CustomError: NewCustomError(nil, http.StatusBadRequest, message), - } - stackErr := errors.WithStackIf(de) - - return stackErr -} - -func NewDomainErrorWithCode(message string, code int) error { - de := &domainError{ - CustomError: NewCustomError(nil, code, message), - } - stackErr := errors.WithStackIf(de) - - return stackErr -} - -func NewDomainErrorWrap(err error, message string) error { - de := &domainError{ - CustomError: NewCustomError(err, http.StatusBadRequest, message), - } - stackErr := errors.WithStackIf(de) - - return stackErr -} - -func NewDomainErrorWithCodeWrap(err error, code int, message string) error { - de := &domainError{ - CustomError: NewCustomError(err, code, message), - } - stackErr := errors.WithStackIf(de) - - return stackErr -} - -type domainError struct { - CustomError -} - -type DomainError interface { - CustomError - IsDomainError() bool -} - -func (d *domainError) IsDomainError() bool { - return true -} - -func IsDomainError(err error, code int) bool { - var domainErr DomainError - // us, ok := grpc_errors.Cause(err).(DomainError) - if errors.As(err, &domainErr) { - return domainErr.IsDomainError() && domainErr.Status() == code - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/forbiden_error.go b/internal/pkg/http/http_errors/custom_errors/forbiden_error.go deleted file mode 100644 index 0a35add3..00000000 --- a/internal/pkg/http/http_errors/custom_errors/forbiden_error.go +++ /dev/null @@ -1,48 +0,0 @@ -package customErrors - -import ( - "net/http" - - "emperror.dev/errors" -) - -func NewForbiddenError(message string) error { - ne := &forbiddenError{ - CustomError: NewCustomError(nil, http.StatusForbidden, message), - } - stackErr := errors.WithStackIf(ne) - - return stackErr -} - -func NewForbiddenErrorWrap(err error, message string) error { - ne := &forbiddenError{ - CustomError: NewCustomError(err, http.StatusForbidden, message), - } - stackErr := errors.WithStackIf(ne) - - return stackErr -} - -type forbiddenError struct { - CustomError -} - -type ForbiddenError interface { - CustomError - IsForbiddenError() bool -} - -func (f *forbiddenError) IsForbiddenError() bool { - return true -} - -func IsForbiddenError(err error) bool { - var forbiddenError ForbiddenError - // us, ok := grpc_errors.Cause(err).(ForbiddenError) - if errors.As(err, &forbiddenError) { - return forbiddenError.IsForbiddenError() - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/internal_server_error.go b/internal/pkg/http/http_errors/custom_errors/internal_server_error.go deleted file mode 100644 index f6db30a5..00000000 --- a/internal/pkg/http/http_errors/custom_errors/internal_server_error.go +++ /dev/null @@ -1,48 +0,0 @@ -package customErrors - -import ( - "net/http" - - "emperror.dev/errors" -) - -func NewInternalServerError(message string) error { - br := &internalServerError{ - CustomError: NewCustomError(nil, http.StatusInternalServerError, message), - } - stackErr := errors.WithStackIf(br) - - return stackErr -} - -func NewInternalServerErrorWrap(err error, message string) error { - br := &internalServerError{ - CustomError: NewCustomError(err, http.StatusInternalServerError, message), - } - stackErr := errors.WithStackIf(br) - - return stackErr -} - -type internalServerError struct { - CustomError -} - -type InternalServerError interface { - CustomError - IsInternalServerError() bool -} - -func (i *internalServerError) IsInternalServerError() bool { - return true -} - -func IsInternalServerError(err error) bool { - var internalErr InternalServerError - // us, ok := grpc_errors.Cause(err).(InternalServerError) - if errors.As(err, &internalErr) { - return internalErr.IsInternalServerError() - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/marshaling_error.go b/internal/pkg/http/http_errors/custom_errors/marshaling_error.go deleted file mode 100644 index 84ce3853..00000000 --- a/internal/pkg/http/http_errors/custom_errors/marshaling_error.go +++ /dev/null @@ -1,51 +0,0 @@ -package customErrors - -import ( - "emperror.dev/errors" -) - -func NewMarshalingError(message string) error { - internal := NewInternalServerError(message) - customErr := GetCustomError(internal) - ue := &marshalingError{ - InternalServerError: customErr.(InternalServerError), - } - stackErr := errors.WithStackIf(ue) - - return stackErr -} - -func NewMarshalingErrorWrap(err error, message string) error { - internal := NewInternalServerErrorWrap(err, message) - customErr := GetCustomError(internal) - ue := &marshalingError{ - InternalServerError: customErr.(InternalServerError), - } - stackErr := errors.WithStackIf(ue) - - return stackErr -} - -type marshalingError struct { - InternalServerError -} - -type MarshalingError interface { - InternalServerError - IsMarshalingError() bool -} - -func (m *marshalingError) IsMarshalingError() bool { - return true -} - -func IsMarshalingError(err error) bool { - var me MarshalingError - - // us, ok := grpc_errors.Cause(err).(MarshalingError) - if errors.As(err, &me) { - return me.IsMarshalingError() - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/not_found_error.go b/internal/pkg/http/http_errors/custom_errors/not_found_error.go deleted file mode 100644 index a334c7c7..00000000 --- a/internal/pkg/http/http_errors/custom_errors/not_found_error.go +++ /dev/null @@ -1,48 +0,0 @@ -package customErrors - -import ( - "net/http" - - "emperror.dev/errors" -) - -func NewNotFoundError(message string) error { - ne := ¬FoundError{ - CustomError: NewCustomError(nil, http.StatusNotFound, message), - } - stackErr := errors.WithStackIf(ne) - - return stackErr -} - -func NewNotFoundErrorWrap(err error, message string) error { - ne := ¬FoundError{ - CustomError: NewCustomError(err, http.StatusNotFound, message), - } - stackErr := errors.WithStackIf(ne) - - return stackErr -} - -type notFoundError struct { - CustomError -} - -type NotFoundError interface { - CustomError - IsNotFoundError() bool -} - -func (n *notFoundError) IsNotFoundError() bool { - return true -} - -func IsNotFoundError(err error) bool { - var notFoundError NotFoundError - // us, ok := grpc_errors.Cause(err).(NotFoundError) - if errors.As(err, ¬FoundError) { - return notFoundError.IsNotFoundError() - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/unauthorized_error.go b/internal/pkg/http/http_errors/custom_errors/unauthorized_error.go deleted file mode 100644 index 51638b5a..00000000 --- a/internal/pkg/http/http_errors/custom_errors/unauthorized_error.go +++ /dev/null @@ -1,48 +0,0 @@ -package customErrors - -import ( - "net/http" - - "emperror.dev/errors" -) - -func NewUnAuthorizedError(message string) error { - ue := &unauthorizedError{ - CustomError: NewCustomError(nil, http.StatusUnauthorized, message), - } - stackErr := errors.WithStackIf(ue) - - return stackErr -} - -func NewUnAuthorizedErrorWrap(err error, message string) error { - ue := &unauthorizedError{ - CustomError: NewCustomError(err, http.StatusUnauthorized, message), - } - stackErr := errors.WithStackIf(ue) - - return stackErr -} - -type unauthorizedError struct { - CustomError -} - -type UnauthorizedError interface { - CustomError - IsUnAuthorizedError() bool -} - -func (u *unauthorizedError) IsUnAuthorizedError() bool { - return true -} - -func IsUnAuthorizedError(err error) bool { - var unauthorizedError UnauthorizedError - // us, ok := grpc_errors.Cause(err).(UnauthorizedError) - if errors.As(err, &unauthorizedError) { - return unauthorizedError.IsUnAuthorizedError() - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/unmarshaling_error.go b/internal/pkg/http/http_errors/custom_errors/unmarshaling_error.go deleted file mode 100644 index b65dec8d..00000000 --- a/internal/pkg/http/http_errors/custom_errors/unmarshaling_error.go +++ /dev/null @@ -1,50 +0,0 @@ -package customErrors - -import ( - "emperror.dev/errors" -) - -func NewUnMarshalingError(message string) error { - internal := NewInternalServerError(message) - customErr := GetCustomError(internal) - ue := &unMarshalingError{ - InternalServerError: customErr.(InternalServerError), - } - stackErr := errors.WithStackIf(ue) - - return stackErr -} - -func NewUnMarshalingErrorWrap(err error, message string) error { - internal := NewInternalServerErrorWrap(err, message) - customErr := GetCustomError(internal) - ue := &unMarshalingError{ - InternalServerError: customErr.(InternalServerError), - } - stackErr := errors.WithStackIf(ue) - - return stackErr -} - -type unMarshalingError struct { - InternalServerError -} - -type UnMarshalingError interface { - InternalServerError - IsUnMarshalingError() bool -} - -func (u *unMarshalingError) IsUnMarshalingError() bool { - return true -} - -func IsUnMarshalingError(err error) bool { - var unMarshalingError UnMarshalingError - // us, ok := grpc_errors.Cause(err).(UnMarshalingError) - if errors.As(err, &unMarshalingError) { - return unMarshalingError.IsUnMarshalingError() - } - - return false -} diff --git a/internal/pkg/http/http_errors/custom_errors/validation_error.go b/internal/pkg/http/http_errors/custom_errors/validation_error.go deleted file mode 100644 index b2d2dda8..00000000 --- a/internal/pkg/http/http_errors/custom_errors/validation_error.go +++ /dev/null @@ -1,50 +0,0 @@ -package customErrors - -import ( - "emperror.dev/errors" -) - -func NewValidationError(message string) error { - bad := NewBadRequestError(message) - customErr := GetCustomError(bad) - ue := &validationError{ - BadRequestError: customErr.(BadRequestError), - } - stackErr := errors.WithStackIf(ue) - - return stackErr -} - -func NewValidationErrorWrap(err error, message string) error { - bad := NewBadRequestErrorWrap(err, message) - customErr := GetCustomError(bad) - ue := &validationError{ - BadRequestError: customErr.(BadRequestError), - } - stackErr := errors.WithStackIf(ue) - - return stackErr -} - -type validationError struct { - BadRequestError -} - -type ValidationError interface { - BadRequestError - IsValidationError() bool -} - -func (v *validationError) IsValidationError() bool { - return true -} - -func IsValidationError(err error) bool { - var validationError ValidationError - // us, ok := grpc_errors.Cause(err).(ValidationError) - if errors.As(err, &validationError) { - return validationError.IsValidationError() - } - - return false -} diff --git a/internal/pkg/http/http_fx.go b/internal/pkg/http/http_fx.go index c2f9334a..7a6419d5 100644 --- a/internal/pkg/http/http_fx.go +++ b/internal/pkg/http/http_fx.go @@ -1,8 +1,8 @@ package http import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/client" - customEcho "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/custom_echo" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/client" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" "go.uber.org/fx" ) diff --git a/internal/pkg/http/http_errors/contracts/contracts.go b/internal/pkg/http/httperrors/contracts/contracts.go similarity index 100% rename from internal/pkg/http/http_errors/contracts/contracts.go rename to internal/pkg/http/httperrors/contracts/contracts.go diff --git a/internal/pkg/http/httperrors/customerrors/api_error.go b/internal/pkg/http/httperrors/customerrors/api_error.go new file mode 100644 index 00000000..6c6e72dd --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/api_error.go @@ -0,0 +1,64 @@ +package customErrors + +import ( + "emperror.dev/errors" +) + +func NewApiError(message string, code int) ApiError { + // `NewPlain` doesn't add stack-trace at all + apiErrMessage := errors.NewPlain("api error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(apiErrMessage, message) + + apiError := &apiError{ + CustomError: NewCustomError(stackErr, code, message), + } + + return apiError +} + +func NewApiErrorWrap(err error, code int, message string) ApiError { + if err == nil { + return NewApiError(message, code) + } + + // `WithMessage` doesn't add stack-trace at all + apiErrMessage := errors.WithMessage(err, "api error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(apiErrMessage, message) + + apiError := &apiError{ + CustomError: NewCustomError(stackErr, code, message), + } + + return apiError +} + +type apiError struct { + CustomError +} + +type ApiError interface { + CustomError + isAPIError() +} + +func (a *apiError) isAPIError() { +} + +func IsApiError(err error, code int) bool { + var apiError ApiError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested api error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(ApiError); ok { + return true + } + + // us, ok := errors.Cause(err).(ApiError) + if errors.As(err, &apiError) { + return apiError.Status() == code + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/application_error.go b/internal/pkg/http/httperrors/customerrors/application_error.go new file mode 100644 index 00000000..dd716506 --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/application_error.go @@ -0,0 +1,78 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewApplicationError(message string) ApplicationError { + return NewApplicationErrorWithCode(message, http.StatusInternalServerError) +} + +func NewApplicationErrorWithCode(message string, code int) ApplicationError { + // `NewPlain` doesn't add stack-trace at all + applicationErrMessage := errors.NewPlain("application error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(applicationErrMessage, message) + + applicationError := &applicationError{ + CustomError: NewCustomError(stackErr, code, message), + } + + return applicationError +} + +func NewApplicationErrorWrap(err error, message string) ApplicationError { + return NewApplicationErrorWrapWithCode(err, http.StatusInternalServerError, message) +} + +func NewApplicationErrorWrapWithCode( + err error, + code int, + message string, +) ApplicationError { + if err == nil { + return NewApplicationErrorWithCode(message, code) + } + + // `WithMessage` doesn't add stack-trace at all + applicationErrMessage := errors.WithMessage(err, "application error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(applicationErrMessage, message) + + applicationError := &applicationError{ + CustomError: NewCustomError(stackErr, code, message), + } + + return applicationError +} + +type applicationError struct { + CustomError +} + +type ApplicationError interface { + CustomError + isApplicationError() +} + +func (a *applicationError) isApplicationError() { +} + +func IsApplicationError(err error, code int) bool { + var applicationError ApplicationError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested application error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(ApplicationError); ok { + return true + } + + // us, ok := errors.Cause(err).(ApplicationError) + if errors.As(err, &applicationError) { + return applicationError.Status() == code + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/bad_request_error.go b/internal/pkg/http/httperrors/customerrors/bad_request_error.go new file mode 100644 index 00000000..84cf6662 --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/bad_request_error.go @@ -0,0 +1,66 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewBadRequestError(message string) BadRequestError { + // `NewPlain` doesn't add stack-trace at all + badRequestErrMessage := errors.NewPlain("bad request error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(badRequestErrMessage, message) + + badRequestError := &badRequestError{ + CustomError: NewCustomError(stackErr, http.StatusBadRequest, message), + } + + return badRequestError +} + +func NewBadRequestErrorWrap(err error, message string) BadRequestError { + if err == nil { + return NewBadRequestError(message) + } + + // `WithMessage` doesn't add stack-trace at all + badRequestErrMessage := errors.WithMessage(err, "bad request error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(badRequestErrMessage, message) + + badRequestError := &badRequestError{ + CustomError: NewCustomError(stackErr, http.StatusBadRequest, message), + } + + return badRequestError +} + +type badRequestError struct { + CustomError +} + +type BadRequestError interface { + CustomError + isBadRequestError() +} + +func (b *badRequestError) isBadRequestError() { +} + +func IsBadRequestError(err error) bool { + var badRequestError BadRequestError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested bad-request error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(BadRequestError); ok { + return true + } + + // us, ok := errors.Cause(err).(BadRequestError) + if errors.As(err, &badRequestError) { + return true + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/conflict_error.go b/internal/pkg/http/httperrors/customerrors/conflict_error.go new file mode 100644 index 00000000..887236d0 --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/conflict_error.go @@ -0,0 +1,66 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewConflictError(message string) ConflictError { + // `NewPlain` doesn't add stack-trace at all + conflictErrMessage := errors.NewPlain("conflict error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(conflictErrMessage, message) + + conflictError := &conflictError{ + CustomError: NewCustomError(stackErr, http.StatusConflict, message), + } + + return conflictError +} + +func NewConflictErrorWrap(err error, message string) ConflictError { + if err == nil { + return NewConflictError(message) + } + + // `WithMessage` doesn't add stack-trace at all + conflictErrMessage := errors.WithMessage(err, "conflict error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(conflictErrMessage, message) + + conflictError := &conflictError{ + CustomError: NewCustomError(stackErr, http.StatusConflict, message), + } + + return conflictError +} + +type conflictError struct { + CustomError +} + +type ConflictError interface { + CustomError + isConflictError() +} + +func (c *conflictError) isConflictError() { +} + +func IsConflictError(err error) bool { + var conflictError ConflictError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested notfound error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(ConflictError); ok { + return true + } + + // us, ok := errors.Cause(err).(ConflictError) + if errors.As(err, &conflictError) { + return true + } + + return false +} diff --git a/internal/pkg/http/http_errors/custom_errors/custom_errors.go b/internal/pkg/http/httperrors/customerrors/custom_errors.go similarity index 69% rename from internal/pkg/http/http_errors/custom_errors/custom_errors.go rename to internal/pkg/http/httperrors/customerrors/custom_errors.go index a7a0d6c7..5b205d7a 100644 --- a/internal/pkg/http/http_errors/custom_errors/custom_errors.go +++ b/internal/pkg/http/httperrors/customerrors/custom_errors.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/contracts" "emperror.dev/errors" ) @@ -20,7 +20,7 @@ import ( type customError struct { statusCode int message string - err error + error } type CustomError interface { @@ -28,7 +28,7 @@ type CustomError interface { contracts.Wrapper contracts.Causer contracts.Formatter - IsCustomError() bool + isCustomError() Status() int Message() string } @@ -36,20 +36,19 @@ type CustomError interface { func NewCustomError(err error, code int, message string) CustomError { m := &customError{ statusCode: code, - err: err, + error: err, message: message, } return m } -func (e *customError) IsCustomError() bool { - return true +func (e *customError) isCustomError() { } func (e *customError) Error() string { - if e.err != nil { - return e.message + ": " + e.err.Error() + if e.error != nil { + return e.error.Error() } return e.message @@ -64,19 +63,25 @@ func (e *customError) Status() int { } func (e *customError) Cause() error { - return e.err + return e.error } func (e *customError) Unwrap() error { - return e.err + return e.error } func (e *customError) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { - fmt.Fprintf(s, "%+v\n", e.Cause()) - io.WriteString(s, e.message) + //%s error messages separated by a colon and a space (": ") + //%q double-quoted error messages separated by a colon and a space (": ") + //%v one error message per line + //%+v one error message per line and stack trace (if any) + + // if we have a call-stacked error, +v shows callstack for this error + fmt.Fprintf(s, "%+v", e.Cause()) + // io.WriteString(s, e.message) return } fallthrough @@ -104,8 +109,9 @@ func IsCustomError(err error) bool { return true } + // us, ok := errors.Cause(err).(ConflictError) if errors.As(err, &customErr) { - return customErr.IsCustomError() + return true } return false diff --git a/internal/pkg/http/httperrors/customerrors/custom_errors_test.go b/internal/pkg/http/httperrors/customerrors/custom_errors_test.go new file mode 100644 index 00000000..f8f97994 --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/custom_errors_test.go @@ -0,0 +1,503 @@ +package customErrors + +import ( + "fmt" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/contracts" + errorUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils/errorutils" + + "emperror.dev/errors" + "github.com/stretchr/testify/assert" +) + +func Test_Domain_Err(t *testing.T) { + rootErr2 := NewDomainErrorWrap( + nil, + fmt.Sprintf("domain_events event already exists in event registry"), + ) + + // `NewPlain` doesn't add stack-trace but `New` will add stack-trace + rootErr := errors.NewPlain("handling domain_events errorUtils") + domainErr := NewDomainErrorWithCodeWrap(rootErr, 400, "this is a domain_events errorUtils") + err := errors.WithMessage(domainErr, "outer errorUtils wrapper") + + assert.True(t, IsDomainError(err, 400)) + assert.True(t, IsDomainError(rootErr2, 400)) + assert.True(t, IsCustomError(err)) + assert.True(t, IsCustomError(rootErr2)) + + var domainError DomainError + errors.As(err, &domainError) + + _, isConflict := domainErr.(ConflictError) + _, isConflict2 := domainError.(ConflictError) + assert.False(t, isConflict) + assert.False(t, isConflict2) + + _, isDomainError := domainErr.(DomainError) + _, isDomainError2 := domainError.(DomainError) + assert.True(t, isDomainError) + assert.True(t, isDomainError2) + + assert.True(t, IsDomainError(domainErr, 400)) + assert.True(t, IsDomainError(domainError, 400)) + assert.False(t, IsDomainError(NewConflictError("conflict error"), 400)) + + assert.Equal(t, 400, domainError.Status()) + assert.Equal(t, "this is a domain_events errorUtils", domainError.Message()) + assert.Equal( + t, + "this is a domain_events errorUtils: domain error: handling domain_events errorUtils", + domainError.Error(), + ) + assert.NotNil(t, domainError.Unwrap()) + assert.NotNil(t, domainError.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println( + errorUtils.ErrorsWithStack(err), + ) // write errorUtils messages with stacktrace + } else { + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) + } +} + +func Test_Application_Err(t *testing.T) { + rootErr2 := NewApplicationErrorWrap( + nil, + fmt.Sprintf("domain_events event already exists in event registry"), + ) + + // `NewPlain` doesn't add stack-trace but `New` will add stack-trace + rootErr := errors.NewPlain("handling application_exceptions errorUtils") + appErr := NewApplicationErrorWrapWithCode( + rootErr, + 400, + "this is a application_exceptions errorUtils", + ) + err := errors.WithMessage(appErr, "outer errorUtils wrapper") + + assert.True(t, IsApplicationError(err, 400)) + assert.True(t, IsApplicationError(rootErr2, 500)) + assert.True(t, IsCustomError(err)) + assert.True(t, IsCustomError(rootErr2)) + + var applicationError ApplicationError + errors.As(err, &applicationError) + + _, isConflict := appErr.(ConflictError) + _, isConflict2 := applicationError.(ConflictError) + assert.False(t, isConflict) + assert.False(t, isConflict2) + + _, isApplicationError := appErr.(ApplicationError) + _, isApplicationError2 := applicationError.(ApplicationError) + assert.True(t, isApplicationError) + assert.True(t, isApplicationError2) + + assert.True(t, IsApplicationError(appErr, 400)) + assert.True(t, IsApplicationError(applicationError, 400)) + assert.False(t, IsApplicationError(NewConflictError("conflict error"), 400)) + + assert.Equal(t, 400, applicationError.Status()) + assert.Equal(t, "this is a application_exceptions errorUtils", applicationError.Message()) + assert.Equal( + t, + "this is a application_exceptions errorUtils: application error: handling application_exceptions errorUtils", + applicationError.Error(), + ) + assert.NotNil(t, applicationError.Unwrap()) + assert.NotNil(t, applicationError.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println(errorUtils.ErrorsWithStack(err)) + } else { + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) + } +} + +func Test_Api_Err(t *testing.T) { + rootErr2 := NewApiErrorWrap( + nil, + http.StatusBadRequest, + fmt.Sprintf("domain_events event already exists in event registry"), + ) + + // `NewPlain` doesn't add stack-trace but `New` will add stack-trace + rootErr := errors.NewPlain("handling api_exceptions errorUtils") + appErr := NewApiErrorWrap( + rootErr, + 400, + "this is a api_exceptions errorUtils", + ) + err := errors.WithMessage(appErr, "outer errorUtils wrapper") + + assert.True(t, IsApiError(err, 400)) + assert.True(t, IsApiError(rootErr2, 500)) + assert.True(t, IsCustomError(err)) + assert.True(t, IsCustomError(rootErr2)) + + var apiError ApiError + errors.As(err, &apiError) + + _, isConflict := appErr.(ConflictError) + _, isConflict2 := apiError.(ConflictError) + assert.False(t, isConflict) + assert.False(t, isConflict2) + + _, isApiError := appErr.(ApiError) + _, isApiError2 := apiError.(ApiError) + assert.True(t, isApiError) + assert.True(t, isApiError2) + + assert.True(t, IsApiError(appErr, 400)) + assert.True(t, IsApiError(apiError, 400)) + assert.False(t, IsApiError(NewConflictError("conflict error"), 400)) + + assert.Equal(t, 400, apiError.Status()) + assert.Equal(t, "this is a api_exceptions errorUtils", apiError.Message()) + assert.Equal( + t, + "this is a api_exceptions errorUtils: api error: handling api_exceptions errorUtils", + apiError.Error(), + ) + assert.NotNil(t, apiError.Unwrap()) + assert.NotNil(t, apiError.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println(errorUtils.ErrorsWithStack(err)) + } else { + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) + } +} + +func Test_BadRequest_Err(t *testing.T) { + rootErr2 := NewBadRequestErrorWrap( + nil, + fmt.Sprintf("domain_events event already exists in event registry"), + ) + + // `NewPlain` doesn't add stack-trace but `New` will add stack-trace + rootErr := errors.NewPlain("handling bad request errorUtils") + badErr := NewBadRequestErrorWrap(rootErr, "this is a bad request errorUtils") + err := errors.WithMessage(badErr, "outer errorUtils wrapper") + + assert.True(t, IsBadRequestError(err)) + assert.True(t, IsCustomError(err)) + assert.True(t, IsCustomError(rootErr2)) + assert.True(t, IsCustomError(rootErr2)) + + var customError CustomError + var customError2 CustomError + errors.As(err, &customError) + errors.As(err, &customError2) + + assert.NotNil(t, customError2) + + var badRequestError BadRequestError + errors.As(err, &badRequestError) + + _, isConflict := badErr.(ConflictError) + _, isConflict2 := badRequestError.(ConflictError) + assert.False(t, isConflict) + assert.False(t, isConflict2) + + _, isBadRequest := badErr.(BadRequestError) + _, isBadRequest2 := badRequestError.(BadRequestError) + assert.True(t, isBadRequest) + assert.True(t, isBadRequest2) + + assert.True(t, IsBadRequestError(badErr)) + assert.True(t, IsBadRequestError(badRequestError)) + assert.False(t, IsBadRequestError(NewConflictError("conflict error"))) + + assert.Equal(t, 400, badRequestError.Status()) + assert.Equal(t, "this is a bad request errorUtils", badRequestError.Message()) + assert.Equal( + t, + "this is a bad request errorUtils: bad request error: handling bad request errorUtils", + badRequestError.Error(), + ) + assert.NotNil(t, badRequestError.Unwrap()) + assert.NotNil(t, badRequestError.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println( + errorUtils.ErrorsWithStack(err), + ) // write errorUtils messages with stacktrace + } else { + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) + } +} + +func Test_NotFound_Err(t *testing.T) { + // `NewPlain` doesn't add stack-trace but `New` will add stack-trace + rootErr := errors.NewPlain("handling not found errorUtils") + notFoundErr := NewNotFoundErrorWrap(rootErr, "this is a not found errorUtils") + err := errors.WithMessage(notFoundErr, "outer errorUtils wrapper") + + assert.True(t, IsNotFoundError(err)) + assert.True(t, IsCustomError(err)) + + var notFound NotFoundError + errors.As(err, ¬Found) + + _, isConflict := notFoundErr.(ConflictError) + _, isConflict2 := notFound.(ConflictError) + assert.False(t, isConflict) + assert.False(t, isConflict2) + + _, isNotFound := notFoundErr.(NotFoundError) + _, isNotFound2 := notFound.(NotFoundError) + assert.True(t, isNotFound) + assert.True(t, isNotFound2) + + assert.True(t, IsNotFoundError(notFoundErr)) + assert.True(t, IsNotFoundError(notFound)) + assert.False(t, IsNotFoundError(NewConflictError("conflict error"))) + + assert.Equal(t, http.StatusNotFound, notFound.Status()) + assert.Equal(t, "this is a not found errorUtils", notFound.Message()) + assert.Equal( + t, + "this is a not found errorUtils: not found error: handling not found errorUtils", + notFound.Error(), + ) + assert.NotNil(t, notFound.Unwrap()) + assert.NotNil(t, notFound.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println( + errorUtils.ErrorsWithStack(err), + ) // write errorUtils messages with stacktrace + } else { + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) + } +} + +func Test_Internal_Server_Error(t *testing.T) { + // `NewPlain` doesn't add stack-trace but `New` will add stack-trace + rootErr := errors.NewPlain("handling internal server errorUtils") + internalServerErr := NewInternalServerErrorWrap(rootErr, "this is a internal server errorUtils") + err := errors.WithMessage(internalServerErr, "outer errorUtils wrapper") + + assert.True(t, IsInternalServerError(err)) + assert.True(t, IsCustomError(err)) + + var internalErr InternalServerError + errors.As(err, &internalErr) + + assert.True(t, IsInternalServerError(internalErr)) + assert.False(t, IsInternalServerError(NewConflictError("conflict error"))) + + assert.Equal(t, http.StatusInternalServerError, internalErr.Status()) + assert.Equal(t, "this is a internal server errorUtils", internalErr.Message()) + assert.Equal( + t, + "this is a internal server errorUtils: internal server error: handling internal server errorUtils", + internalErr.Error(), + ) + assert.NotNil(t, internalErr.Unwrap()) + assert.NotNil(t, internalErr.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println(errorUtils.ErrorsWithStack(err)) + } else { + fmt.Println(errorUtils.ErrorsWithStack(err)) + } +} + +func Test_Forbidden_Error(t *testing.T) { + // `NewPlain` doesn't add stack-trace but `New` will add stack-trace + rootErr := errors.NewPlain("handling forbidden errorUtils") + forbiddenError := NewForbiddenErrorWrap(rootErr, "this is a forbidden errorUtils") + err := errors.WithMessage(forbiddenError, "outer errorUtils wrapper") + + assert.True(t, IsForbiddenError(err)) + assert.True(t, IsCustomError(err)) + + var forbiddenErr ForbiddenError + errors.As(err, &forbiddenErr) + + assert.True(t, IsForbiddenError(forbiddenErr)) + assert.False(t, IsForbiddenError(NewConflictError("conflict error"))) + + assert.Equal(t, http.StatusForbidden, forbiddenErr.Status()) + assert.Equal(t, "this is a forbidden errorUtils", forbiddenErr.Message()) + assert.Equal( + t, + "this is a forbidden errorUtils: forbidden error: handling forbidden errorUtils", + forbiddenErr.Error(), + ) + assert.NotNil(t, forbiddenErr.Unwrap()) + assert.NotNil(t, forbiddenErr.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println(errorUtils.ErrorsWithStack(err)) + } else { + fmt.Println(errorUtils.ErrorsWithStack(err)) + } +} + +func Test_Marshaling_Error(t *testing.T) { + rootErr2 := NewMarshalingErrorWrap( + nil, + fmt.Sprintf("domain_events event already exists in event registry"), + ) + + rootErr := errors.NewPlain("handling marshaling errorUtils") + marshalErr := NewMarshalingErrorWrap(rootErr, "this is a marshaling errorUtils") + err := errors.WithMessage(marshalErr, "outer errorUtils wrapper") + + assert.True(t, IsInternalServerError(err)) + assert.True(t, IsInternalServerError(rootErr2)) + assert.True(t, IsCustomError(err)) + assert.True(t, IsCustomError(rootErr2)) + + assert.True(t, IsMarshalingError(err)) + assert.True(t, IsMarshalingError(rootErr2)) + + var marshallingErr MarshalingError + errors.As(err, &marshallingErr) + + assert.True(t, IsMarshalingError(marshallingErr)) + assert.False(t, IsMarshalingError(NewConflictError("conflict error"))) + + assert.Equal(t, 500, marshallingErr.Status()) + assert.Equal(t, "this is a marshaling errorUtils", marshallingErr.Message()) + assert.Equal( + t, + "this is a marshaling errorUtils: marshaling error: handling marshaling errorUtils", + marshallingErr.Error(), + ) + assert.NotNil(t, marshallingErr.Unwrap()) + assert.NotNil(t, marshallingErr.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println(errorUtils.ErrorsWithStack(err)) + } else { + fmt.Println(errorUtils.ErrorsWithStack(err)) + } +} + +func Test_Validation_Error(t *testing.T) { + rootErr2 := NewValidationErrorWrap( + nil, + fmt.Sprintf("domain_events event already exists in event registry"), + ) + + rootErr := errors.New("handling validation errorUtils") + validationErr := NewValidationErrorWrap(rootErr, "this is a validation errorUtils") + err := errors.WithMessage(validationErr, "this is a top error message") + + assert.True(t, IsBadRequestError(err)) + assert.True(t, IsBadRequestError(rootErr2)) + assert.True(t, IsCustomError(err)) + assert.True(t, IsCustomError(rootErr2)) + + assert.True(t, IsValidationError(err)) + assert.True(t, IsValidationError(rootErr2)) + + var customErr CustomError + var customErr2 CustomError + + errors.As(err, &customErr) + errors.As(rootErr2, &customErr2) + + assert.NotNil(t, customErr) + assert.NotNil(t, customErr2) + + assert.Equal(t, http.StatusBadRequest, customErr.Status()) + assert.Equal(t, "this is a validation errorUtils", customErr.Message()) + assert.Equal( + t, + "this is a validation errorUtils: validation error: handling validation errorUtils", + customErr.Error(), + ) + assert.NotNil(t, customErr.Unwrap()) + assert.NotNil(t, customErr.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println(errorUtils.ErrorsWithStack(err)) + } else { + fmt.Println(errorUtils.ErrorsWithStack(err)) + } +} + +func Test_Conflict_Error(t *testing.T) { + rootErr2 := NewConflictErrorWrap( + nil, + fmt.Sprintf("domain_events event already exists in event registry"), + ) + + // `NewPlain` doesn't add stack-trace but `New` will add stack-trace + rootErr := errors.NewPlain("handling conflict errorUtils") + conflictErr := NewConflictErrorWrap(rootErr, "this is a conflict errorUtils") + err := errors.WithMessage(conflictErr, "this is a top error message") + + assert.True(t, IsCustomError(err)) + assert.True(t, IsConflictError(err)) + assert.True(t, IsCustomError(rootErr2)) + assert.True(t, IsConflictError(rootErr2)) + + var conflictError ConflictError + errors.As(err, &conflictError) + + assert.Equal(t, 409, conflictError.Status()) + assert.Equal(t, "this is a conflict errorUtils", conflictError.Message()) + assert.Equal( + t, + "this is a conflict errorUtils: conflict error: handling conflict errorUtils", + conflictError.Error(), + ) + assert.NotNil(t, conflictError.Unwrap()) + assert.NotNil(t, conflictError.Cause()) + + var stackErr contracts.StackTracer + if ok := errors.As(err, &stackErr); ok { + // https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package + fmt.Println(errorUtils.ErrorsWithoutStack(err, false)) // Just write errorUtils messages for + fmt.Println(errorUtils.ErrorsWithStack(err)) + } else { + fmt.Println(errorUtils.ErrorsWithStack(err)) + } +} + +func myfoo(e error) error { + // https://itnext.io/golang-error-handling-best-practice-a36f47b0b94c + // Note: Do not repeat Wrap, it will record redundancy call stacks, we usually care about root stack trace + return errors.WithMessage(e, "foo failed") // or grpc_errors.WrapIf() +} + +func mybar(e error) error { + return errors.WithMessage(myfoo(e), "bar failed") // or grpc_errors.WrapIf() +} diff --git a/internal/pkg/http/httperrors/customerrors/domain_error.go b/internal/pkg/http/httperrors/customerrors/domain_error.go new file mode 100644 index 00000000..f10a779e --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/domain_error.go @@ -0,0 +1,74 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewDomainError(message string) DomainError { + return NewDomainErrorWithCode(message, http.StatusBadRequest) +} + +func NewDomainErrorWithCode(message string, code int) DomainError { + // `NewPlain` doesn't add stack-trace at all + domainErrMessage := errors.NewPlain("domain error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(domainErrMessage, message) + + domainError := &domainError{ + CustomError: NewCustomError(stackErr, code, message), + } + + return domainError +} + +func NewDomainErrorWrap(err error, message string) DomainError { + return NewDomainErrorWithCodeWrap(err, http.StatusBadRequest, message) +} + +func NewDomainErrorWithCodeWrap(err error, code int, message string) DomainError { + if err == nil { + return NewDomainErrorWithCode(message, code) + } + + // `WithMessage` doesn't add stack-trace at all + domainErrMessage := errors.WithMessage(err, "domain error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(domainErrMessage, message) + + domainError := &domainError{ + CustomError: NewCustomError(stackErr, code, message), + } + + return domainError +} + +type domainError struct { + CustomError +} + +type DomainError interface { + CustomError + isDomainError() +} + +func (d *domainError) isDomainError() { +} + +func IsDomainError(err error, code int) bool { + var domainErr DomainError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested notfound error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(DomainError); ok { + return true + } + + // us, ok := errors.Cause(err).(DomainError) + if errors.As(err, &domainErr) { + return domainErr.Status() == code + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/forbiden_error.go b/internal/pkg/http/httperrors/customerrors/forbiden_error.go new file mode 100644 index 00000000..0f91b1e4 --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/forbiden_error.go @@ -0,0 +1,66 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewForbiddenError(message string) ForbiddenError { + // `NewPlain` doesn't add stack-trace at all + forbiddenErrMessage := errors.NewPlain("forbidden error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(forbiddenErrMessage, message) + + forbiddenError := &forbiddenError{ + CustomError: NewCustomError(stackErr, http.StatusForbidden, message), + } + + return forbiddenError +} + +func NewForbiddenErrorWrap(err error, message string) ForbiddenError { + if err == nil { + return NewForbiddenError(message) + } + + // `WithMessage` doesn't add stack-trace at all + forbiddenErrMessage := errors.WithMessage(err, "forbidden error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(forbiddenErrMessage, message) + + forbiddenError := &forbiddenError{ + CustomError: NewCustomError(stackErr, http.StatusForbidden, message), + } + + return forbiddenError +} + +type forbiddenError struct { + CustomError +} + +type ForbiddenError interface { + CustomError + isForbiddenError() +} + +func (f *forbiddenError) isForbiddenError() { +} + +func IsForbiddenError(err error) bool { + var forbiddenError ForbiddenError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested forbidden error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(ForbiddenError); ok { + return true + } + + // us, ok := errors.Cause(err).(ForbiddenError) + if errors.As(err, &forbiddenError) { + return true + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/internal_server_error.go b/internal/pkg/http/httperrors/customerrors/internal_server_error.go new file mode 100644 index 00000000..b2818574 --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/internal_server_error.go @@ -0,0 +1,66 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewInternalServerError(message string) InternalServerError { + // `NewPlain` doesn't add stack-trace at all + internalErrMessage := errors.NewPlain("internal server error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(internalErrMessage, message) + + internalServerError := &internalServerError{ + CustomError: NewCustomError(stackErr, http.StatusInternalServerError, message), + } + + return internalServerError +} + +func NewInternalServerErrorWrap(err error, message string) InternalServerError { + if err == nil { + return NewInternalServerError(message) + } + + // `WithMessage` doesn't add stack-trace at all + internalErrMessage := errors.WithMessage(err, "internal server error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(internalErrMessage, message) + + internalServerError := &internalServerError{ + CustomError: NewCustomError(stackErr, http.StatusInternalServerError, message), + } + + return internalServerError +} + +type internalServerError struct { + CustomError +} + +type InternalServerError interface { + CustomError + isInternalServerError() +} + +func (i *internalServerError) isInternalServerError() { +} + +func IsInternalServerError(err error) bool { + var internalServerErr InternalServerError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested internal server error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(InternalServerError); ok { + return true + } + + // us, ok := errors.Cause(err).(InternalServerError) + if errors.As(err, &internalServerErr) { + return true + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/marshaling_error.go b/internal/pkg/http/httperrors/customerrors/marshaling_error.go new file mode 100644 index 00000000..04c09637 --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/marshaling_error.go @@ -0,0 +1,69 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewMarshalingError(message string) MarshalingError { + // `NewPlain` doesn't add stack-trace at all + marshalingErrMessage := errors.NewPlain("marshaling error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(marshalingErrMessage, message) + + marshalingError := &marshalingError{ + CustomError: NewCustomError(stackErr, http.StatusInternalServerError, message), + } + + return marshalingError +} + +func NewMarshalingErrorWrap(err error, message string) MarshalingError { + if err == nil { + return NewMarshalingError(message) + } + + // `WithMessage` doesn't add stack-trace at all + marshalingErrMessage := errors.WithMessage(err, "marshaling error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(marshalingErrMessage, message) + + marshalingError := &marshalingError{ + CustomError: NewCustomError(stackErr, http.StatusInternalServerError, message), + } + + return marshalingError +} + +type marshalingError struct { + CustomError +} + +type MarshalingError interface { + InternalServerError + isMarshalingError() +} + +func (m *marshalingError) isMarshalingError() { +} + +func (m *marshalingError) isInternalServerError() { +} + +func IsMarshalingError(err error) bool { + var marshalingErr MarshalingError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested marshaling error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(MarshalingError); ok { + return true + } + + // us, ok := errors.Cause(err).(MarshalingError) + if errors.As(err, &marshalingErr) { + return true + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/not_found_error.go b/internal/pkg/http/httperrors/customerrors/not_found_error.go new file mode 100644 index 00000000..ed70336d --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/not_found_error.go @@ -0,0 +1,66 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewNotFoundError(message string) NotFoundError { + // `NewPlain` doesn't add stack-trace at all + notFoundErrMessage := errors.NewPlain("not found error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(notFoundErrMessage, message) + + notFoundError := ¬FoundError{ + CustomError: NewCustomError(stackErr, http.StatusBadRequest, message), + } + + return notFoundError +} + +func NewNotFoundErrorWrap(err error, message string) NotFoundError { + if err == nil { + return NewNotFoundError(message) + } + + // `WithMessage` doesn't add stack-trace at all + notFoundErrMessage := errors.WithMessage(err, "not found error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(notFoundErrMessage, message) + + notFoundError := ¬FoundError{ + CustomError: NewCustomError(stackErr, http.StatusNotFound, message), + } + + return notFoundError +} + +type notFoundError struct { + CustomError +} + +type NotFoundError interface { + CustomError + isNotFoundError() +} + +func (n *notFoundError) isNotFoundError() { +} + +func IsNotFoundError(err error) bool { + var notFoundError NotFoundError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested notfound error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(NotFoundError); ok { + return true + } + + // us, ok := errors.Cause(err).(NotFoundError) + if errors.As(err, ¬FoundError) { + return true + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/unauthorized_error.go b/internal/pkg/http/httperrors/customerrors/unauthorized_error.go new file mode 100644 index 00000000..2a6cc25e --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/unauthorized_error.go @@ -0,0 +1,66 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewUnAuthorizedError(message string) UnauthorizedError { + // `NewPlain` doesn't add stack-trace at all + unAuthorizedErrMessage := errors.NewPlain("unauthorized error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(unAuthorizedErrMessage, message) + + unAuthorizedError := &unauthorizedError{ + CustomError: NewCustomError(stackErr, http.StatusUnauthorized, message), + } + + return unAuthorizedError +} + +func NewUnAuthorizedErrorWrap(err error, message string) UnauthorizedError { + if err == nil { + return NewUnAuthorizedError(message) + } + + // `WithMessage` doesn't add stack-trace at all + unAuthorizedErrMessage := errors.WithMessage(err, "unauthorized error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(unAuthorizedErrMessage, message) + + unAuthorizedError := &unauthorizedError{ + CustomError: NewCustomError(stackErr, http.StatusUnauthorized, message), + } + + return unAuthorizedError +} + +type unauthorizedError struct { + CustomError +} + +type UnauthorizedError interface { + CustomError + isUnAuthorizedError() +} + +func (u *unauthorizedError) isUnAuthorizedError() { +} + +func IsUnAuthorizedError(err error) bool { + var unauthorizedError UnauthorizedError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested unauthorized error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(UnauthorizedError); ok { + return true + } + + // us, ok := errors.Cause(err).(UnauthorizedError) + if errors.As(err, &unauthorizedError) { + return true + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/unmarshaling_error.go b/internal/pkg/http/httperrors/customerrors/unmarshaling_error.go new file mode 100644 index 00000000..e6bbea35 --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/unmarshaling_error.go @@ -0,0 +1,69 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewUnMarshalingError(message string) UnMarshalingError { + // `NewPlain` doesn't add stack-trace at all + unMarshalingErrMessage := errors.NewPlain("unMarshaling error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(unMarshalingErrMessage, message) + + unMarshalingError := &unMarshalingError{ + CustomError: NewCustomError(stackErr, http.StatusInternalServerError, message), + } + + return unMarshalingError +} + +func NewUnMarshalingErrorWrap(err error, message string) UnMarshalingError { + if err == nil { + return NewUnMarshalingError(message) + } + + // `WithMessage` doesn't add stack-trace at all + unMarshalingErrMessage := errors.WithMessage(err, "unMarshaling error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(unMarshalingErrMessage, message) + + unMarshalingError := &unMarshalingError{ + CustomError: NewCustomError(stackErr, http.StatusInternalServerError, message), + } + + return unMarshalingError +} + +type unMarshalingError struct { + CustomError +} + +type UnMarshalingError interface { + InternalServerError + isUnMarshalingError() +} + +func (u *unMarshalingError) isUnMarshalingError() { +} + +func (u *unMarshalingError) isInternalServerError() { +} + +func IsUnMarshalingError(err error) bool { + var unMarshalingError UnMarshalingError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested unMarshaling error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(UnMarshalingError); ok { + return true + } + + // us, ok := errors.Cause(err).(UnMarshalingError) + if errors.As(err, &unMarshalingError) { + return true + } + + return false +} diff --git a/internal/pkg/http/httperrors/customerrors/validation_error.go b/internal/pkg/http/httperrors/customerrors/validation_error.go new file mode 100644 index 00000000..74164940 --- /dev/null +++ b/internal/pkg/http/httperrors/customerrors/validation_error.go @@ -0,0 +1,69 @@ +package customErrors + +import ( + "net/http" + + "emperror.dev/errors" +) + +func NewValidationError(message string) ValidationError { + // `NewPlain` doesn't add stack-trace at all + validationErrMessage := errors.NewPlain("validation error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(validationErrMessage, message) + + validationError := &validationError{ + CustomError: NewCustomError(stackErr, http.StatusBadRequest, message), + } + + return validationError +} + +func NewValidationErrorWrap(err error, message string) ValidationError { + if err == nil { + return NewValidationError(message) + } + + // `WithMessage` doesn't add stack-trace at all + validationErrMessage := errors.WithMessage(err, "validation error") + // `WrapIf` add stack-trace if not added before + stackErr := errors.WrapIf(validationErrMessage, message) + + validationError := &validationError{ + CustomError: NewCustomError(stackErr, http.StatusBadRequest, message), + } + + return validationError +} + +type validationError struct { + CustomError +} + +type ValidationError interface { + BadRequestError + isValidationError() +} + +func (v *validationError) isValidationError() { +} + +func (v *validationError) isBadRequestError() { +} + +func IsValidationError(err error) bool { + var validationError ValidationError + + // https://github.com/golang/go/blob/master/src/net/error_windows.go#L10C2-L12C3 + // this doesn't work for a nested validation error, and we should use errors.As for traversing errors in all levels + if _, ok := err.(ValidationError); ok { + return true + } + + // us, ok := errors.Cause(err).(ValidationError) + if errors.As(err, &validationError) { + return true + } + + return false +} diff --git a/internal/pkg/http/http_errors/problemDetails/custom_problem_details_errors.go b/internal/pkg/http/httperrors/problemdetails/custom_problem_details_errors.go similarity index 97% rename from internal/pkg/http/http_errors/problemDetails/custom_problem_details_errors.go rename to internal/pkg/http/httperrors/problemdetails/custom_problem_details_errors.go index f40a42ea..5529e38a 100644 --- a/internal/pkg/http/http_errors/problemDetails/custom_problem_details_errors.go +++ b/internal/pkg/http/httperrors/problemdetails/custom_problem_details_errors.go @@ -4,7 +4,7 @@ import ( "net/http" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" ) func NewValidationProblemDetail(detail string, stackTrace string) ProblemDetailErr { diff --git a/internal/pkg/http/http_errors/problemDetails/option_builder.go b/internal/pkg/http/httperrors/problemdetails/option_builder.go similarity index 100% rename from internal/pkg/http/http_errors/problemDetails/option_builder.go rename to internal/pkg/http/httperrors/problemdetails/option_builder.go diff --git a/internal/pkg/http/http_errors/problemDetails/problem_detail_parser.go b/internal/pkg/http/httperrors/problemdetails/problem_detail_parser.go similarity index 67% rename from internal/pkg/http/http_errors/problemDetails/problem_detail_parser.go rename to internal/pkg/http/httperrors/problemdetails/problem_detail_parser.go index 85bfd2ae..4ec43eb6 100644 --- a/internal/pkg/http/http_errors/problemDetails/problem_detail_parser.go +++ b/internal/pkg/http/httperrors/problemdetails/problem_detail_parser.go @@ -6,10 +6,10 @@ import ( "net/http" "reflect" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + errorUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils/errorutils" "emperror.dev/errors" "github.com/go-playground/validator" @@ -19,7 +19,11 @@ type ProblemDetailParser struct { internalErrors map[reflect.Type]func(err error) ProblemDetailErr } -func NewProblemDetailParser(builder func(builder *OptionBuilder)) *ProblemDetailParser { +type ErrorParserFunc func(err error) ProblemDetailErr + +func NewProblemDetailParser( + builder func(builder *OptionBuilder), +) *ProblemDetailParser { optionBuilder := NewOptionBuilder() builder(optionBuilder) items := optionBuilder.Build() @@ -40,14 +44,26 @@ func ParseError(err error) ProblemDetailErr { customErr := customErrors.GetCustomError(err) var validatorErr validator.ValidationErrors - if err != nil { + if err != nil && customErr != nil { switch { case customErrors.IsDomainError(err, customErr.Status()): - return NewDomainProblemDetail(customErr.Status(), customErr.Error(), stackTrace) + return NewDomainProblemDetail( + customErr.Status(), + customErr.Error(), + stackTrace, + ) case customErrors.IsApplicationError(err, customErr.Status()): - return NewApplicationProblemDetail(customErr.Status(), customErr.Error(), stackTrace) + return NewApplicationProblemDetail( + customErr.Status(), + customErr.Error(), + stackTrace, + ) case customErrors.IsApiError(err, customErr.Status()): - return NewApiProblemDetail(customErr.Status(), customErr.Error(), stackTrace) + return NewApiProblemDetail( + customErr.Status(), + customErr.Error(), + stackTrace, + ) case customErrors.IsBadRequestError(err): return NewBadRequestProblemDetail(customErr.Error(), stackTrace) case customErrors.IsNotFoundError(err): @@ -55,7 +71,10 @@ func ParseError(err error) ProblemDetailErr { case customErrors.IsValidationError(err): return NewValidationProblemDetail(customErr.Error(), stackTrace) case customErrors.IsUnAuthorizedError(err): - return NewUnAuthorizedErrorProblemDetail(customErr.Error(), stackTrace) + return NewUnAuthorizedErrorProblemDetail( + customErr.Error(), + stackTrace, + ) case customErrors.IsForbiddenError(err): return NewForbiddenProblemDetail(customErr.Error(), stackTrace) case customErrors.IsConflictError(err): @@ -63,11 +82,21 @@ func ParseError(err error) ProblemDetailErr { case customErrors.IsInternalServerError(err): return NewInternalServerProblemDetail(customErr.Error(), stackTrace) case customErrors.IsCustomError(err): - return NewProblemDetailFromCodeAndDetail(customErr.Status(), customErr.Error(), stackTrace) + return NewProblemDetailFromCodeAndDetail( + customErr.Status(), + customErr.Error(), + stackTrace, + ) case customErrors.IsUnMarshalingError(err): return NewInternalServerProblemDetail(err.Error(), stackTrace) case customErrors.IsMarshalingError(err): return NewInternalServerProblemDetail(err.Error(), stackTrace) + + default: + return NewInternalServerProblemDetail(err.Error(), stackTrace) + } + } else if err != nil && customErr == nil { + switch { case errors.Is(err, sql.ErrNoRows): return NewNotFoundErrorProblemDetail(err.Error(), stackTrace) case errors.Is(err, context.DeadlineExceeded): diff --git a/internal/pkg/http/http_errors/problemDetails/problem_details.go b/internal/pkg/http/httperrors/problemdetails/problem_details.go similarity index 90% rename from internal/pkg/http/http_errors/problemDetails/problem_details.go rename to internal/pkg/http/httperrors/problemdetails/problem_details.go index f7a8d61f..aec09ce2 100644 --- a/internal/pkg/http/http_errors/problemDetails/problem_details.go +++ b/internal/pkg/http/httperrors/problemdetails/problem_details.go @@ -7,9 +7,9 @@ import ( "reflect" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/contracts" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/contracts" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "emperror.dev/errors" ) @@ -114,7 +114,12 @@ func (p *problemDetail) SetStackTrace(stackTrace string) ProblemDetailErr { } // NewProblemDetail New ProblemDetail Error -func NewProblemDetail(status int, title string, detail string, stackTrace string) ProblemDetailErr { +func NewProblemDetail( + status int, + title string, + detail string, + stackTrace string, +) ProblemDetailErr { problemDetail := &problemDetail{ Status: status, Title: title, @@ -155,7 +160,7 @@ func NewProblemDetailFromCodeAndDetail( } func Map[E error](problem ProblemDetailFunc[E]) { - errorType := typeMapper.GetTypeFromGeneric[E]() + errorType := typeMapper.GetGenericTypeByT[E]() if errorType.Kind() == reflect.Interface { types := typeMapper.TypesImplementedInterface[E]() for _, t := range types { @@ -191,7 +196,7 @@ func ResolveProblemDetail(err error) ProblemDetailErr { // WriteTo writes the JSON Problem to an HTTP Response Writer func WriteTo(p ProblemDetailErr, w http.ResponseWriter) (int, error) { - defaultLogger.Logger.Error(p.Error()) + defaultLogger.GetLogger().Error(p.Error()) stackTrace := p.GetStackTrace() fmt.Println(stackTrace) diff --git a/internal/pkg/http/http_errors/problemDetails/problem_details_test.go b/internal/pkg/http/httperrors/problemdetails/problem_details_test.go similarity index 95% rename from internal/pkg/http/http_errors/problemDetails/problem_details_test.go rename to internal/pkg/http/httperrors/problemdetails/problem_details_test.go index 0f618e7a..258363e1 100644 --- a/internal/pkg/http/http_errors/problemDetails/problem_details_test.go +++ b/internal/pkg/http/httperrors/problemdetails/problem_details_test.go @@ -4,7 +4,7 @@ import ( "net/http" "testing" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" "emperror.dev/errors" "github.com/stretchr/testify/assert" diff --git a/internal/pkg/logger/config/log_options.go b/internal/pkg/logger/config/log_options.go index 6163960b..20631122 100644 --- a/internal/pkg/logger/config/log_options.go +++ b/internal/pkg/logger/config/log_options.go @@ -1,22 +1,23 @@ package config import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/models" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/models" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) -var optionName = strcase.ToLowerCamel(typeMapper.GetTypeNameByT[LogOptions]()) +var optionName = strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[LogOptions]()) type LogOptions struct { LogLevel string `mapstructure:"level"` LogType models.LogType `mapstructure:"logType"` CallerEnabled bool `mapstructure:"callerEnabled"` + EnableTracing bool `mapstructure:"enableTracing" default:"true"` } -func ProvideLogConfig(env environemnt.Environment) (*LogOptions, error) { +func ProvideLogConfig(env environment.Environment) (*LogOptions, error) { return config.BindConfigKey[*LogOptions](optionName, env) } diff --git a/internal/pkg/logger/default_logger/default_logger.go b/internal/pkg/logger/default_logger/default_logger.go deleted file mode 100644 index 0623b17c..00000000 --- a/internal/pkg/logger/default_logger/default_logger.go +++ /dev/null @@ -1,34 +0,0 @@ -package defaultLogger - -import ( - "os" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/logrous" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/models" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/zap" -) - -var Logger logger.Logger - -func SetupDefaultLogger() { - logType := os.Getenv("LogConfig_LogType") - - switch logType { - case "Zap", "": - Logger = zap.NewZapLogger( - &config.LogOptions{LogType: models.Zap, CallerEnabled: false}, - constants.Dev, - ) - break - case "Logrus": - Logger = logrous.NewLogrusLogger( - &config.LogOptions{LogType: models.Logrus, CallerEnabled: false}, - constants.Dev, - ) - break - default: - } -} diff --git a/internal/pkg/logger/defaultlogger/default_logger.go b/internal/pkg/logger/defaultlogger/default_logger.go new file mode 100644 index 00000000..5947ab0e --- /dev/null +++ b/internal/pkg/logger/defaultlogger/default_logger.go @@ -0,0 +1,42 @@ +package defaultLogger + +import ( + "os" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/logrous" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" +) + +var l logger.Logger + +func initLogger() { + logType := os.Getenv("LogConfig_LogType") + + switch logType { + case "Zap", "": + l = zap.NewZapLogger( + &config.LogOptions{LogType: models.Zap, CallerEnabled: false}, + constants.Dev, + ) + break + case "Logrus": + l = logrous.NewLogrusLogger( + &config.LogOptions{LogType: models.Logrus, CallerEnabled: false}, + constants.Dev, + ) + break + default: + } +} + +func GetLogger() logger.Logger { + if l == nil { + initLogger() + } + + return l +} diff --git a/internal/pkg/logger/empty/empty_fx.go b/internal/pkg/logger/empty/empty_fx.go index e7d60fbb..ce6b89c7 100644 --- a/internal/pkg/logger/empty/empty_fx.go +++ b/internal/pkg/logger/empty/empty_fx.go @@ -1,8 +1,8 @@ package empty import ( - logger2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/config" + logger2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/config" "go.uber.org/fx" ) diff --git a/internal/pkg/logger/empty/empty_logger.go b/internal/pkg/logger/empty/empty_logger.go index 4808a905..2515d58e 100644 --- a/internal/pkg/logger/empty/empty_logger.go +++ b/internal/pkg/logger/empty/empty_logger.go @@ -3,8 +3,8 @@ package empty import ( "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/models" ) var EmptyLogger logger.Logger = &emptyLogger{} diff --git a/internal/pkg/logger/external/fxlog/fx.go b/internal/pkg/logger/external/fxlog/fx.go index 934049ff..9526ac46 100644 --- a/internal/pkg/logger/external/fxlog/fx.go +++ b/internal/pkg/logger/external/fxlog/fx.go @@ -3,7 +3,7 @@ package fxlog import ( "strings" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "go.uber.org/fx" "go.uber.org/fx/fxevent" @@ -139,7 +139,7 @@ func (l *FxCustomLogger) LogEvent(event fxevent.Event) { logger.Fields{"error": e.Err}, ) } else { - l.Debugw("initialized custom fxevent.Logger", logger.Fields{"function": e.ConstructorName}) + l.Debugw("initialized custom fxevent.logger", logger.Fields{"function": e.ConstructorName}) } } } diff --git a/internal/pkg/logger/external/gromlog/gorm.go b/internal/pkg/logger/external/gromlog/gorm.go index 65a2577f..b4dcc576 100644 --- a/internal/pkg/logger/external/gromlog/gorm.go +++ b/internal/pkg/logger/external/gromlog/gorm.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" gormlogger "gorm.io/gorm/logger" ) @@ -20,12 +20,12 @@ type GormCustomLogger struct { func NewGormCustomLogger(logger logger.Logger) *GormCustomLogger { //cfg, err := config.ProvideLogConfig() // - //var logger logger.Logger - //if cfg.LogType == models.Logrus && err != nil { + //var logger logger.logger + //if cfg.LogType == datamodels.Logrus && err != nil { // logger = logrous.NewLogrusLogger(cfg, constants.Dev) //} else { // if err != nil { - // cfg = &config.LogOptions{LogLevel: "info", LogType: models.Zap} + // cfg = &config.LogOptions{LogLevel: "info", LogType: datamodels.Zap} // } // logger = zap.NewZapLogger(cfg, constants.Dev) //} diff --git a/internal/pkg/logger/logger.go b/internal/pkg/logger/logger.go index 50451bdd..7d48aff1 100644 --- a/internal/pkg/logger/logger.go +++ b/internal/pkg/logger/logger.go @@ -3,7 +3,7 @@ package logger import ( "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/models" ) type Fields map[string]interface{} diff --git a/internal/pkg/logger/logrous/logrous_fx.go b/internal/pkg/logger/logrous/logrous_fx.go index efb23e29..ebc42421 100644 --- a/internal/pkg/logger/logrous/logrous_fx.go +++ b/internal/pkg/logger/logrous/logrous_fx.go @@ -1,8 +1,8 @@ package logrous import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/config" "go.uber.org/fx" ) diff --git a/internal/pkg/logger/logrous/logrous_logger.go b/internal/pkg/logger/logrous/logrous_logger.go index 277bd89a..a67b72f4 100644 --- a/internal/pkg/logger/logrous/logrous_logger.go +++ b/internal/pkg/logger/logrous/logrous_logger.go @@ -4,14 +4,15 @@ import ( "os" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - config2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + config2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/models" "github.com/nolleh/caption_json_formatter" "github.com/sirupsen/logrus" + "github.com/uptrace/opentelemetry-go-extra/otellogrus" ) type logrusLogger struct { @@ -32,7 +33,10 @@ var loggerLevelMap = map[string]logrus.Level{ } // NewLogrusLogger creates a new logrus logger -func NewLogrusLogger(cfg *config2.LogOptions, env environemnt.Environment) logger.Logger { +func NewLogrusLogger( + cfg *config2.LogOptions, + env environment.Environment, +) logger.Logger { logrusLogger := &logrusLogger{level: cfg.LogLevel, logOptions: cfg} logrusLogger.initLogger(env) @@ -40,7 +44,7 @@ func NewLogrusLogger(cfg *config2.LogOptions, env environemnt.Environment) logge } // InitLogger Init logger -func (l *logrusLogger) initLogger(env environemnt.Environment) { +func (l *logrusLogger) initLogger(env environment.Environment) { logLevel := l.GetLoggerLevel() // Create a new instance of the logger. You can have any number of instances. @@ -65,6 +69,16 @@ func (l *logrusLogger) initLogger(env environemnt.Environment) { logrusLogger.SetFormatter(&caption_json_formatter.Formatter{PrettyPrint: true}) } + if l.logOptions.EnableTracing { + // Instrument logrus. + logrus.AddHook(otellogrus.NewHook(otellogrus.WithLevels( + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + logrus.WarnLevel, + ))) + } + l.logger = logrusLogger } @@ -190,6 +204,8 @@ func (l *logrusLogger) GrpcClientInterceptorLogger( ) } -func (l *logrusLogger) mapToFields(fields map[string]interface{}) *logrus.Entry { +func (l *logrusLogger) mapToFields( + fields map[string]interface{}, +) *logrus.Entry { return l.logger.WithFields(logrus.Fields{"ss": 1}) } diff --git a/internal/pkg/logger/pipelines/logging_pipeline.go b/internal/pkg/logger/pipelines/logging_pipeline.go new file mode 100644 index 00000000..4d852ce2 --- /dev/null +++ b/internal/pkg/logger/pipelines/logging_pipeline.go @@ -0,0 +1,58 @@ +package loggingpipelines + +import ( + "context" + "fmt" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/mehdihadeli/go-mediatr" +) + +type requestLoggerPipeline struct { + logger logger.Logger +} + +func NewMediatorLoggingPipeline(l logger.Logger) mediatr.PipelineBehavior { + return &requestLoggerPipeline{logger: l} +} + +func (r *requestLoggerPipeline) Handle( + ctx context.Context, + request interface{}, + next mediatr.RequestHandlerFunc, +) (interface{}, error) { + startTime := time.Now() + defer func() { + elapsed := time.Since(startTime) + r.logger.Infof("Request took %s", elapsed) + }() + + requestName := typeMapper.GetNonePointerTypeName(request) + + r.logger.Infow( + fmt.Sprintf("Handling request: '%s'", requestName), + logger.Fields{"Request": request}, + ) + + response, err := next(ctx) + if err != nil { + r.logger.Infof("Request failed with error: %v", err) + + return nil, err + } + + responseName := typeMapper.GetNonePointerTypeName(response) + + r.logger.Infow( + fmt.Sprintf( + "Request handled successfully with response: '%s'", + responseName, + ), + logger.Fields{"Response": response}, + ) + + return response, nil +} diff --git a/internal/pkg/logger/zap/zap_fx.go b/internal/pkg/logger/zap/zap_fx.go index db43ea6a..18095385 100644 --- a/internal/pkg/logger/zap/zap_fx.go +++ b/internal/pkg/logger/zap/zap_fx.go @@ -1,8 +1,8 @@ package zap import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/config" "go.uber.org/fx" ) diff --git a/internal/pkg/logger/zap/zap_logger.go b/internal/pkg/logger/zap/zap_logger.go index bb142a9d..f6009530 100644 --- a/internal/pkg/logger/zap/zap_logger.go +++ b/internal/pkg/logger/zap/zap_logger.go @@ -4,12 +4,13 @@ import ( "os" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - config2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/constants" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + config2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/models" + "github.com/uptrace/opentelemetry-go-extra/otelzap" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) @@ -40,9 +41,13 @@ var loggerLevelMap = map[string]zapcore.Level{ } // NewZapLogger create new zap logger -func NewZapLogger(cfg *config2.LogOptions, env environemnt.Environment) ZapLogger { +func NewZapLogger( + cfg *config2.LogOptions, + env environment.Environment, +) ZapLogger { zapLogger := &zapLogger{level: cfg.LogLevel, logOptions: cfg} zapLogger.initLogger(env) + return zapLogger } @@ -55,11 +60,12 @@ func (l *zapLogger) getLoggerLevel() zapcore.Level { if !exist { return zapcore.DebugLevel } + return level } // InitLogger Init logger -func (l *zapLogger) initLogger(env environemnt.Environment) { +func (l *zapLogger) initLogger(env environment.Environment) { logLevel := l.getLoggerLevel() logWriter := zapcore.AddSync(os.Stdout) @@ -109,6 +115,11 @@ func (l *zapLogger) initLogger(env environemnt.Environment) { logger := zap.New(core, options...) + if l.logOptions.EnableTracing { + // add logs as events to tracing + logger = otelzap.New(logger).Logger + } + l.logger = logger l.sugarLogger = logger.Sugar() } @@ -301,6 +312,7 @@ func getFieldType(value interface{}) zapcore.FieldType { case error: return zapcore.ErrorType default: - return zapcore.StringerType + // uses reflection to serialize arbitrary objects, so it can be slow and allocation-heavy. + return zapcore.ReflectType } } diff --git a/internal/pkg/mapper/mapper.go b/internal/pkg/mapper/mapper.go index e5693696..daa367c6 100644 --- a/internal/pkg/mapper/mapper.go +++ b/internal/pkg/mapper/mapper.go @@ -7,8 +7,8 @@ import ( "fmt" "reflect" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - reflectionHelper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/reflection_helper" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + reflectionHelper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/reflectionhelper" "emperror.dev/errors" "github.com/ahmetb/go-linq/v3" @@ -80,9 +80,16 @@ func CreateMap[TSrc any, TDst any]() error { return ErrUnsupportedMap } - if srcType.Kind() == reflect.Ptr && srcType.Elem().Kind() == reflect.Struct { - pointerStructTypeKey := mappingsEntry{SourceType: srcType, DestinationType: desType} - nonePointerStructTypeKey := mappingsEntry{SourceType: srcType.Elem(), DestinationType: desType.Elem()} + if srcType.Kind() == reflect.Ptr && + srcType.Elem().Kind() == reflect.Struct { + pointerStructTypeKey := mappingsEntry{ + SourceType: srcType, + DestinationType: desType, + } + nonePointerStructTypeKey := mappingsEntry{ + SourceType: srcType.Elem(), + DestinationType: desType.Elem(), + } if _, ok := maps[nonePointerStructTypeKey]; ok { return ErrMapAlreadyExists } @@ -108,11 +115,13 @@ func CreateMap[TSrc any, TDst any]() error { maps[pointerStructTypeKey] = nil } - if srcType.Kind() == reflect.Ptr && srcType.Elem().Kind() == reflect.Struct { + if srcType.Kind() == reflect.Ptr && + srcType.Elem().Kind() == reflect.Struct { srcType = srcType.Elem() } - if desType.Kind() == reflect.Ptr && desType.Elem().Kind() == reflect.Struct { + if desType.Kind() == reflect.Ptr && + desType.Elem().Kind() == reflect.Struct { desType = desType.Elem() } @@ -163,14 +172,16 @@ func Map[TDes any, TSrc any](src TSrc) (TDes, error) { desIsArray := false srcIsArray := false - if srcType.Kind() == reflect.Array || (srcType.Kind() == reflect.Ptr && srcType.Elem().Kind() == reflect.Array) || + if srcType.Kind() == reflect.Array || + (srcType.Kind() == reflect.Ptr && srcType.Elem().Kind() == reflect.Array) || srcType.Kind() == reflect.Slice || (srcType.Kind() == reflect.Ptr && srcType.Elem().Kind() == reflect.Slice) { srcType = srcType.Elem() srcIsArray = true } - if desType.Kind() == reflect.Array || (desType.Kind() == reflect.Ptr && desType.Elem().Kind() == reflect.Array) || + if desType.Kind() == reflect.Array || + (desType.Kind() == reflect.Ptr && desType.Elem().Kind() == reflect.Array) || desType.Kind() == reflect.Slice || (desType.Kind() == reflect.Ptr && desType.Elem().Kind() == reflect.Slice) { desType = desType.Elem() @@ -209,7 +220,7 @@ func configProfile(srcType reflect.Type, destType reflect.Type) { // check for provided types kind. // if not struct - skip. if srcType.Kind() != reflect.Struct { - defaultLogger.Logger.Errorf( + defaultLogger.GetLogger().Errorf( "expected reflect.Struct kind for type %s, but got %s", srcType.String(), srcType.Kind().String(), @@ -217,7 +228,7 @@ func configProfile(srcType reflect.Type, destType reflect.Type) { } if destType.Kind() != reflect.Struct { - defaultLogger.Logger.Errorf( + defaultLogger.GetLogger().Errorf( "expected reflect.Struct kind for type %s, but got %s", destType.String(), destType.Kind().String(), @@ -234,7 +245,10 @@ func configProfile(srcType reflect.Type, destType reflect.Type) { for srcKey, srcTag := range srcMeta.keysToTags { if _, ok := destMeta.keysToTags[strcase.ToCamel(srcKey)]; ok { - profile = append(profile, [2]string{srcKey, strcase.ToCamel(srcKey)}) + profile = append( + profile, + [2]string{srcKey, strcase.ToCamel(srcKey)}, + ) } // case src key equals dest key @@ -319,7 +333,7 @@ func mapStructs[TDes any, TSrc any](src reflect.Value, dest reflect.Value) { // if types or their slices were not registered - abort profile, ok := profiles[getProfileKey(src.Type(), dest.Type())] if !ok { - defaultLogger.Logger.Errorf( + defaultLogger.GetLogger().Errorf( "no conversion specified for types %s and %s", src.Type().String(), dest.Type().String(), @@ -337,7 +351,9 @@ func mapStructs[TDes any, TSrc any](src reflect.Value, dest reflect.Value) { // var destinationFieldValue reflect.Value if !sourceField.CanInterface() { if mapperConfig.MapUnexportedFields { - sourceFiledValue = reflectionHelper.GetFieldValue(sourceField) + sourceFiledValue = reflectionHelper.GetFieldValue( + sourceField, + ) } else { // for getting pointer for non-pointer struct we can use reflect.Addr() for calling pointer receivers properties sourceFiledValue = reflectionHelper.GetFieldValueFromMethodAndReflectValue(src.Addr(), strcase.ToCamel(keys[SrcKeyIndex])) @@ -404,7 +420,10 @@ func mapMaps[TDes any, TSrc any](src reflect.Value, dest reflect.Value) { } } -func processValues[TDes any, TSrc any](src reflect.Value, dest reflect.Value) error { +func processValues[TDes any, TSrc any]( + src reflect.Value, + dest reflect.Value, +) error { // if src of dest is an interface - get underlying type if src.Kind() == reflect.Interface { src = src.Elem() diff --git a/internal/pkg/messaging/bus/bus.go b/internal/pkg/messaging/bus/bus.go deleted file mode 100644 index 9719400f..00000000 --- a/internal/pkg/messaging/bus/bus.go +++ /dev/null @@ -1,12 +0,0 @@ -package bus - -import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/producer" -) - -type Bus interface { - producer.Producer - consumer.BusControl - consumer.ConsumerConnector -} diff --git a/internal/pkg/messaging/otel/tracing/utils.go b/internal/pkg/messaging/otel/tracing/utils.go deleted file mode 100644 index 91c17097..00000000 --- a/internal/pkg/messaging/otel/tracing/utils.go +++ /dev/null @@ -1,38 +0,0 @@ -package tracing - -import ( - "context" - - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" -) - -// TraceMessagingErrFromSpan setting span with status error with error message -func TraceMessagingErrFromSpan(span trace.Span, err error) error { - if err != nil { - stackTraceError := errorUtils.ErrorsWithStack(err) - span.SetStatus(codes.Error, "") - span.SetAttributes(attribute.String(MessagingErrorMessage, stackTraceError)) - span.RecordError(err) - } - - return err -} - -func TraceMessagingErrFromContext(ctx context.Context, err error) error { - // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors - span := trace.SpanFromContext(ctx) - defer span.End() - - if err != nil { - stackTraceError := errorUtils.ErrorsWithStack(err) - span.SetStatus(codes.Error, "") - span.SetAttributes(attribute.String(MessagingErrorMessage, stackTraceError)) - span.RecordError(err) - } - - return err -} diff --git a/internal/pkg/migration/gomigrate/gomigrate_fx.go b/internal/pkg/migration/gomigrate/gomigrate_fx.go index 97b9874c..846d0b57 100644 --- a/internal/pkg/migration/gomigrate/gomigrate_fx.go +++ b/internal/pkg/migration/gomigrate/gomigrate_fx.go @@ -1,7 +1,7 @@ package gomigrate import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/migration" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration" "go.uber.org/fx" ) diff --git a/internal/pkg/migration/gomigrate/postgres.go b/internal/pkg/migration/gomigrate/postgres.go index 8d71d44a..cfa267a2 100644 --- a/internal/pkg/migration/gomigrate/postgres.go +++ b/internal/pkg/migration/gomigrate/postgres.go @@ -7,9 +7,9 @@ import ( "database/sql" "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/migration" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/migration/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration/contracts" "emperror.dev/errors" "github.com/golang-migrate/migrate/v4" diff --git a/internal/pkg/migration/goose/goose_fx.go b/internal/pkg/migration/goose/goose_fx.go index 6f1547d2..65c362ac 100644 --- a/internal/pkg/migration/goose/goose_fx.go +++ b/internal/pkg/migration/goose/goose_fx.go @@ -1,7 +1,7 @@ package goose import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/migration" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration" "go.uber.org/fx" ) diff --git a/internal/pkg/migration/goose/postgres.go b/internal/pkg/migration/goose/postgres.go index 91e3d0c3..b8d9fa19 100644 --- a/internal/pkg/migration/goose/postgres.go +++ b/internal/pkg/migration/goose/postgres.go @@ -8,9 +8,9 @@ import ( "errors" "strconv" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - migration "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/migration" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/migration/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + migration "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration/contracts" "github.com/pressly/goose/v3" ) @@ -43,21 +43,34 @@ func (m *goosePostgresMigrator) Down(_ context.Context, version uint) error { return err } -func (m *goosePostgresMigrator) executeCommand(command migration.CommandType, version uint) error { +func (m *goosePostgresMigrator) executeCommand( + command migration.CommandType, + version uint, +) error { switch command { case migration.Up: if version == 0 { - // In test environment, ewe need a fix for applying application working directory correctly. we will apply this in our environment setup process in `config/environment` file + // In test environment, we need a fix for applying application working directory correctly. we will apply this in our environment setup process in `config/environment` file return goose.Run("up", m.db, m.config.MigrationsDir) } - return goose.Run("up-to VERSION ", m.db, m.config.MigrationsDir, strconv.FormatUint(uint64(version), 10)) + return goose.Run( + "up-to VERSION ", + m.db, + m.config.MigrationsDir, + strconv.FormatUint(uint64(version), 10), + ) case migration.Down: if version == 0 { return goose.Run("down", m.db, m.config.MigrationsDir) } - return goose.Run("down-to VERSION ", m.db, m.config.MigrationsDir, strconv.FormatUint(uint64(version), 10)) + return goose.Run( + "down-to VERSION ", + m.db, + m.config.MigrationsDir, + strconv.FormatUint(uint64(version), 10), + ) default: return errors.New("invalid migration direction") } diff --git a/internal/pkg/migration/migration_options.go b/internal/pkg/migration/migration_options.go index 99997a0e..b7f5c9ad 100644 --- a/internal/pkg/migration/migration_options.go +++ b/internal/pkg/migration/migration_options.go @@ -1,9 +1,9 @@ package migration import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) @@ -27,8 +27,8 @@ type MigrationOptions struct { SkipMigration bool `mapstructure:"skipMigration"` } -func ProvideConfig(environment environemnt.Environment) (*MigrationOptions, error) { - optionName := strcase.ToLowerCamel(typeMapper.GetTypeNameByT[MigrationOptions]()) +func ProvideConfig(environment environment.Environment) (*MigrationOptions, error) { + optionName := strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[MigrationOptions]()) return config.BindConfigKey[*MigrationOptions](optionName, environment) } diff --git a/internal/pkg/mongodb/health.go b/internal/pkg/mongodb/health.go index e48af264..173916fe 100644 --- a/internal/pkg/mongodb/health.go +++ b/internal/pkg/mongodb/health.go @@ -3,7 +3,7 @@ package mongodb import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/health" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" "go.mongodb.org/mongo-driver/mongo" ) @@ -12,7 +12,7 @@ type mongoHealthChecker struct { client *mongo.Client } -func NewMongoHealthChecker(client *mongo.Client) health.Health { +func NewMongoHealthChecker(client *mongo.Client) contracts.Health { return &mongoHealthChecker{client} } diff --git a/internal/pkg/mongodb/helpers.go b/internal/pkg/mongodb/helpers.go index 3f10068f..02b605ab 100644 --- a/internal/pkg/mongodb/helpers.go +++ b/internal/pkg/mongodb/helpers.go @@ -3,7 +3,7 @@ package mongodb import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" "emperror.dev/errors" "go.mongodb.org/mongo-driver/bson" @@ -31,28 +31,31 @@ func Paginate[T any]( limit := int64(listQuery.GetLimit()) skip := int64(listQuery.GetOffset()) - cursor, err := collection.Find(ctx, filter, &options.FindOptions{ - Limit: &limit, - Skip: &skip, - }) + cursor, err := collection.Find( + ctx, + filter, + &options.FindOptions{ + Limit: &limit, + Skip: &skip, + }) if err != nil { - return nil, errors.WrapIf(err, "Find") + return nil, err } - defer cursor.Close(ctx) // nolint: errcheck - products := make([]T, 0, listQuery.GetSize()) + defer cursor.Close(ctx) - for cursor.Next(ctx) { - var prod T - if err := cursor.Decode(&prod); err != nil { - return nil, errors.WrapIf(err, "Find") - } - products = append(products, prod) - } + var items []T - if err := cursor.Err(); err != nil { - return nil, errors.WrapIf(err, "cursor.Err") + // https://www.mongodb.com/docs/drivers/go/current/fundamentals/crud/read-operations/cursor/#retrieve-all-documents + err = cursor.All(ctx, &items) + if err != nil { + return nil, err } - return utils.NewListResult[T](products, listQuery.GetSize(), listQuery.GetPage(), count), nil + return utils.NewListResult[T]( + items, + listQuery.GetSize(), + listQuery.GetPage(), + count, + ), nil } diff --git a/internal/pkg/mongodb/mongo_fx.go b/internal/pkg/mongodb/mongo_fx.go index f4ec39a4..ca941fbc 100644 --- a/internal/pkg/mongodb/mongo_fx.go +++ b/internal/pkg/mongodb/mongo_fx.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/health" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "go.mongodb.org/mongo-driver/mongo" "go.uber.org/fx" @@ -26,7 +26,7 @@ var ( NewMongoDB, fx.Annotate( NewMongoHealthChecker, - fx.As(new(health.Health)), + fx.As(new(contracts.Health)), fx.ResultTags(fmt.Sprintf(`group:"%s"`, "healths")), ), ) @@ -34,7 +34,11 @@ var ( mongoInvokes = fx.Invoke(registerHooks) //nolint:gochecknoglobals ) -func registerHooks(lc fx.Lifecycle, client *mongo.Client, logger logger.Logger) { +func registerHooks( + lc fx.Lifecycle, + client *mongo.Client, + logger logger.Logger, +) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { err := client.Ping(ctx, nil) diff --git a/internal/pkg/mongodb/mongo_options.go b/internal/pkg/mongodb/mongo_options.go index 46ba2f6b..edb1bd6b 100644 --- a/internal/pkg/mongodb/mongo_options.go +++ b/internal/pkg/mongodb/mongo_options.go @@ -1,23 +1,28 @@ package mongodb import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) type MongoDbOptions struct { - Host string `mapstructure:"host"` - Port int `mapstructure:"port"` - User string `mapstructure:"user"` - Password string `mapstructure:"password"` - Database string `mapstructure:"database"` - UseAuth bool `mapstructure:"useAuth"` + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + User string `mapstructure:"user"` + Password string `mapstructure:"password"` + Database string `mapstructure:"database"` + UseAuth bool `mapstructure:"useAuth"` + EnableTracing bool `mapstructure:"enableTracing" default:"true"` } -func provideConfig(environment environemnt.Environment) (*MongoDbOptions, error) { - optionName := strcase.ToLowerCamel(typeMapper.GetTypeNameByT[MongoDbOptions]()) +func provideConfig( + environment environment.Environment, +) (*MongoDbOptions, error) { + optionName := strcase.ToLowerCamel( + typeMapper.GetGenericTypeNameByT[MongoDbOptions](), + ) return config.BindConfigKey[*MongoDbOptions](optionName, environment) } diff --git a/internal/pkg/mongodb/mongodb.go b/internal/pkg/mongodb/mongodb.go index e448c09f..338f079e 100644 --- a/internal/pkg/mongodb/mongodb.go +++ b/internal/pkg/mongodb/mongodb.go @@ -8,6 +8,7 @@ import ( "github.com/kamva/mgm/v3" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" _ "github.com/golang-migrate/migrate/v4/source/file" ) @@ -21,7 +22,13 @@ const ( // NewMongoDB Create new MongoDB client func NewMongoDB(cfg *MongoDbOptions) (*mongo.Client, error) { - uriAddress := fmt.Sprintf("mongodb://%s:%s@%s:%d", cfg.User, cfg.Password, cfg.Host, cfg.Port) + uriAddress := fmt.Sprintf( + "mongodb://%s:%s@%s:%d", + cfg.User, + cfg.Password, + cfg.Host, + cfg.Port, + ) opt := options.Client().ApplyURI(uriAddress). SetConnectTimeout(connectTimeout). SetMaxConnIdleTime(maxConnIdleTime). @@ -29,7 +36,9 @@ func NewMongoDB(cfg *MongoDbOptions) (*mongo.Client, error) { SetMaxPoolSize(maxPoolSize) if cfg.UseAuth { - opt = opt.SetAuth(options.Credential{Username: cfg.User, Password: cfg.Password}) + opt = opt.SetAuth( + options.Credential{Username: cfg.User, Password: cfg.Password}, + ) } ctx := context.Background() @@ -38,6 +47,11 @@ func NewMongoDB(cfg *MongoDbOptions) (*mongo.Client, error) { return nil, err } + if cfg.EnableTracing { + // add tracing + opt.Monitor = otelmongo.NewMonitor() + } + // setup https://github.com/Kamva/mgm err = mgm.SetDefaultConfig(nil, cfg.Database, opt) if err != nil { diff --git a/internal/pkg/mongodb/repository/mongo_generic_repository.go b/internal/pkg/mongodb/repository/mongo_generic_repository.go index 32b74215..589e5bde 100644 --- a/internal/pkg/mongodb/repository/mongo_generic_repository.go +++ b/internal/pkg/mongodb/repository/mongo_generic_repository.go @@ -4,14 +4,14 @@ import ( "context" "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data/specification" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mapper" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mongodb" - reflectionHelper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/reflection_helper" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/data/specification" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + reflectionHelper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/reflectionhelper" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" "emperror.dev/errors" "github.com/goccy/go-reflect" @@ -61,9 +61,12 @@ func NewGenericMongoRepository[TEntity interface{}]( } } -func (m *mongoGenericRepository[TDataModel, TEntity]) Add(ctx context.Context, entity TEntity) error { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() +func (m *mongoGenericRepository[TDataModel, TEntity]) Add( + ctx context.Context, + entity TEntity, +) error { + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() collection := m.db.Database(m.databaseName).Collection(m.collectionName) @@ -91,7 +94,10 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) Add(ctx context.Context, e return nil } -func (m *mongoGenericRepository[TDataModel, TEntity]) AddAll(ctx context.Context, entities []TEntity) error { +func (m *mongoGenericRepository[TDataModel, TEntity]) AddAll( + ctx context.Context, + entities []TEntity, +) error { for _, entity := range entities { err := m.Add(ctx, entity) if err != nil { @@ -102,9 +108,12 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) AddAll(ctx context.Context return nil } -func (m *mongoGenericRepository[TDataModel, TEntity]) GetById(ctx context.Context, id uuid.UUID) (TEntity, error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() +func (m *mongoGenericRepository[TDataModel, TEntity]) GetById( + ctx context.Context, + id uuid.UUID, +) (TEntity, error) { + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() collection := m.db.Database(m.databaseName).Collection(m.collectionName) if modelType == dataModelType { @@ -118,12 +127,18 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) GetById(ctx context.Contex if err == mongo.ErrNoDocuments { return *new(TEntity), customErrors.NewNotFoundErrorWrap( err, - fmt.Sprintf("can't find the entity with id %s into the database.", id.String()), + fmt.Sprintf( + "can't find the entity with id %s into the database.", + id.String(), + ), ) } return *new(TEntity), errors.WrapIf( err, - fmt.Sprintf("can't find the entity with id %s into the database.", id.String()), + fmt.Sprintf( + "can't find the entity with id %s into the database.", + id.String(), + ), ) } return model, nil @@ -148,12 +163,17 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) GetAll( ctx context.Context, listQuery *utils.ListQuery, ) (*utils.ListResult[TEntity], error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() collection := m.db.Database(m.databaseName).Collection(m.collectionName) if modelType == dataModelType { - result, err := mongodb.Paginate[TEntity](ctx, listQuery, collection, nil) + result, err := mongodb.Paginate[TEntity]( + ctx, + listQuery, + collection, + nil, + ) if err != nil { return nil, err } @@ -176,37 +196,49 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) Search( searchTerm string, listQuery *utils.ListQuery, ) (*utils.ListResult[TEntity], error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() collection := m.db.Database(m.databaseName).Collection(m.collectionName) if modelType == dataModelType { - fields := reflectionHelper.GetAllFields(typeMapper.GetTypeFromGeneric[TEntity]()) + fields := reflectionHelper.GetAllFields( + typeMapper.GetGenericTypeByT[TEntity](), + ) var a bson.A for _, field := range fields { if field.Type.Kind() != reflect.String { continue } name := strcase.ToLowerCamel(field.Name) - a = append(a, bson.D{{Key: name, Value: primitive.Regex{Pattern: searchTerm, Options: "gi"}}}) + a = append( + a, + bson.D{ + {Key: name, Value: primitive.Regex{Pattern: searchTerm}}, + }, + ) } filter := bson.D{ {Key: "$or", Value: a}, } - result, err := mongodb.Paginate[TEntity](ctx, listQuery, collection, filter) + result, err := mongodb.Paginate[TEntity]( + ctx, + listQuery, + collection, + filter, + ) if err != nil { return nil, err } return result, nil } else { - fields := reflectionHelper.GetAllFields(typeMapper.GetTypeFromGeneric[TDataModel]()) + fields := reflectionHelper.GetAllFields(typeMapper.GetGenericTypeByT[TDataModel]()) var a bson.A for _, field := range fields { if field.Type.Kind() != reflect.String { continue } name := strcase.ToLowerCamel(field.Name) - a = append(a, bson.D{{Key: name, Value: primitive.Regex{Pattern: searchTerm, Options: "gi"}}}) + a = append(a, bson.D{{Key: name, Value: primitive.Regex{Pattern: searchTerm}}}) } filter := bson.D{ {Key: "$or", Value: a}, @@ -227,8 +259,8 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) GetByFilter( ctx context.Context, filters map[string]interface{}, ) ([]TEntity, error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() collection := m.db.Database(m.databaseName).Collection(m.collectionName) // we could use also bson.D{} for filtering, it is also a map @@ -281,8 +313,8 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) FirstOrDefault( ctx context.Context, filters map[string]interface{}, ) (TEntity, error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() collection := m.db.Database(m.databaseName).Collection(m.collectionName) if modelType == dataModelType { @@ -315,9 +347,12 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) FirstOrDefault( } } -func (m *mongoGenericRepository[TDataModel, TEntity]) Update(ctx context.Context, entity TEntity) error { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() +func (m *mongoGenericRepository[TDataModel, TEntity]) Update( + ctx context.Context, + entity TEntity, +) error { + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() collection := m.db.Database(m.databaseName).Collection(m.collectionName) ops := options.FindOneAndUpdate() ops.SetReturnDocument(options.After) @@ -367,7 +402,10 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) Update(ctx context.Context return nil } -func (m *mongoGenericRepository[TDataModel, TEntity]) UpdateAll(ctx context.Context, entities []TEntity) error { +func (m *mongoGenericRepository[TDataModel, TEntity]) UpdateAll( + ctx context.Context, + entities []TEntity, +) error { for _, e := range entities { err := m.Update(ctx, e) if err != nil { @@ -378,7 +416,10 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) UpdateAll(ctx context.Cont return nil } -func (m *mongoGenericRepository[TDataModel, TEntity]) Delete(ctx context.Context, id uuid.UUID) error { +func (m *mongoGenericRepository[TDataModel, TEntity]) Delete( + ctx context.Context, + id uuid.UUID, +) error { collection := m.db.Database(m.databaseName).Collection(m.collectionName) if err := collection.FindOneAndDelete(ctx, bson.M{"_id": id.String()}).Err(); err != nil { @@ -393,8 +434,8 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) SkipTake( skip int, take int, ) ([]TEntity, error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() collection := m.db.Database(m.databaseName).Collection(m.collectionName) l := int64(take) s := int64(skip) @@ -436,7 +477,9 @@ func (m *mongoGenericRepository[TDataModel, TEntity]) SkipTake( } } -func (m *mongoGenericRepository[TDataModel, TEntity]) Count(ctx context.Context) int64 { +func (m *mongoGenericRepository[TDataModel, TEntity]) Count( + ctx context.Context, +) int64 { collection := m.db.Database(m.databaseName).Collection(m.collectionName) count, err := collection.CountDocuments(ctx, bson.M{}) if err != nil { diff --git a/internal/pkg/mongodb/repository/mongo_generic_repository_test.go b/internal/pkg/mongodb/repository/mongo_generic_repository_test.go index 7837d708..228949b6 100644 --- a/internal/pkg/mongodb/repository/mongo_generic_repository_test.go +++ b/internal/pkg/mongodb/repository/mongo_generic_repository_test.go @@ -2,28 +2,24 @@ package repository import ( "context" - "log" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data/specification" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mapper" - mongo2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/testcontainer/mongo" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/data" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + mongocontainer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/mongo" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/brianvoe/gofakeit/v6" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) -const ( - DatabaseName = "catalogs_write" - CollectionName = "products" -) - // Product is a domain_events entity type Product struct { ID string @@ -39,98 +35,121 @@ type ProductMongo struct { IsAvailable bool `json:"isAvailable" bson:"isAvailable"` } -func init() { - err := mapper.CreateMap[*ProductMongo, *Product]() - if err != nil { - log.Fatal(err) - } +type mongoGenericRepositoryTest struct { + suite.Suite + databaseName string + collectionName string + mongoClient *mongo.Client + productRepository data.GenericRepository[*ProductMongo] + productRepositoryWithDataModel data.GenericRepositoryWithDataModel[*ProductMongo, *Product] + products []*ProductMongo +} + +func TestMongoGenericRepository(t *testing.T) { + suite.Run( + t, + &mongoGenericRepositoryTest{ + databaseName: "catalogs_write", + collectionName: "products", + }, + ) +} + +func (c *mongoGenericRepositoryTest) SetupSuite() { + opts, err := mongocontainer.NewMongoTestContainers(defaultLogger.GetLogger()). + PopulateContainerOptions(context.Background(), c.T()) + c.Require().NoError(err) + + mongoClient, err := mongodb.NewMongoDB(opts) + c.Require().NoError(err) + c.mongoClient = mongoClient + + c.productRepository = NewGenericMongoRepository[*ProductMongo]( + mongoClient, + c.databaseName, + c.collectionName, + ) + c.productRepositoryWithDataModel = NewGenericMongoRepositoryWithDataModel[*ProductMongo, *Product]( + mongoClient, + c.databaseName, + c.collectionName, + ) + + err = mapper.CreateMap[*ProductMongo, *Product]() + c.Require().NoError(err) err = mapper.CreateMap[*Product, *ProductMongo]() - if err != nil { - log.Fatal(err) - } + c.Require().NoError(err) } -func Test_Add(t *testing.T) { +func (c *mongoGenericRepositoryTest) SetupTest() { + p, err := c.seedData(context.Background()) + c.Require().NoError(err) + c.products = p +} + +func (c *mongoGenericRepositoryTest) TearDownTest() { + err := c.cleanupMongoData() + c.Require().NoError(err) +} + +func (c *mongoGenericRepositoryTest) Test_Add() { ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) product := &ProductMongo{ - ID: uuid.NewV4(). - String(), // we generate id ourselves because auto generate mongo string id column with type _id is not an uuid - Name: "added_product", - Weight: 100, + ID: uuid.NewV4().String(), + Name: gofakeit.Name(), + Weight: gofakeit.Number(100, 1000), IsAvailable: true, } - err = repository.Add(ctx, product) - if err != nil { - t.Fatal(err) - } + err := c.productRepository.Add(ctx, product) + c.Require().NoError(err) id, err := uuid.FromString(product.ID) - if err != nil { - return - } + c.Require().NoError(err) - p, err := repository.GetById(ctx, id) - if err != nil { - return - } + p, err := c.productRepository.GetById(ctx, id) + c.Require().NoError(err) - assert.NotNil(t, p) - assert.Equal(t, product.ID, p.ID) + c.Assert().NotNil(p) + c.Assert().Equal(product.ID, p.ID) } -func Test_Add_With_Data_Model(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Add_With_Data_Model() { ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - product := &Product{ - ID: uuid.NewV4(). - String(), + product := &ProductMongo{ // we generate id ourselves because auto generate mongo string id column with type _id is not an uuid - Name: "added_product", - Weight: 100, + ID: uuid.NewV4().String(), + Name: gofakeit.Name(), + Weight: gofakeit.Number(100, 1000), IsAvailable: true, } - err = repository.Add(ctx, product) - if err != nil { - t.Fatal(err) - } + err := c.productRepository.Add(ctx, product) + c.Require().NoError(err) id, err := uuid.FromString(product.ID) - if err != nil { - return - } + c.Require().NoError(err) - p, err := repository.GetById(ctx, id) - if err != nil { - return - } + p, err := c.productRepository.GetById(ctx, id) + c.Require().NoError(err) - assert.NotNil(t, p) - assert.Equal(t, product.ID, p.ID) + c.Assert().NotNil(p) + c.Assert().Equal(product.ID, p.ID) } -func Test_Get_By_Id(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Get_By_Id() { ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - all, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - return - } + all, err := c.productRepository.GetAll(ctx, utils.NewListQuery(10, 1)) + c.Require().NoError(err) + p := all.Items[0] id, err := uuid.FromString(p.ID) + name := p.Name testCases := []struct { Name string @@ -138,7 +157,7 @@ func Test_Get_By_Id(t *testing.T) { ExpectResult *ProductMongo }{ { - Name: "ExistingProduct", + Name: name, ProductId: id, ExpectResult: p, }, @@ -149,12 +168,11 @@ func Test_Get_By_Id(t *testing.T) { }, } - for _, c := range testCases { - c := c - t.Run(c.Name, func(t *testing.T) { + for _, s := range testCases { + c.T().Run(s.Name, func(t *testing.T) { t.Parallel() - res, err := repository.GetById(ctx, c.ProductId) - if c.ExpectResult == nil { + res, err := c.productRepository.GetById(ctx, s.ProductId) + if s.ExpectResult == nil { assert.Error(t, err) assert.True(t, customErrors.IsNotFoundError(err)) assert.Nil(t, res) @@ -167,20 +185,18 @@ func Test_Get_By_Id(t *testing.T) { } } -func Test_Get_By_Id_With_Data_Model(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Get_By_Id_With_Data_Model() { ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - all, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - return - } + all, err := c.productRepositoryWithDataModel.GetAll( + ctx, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) p := all.Items[0] id, err := uuid.FromString(p.ID) + name := p.Name testCases := []struct { Name string @@ -188,7 +204,7 @@ func Test_Get_By_Id_With_Data_Model(t *testing.T) { ExpectResult *Product }{ { - Name: "ExistingProduct", + Name: name, ProductId: id, ExpectResult: p, }, @@ -199,12 +215,14 @@ func Test_Get_By_Id_With_Data_Model(t *testing.T) { }, } - for _, c := range testCases { - c := c - t.Run(c.Name, func(t *testing.T) { + for _, s := range testCases { + c.T().Run(s.Name, func(t *testing.T) { t.Parallel() - res, err := repository.GetById(ctx, c.ProductId) - if c.ExpectResult == nil { + res, err := c.productRepositoryWithDataModel.GetById( + ctx, + s.ProductId, + ) + if s.ExpectResult == nil { assert.Error(t, err) assert.True(t, customErrors.IsNotFoundError(err)) assert.Nil(t, res) @@ -217,359 +235,355 @@ func Test_Get_By_Id_With_Data_Model(t *testing.T) { } } -func Test_First_Or_Default(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_First_Or_Default() { ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - all, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - return - } + all, err := c.productRepository.GetAll(ctx, utils.NewListQuery(10, 1)) + c.Require().NoError(err) + p := all.Items[0] - single, err := repository.FirstOrDefault(ctx, map[string]interface{}{"_id": p.ID}) - if err != nil { - t.Fatal(err) - } - assert.NotNil(t, single) + single, err := c.productRepository.FirstOrDefault( + ctx, + map[string]interface{}{"_id": p.ID}, + ) + c.Require().NoError(err) + c.Assert().NotNil(single) } -func Test_First_Or_Default_With_Data_Model(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_First_Or_Default_With_Data_Model() { ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - all, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - return - } + all, err := c.productRepositoryWithDataModel.GetAll( + ctx, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) + p := all.Items[0] - single, err := repository.FirstOrDefault(ctx, map[string]interface{}{"_id": p.ID}) - if err != nil { - t.Fatal(err) - } - assert.NotNil(t, single) + single, err := c.productRepositoryWithDataModel.FirstOrDefault( + ctx, + map[string]interface{}{"_id": p.ID}, + ) + + c.Require().NoError(err) + c.Assert().NotNil(single) } -func Test_Get_All(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Get_All() { ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - models, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } + models, err := c.productRepository.GetAll(ctx, utils.NewListQuery(10, 1)) + c.Require().NoError(err) - assert.NotEmpty(t, models.Items) + c.Assert().NotEmpty(models.Items) } -func Test_Get_All_With_Data_Model(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Get_All_With_Data_Model() { ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - models, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } + models, err := c.productRepositoryWithDataModel.GetAll( + ctx, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) - assert.NotEmpty(t, models.Items) + c.Assert().NotEmpty(models.Items) } -func Test_Search(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Search() { ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - models, err := repository.Search(ctx, "seed_product1", utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } + models, err := c.productRepository.Search( + ctx, + c.products[0].Name, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) - assert.NotEmpty(t, models.Items) - assert.Equal(t, len(models.Items), 1) + c.Assert().NotEmpty(models.Items) + c.Assert().Equal(len(models.Items), 1) } -func Test_Search_With_Data_Model(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Search_With_Data_Model() { ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - models, err := repository.Search(ctx, "seed_product1", utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } + models, err := c.productRepositoryWithDataModel.Search( + ctx, + c.products[0].Name, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) - assert.NotEmpty(t, models.Items) - assert.Equal(t, len(models.Items), 1) + c.Assert().NotEmpty(models.Items) + c.Assert().Equal(len(models.Items), 1) } -func Test_GetByFilter(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_GetByFilter() { ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - models, err := repository.GetByFilter(ctx, map[string]interface{}{"name": "seed_product1"}) - if err != nil { - t.Fatal(err) - } + models, err := c.productRepository.GetByFilter( + ctx, + map[string]interface{}{"name": c.products[0].Name}, + ) + c.Require().NoError(err) - assert.NotEmpty(t, models) - assert.Equal(t, len(models), 1) + c.Assert().NotEmpty(models) + c.Assert().Equal(len(models), 1) } -func Test_GetByFilter_With_Data_Model(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_GetByFilter_With_Data_Model() { ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - models, err := repository.GetByFilter(ctx, map[string]interface{}{"name": "seed_product1"}) - if err != nil { - t.Fatal(err) - } + models, err := c.productRepositoryWithDataModel.GetByFilter( + ctx, + map[string]interface{}{"name": c.products[0].Name}, + ) + c.Require().NoError(err) - assert.NotEmpty(t, models) - assert.Equal(t, len(models), 1) + c.Assert().NotEmpty(models) + c.Assert().Equal(len(models), 1) } -func Test_Update(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Update() { ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } + products, err := c.productRepository.GetAll(ctx, utils.NewListQuery(10, 1)) + c.Require().NoError(err) + product := products.Items[0] product.Name = "product2_updated" - err = repository.Update(ctx, product) - if err != nil { - t.Fatal(err) - } + err = c.productRepository.Update(ctx, product) + c.Require().NoError(err) id, err := uuid.FromString(product.ID) - if err != nil { - t.Fatal(err) - } + c.Require().NoError(err) - single, err := repository.GetById(ctx, id) - if err != nil { - t.Fatal(err) - } - assert.NotNil(t, single) - assert.Equal(t, "product2_updated", single.Name) + single, err := c.productRepository.GetById(ctx, id) + c.Require().NoError(err) + + c.Assert().NotNil(single) + c.Assert().Equal("product2_updated", single.Name) } -func Test_Update_With_Data_Model(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Update_With_Data_Model() { ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } + products, err := c.productRepositoryWithDataModel.GetAll( + ctx, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) + product := products.Items[0] product.Name = "product2_updated" - err = repository.Update(ctx, product) - if err != nil { - t.Fatal(err) - } + err = c.productRepositoryWithDataModel.Update(ctx, product) + c.Require().NoError(err) id, err := uuid.FromString(product.ID) - if err != nil { - t.Fatal(err) - } + c.Require().NoError(err) - single, err := repository.GetById(ctx, id) - if err != nil { - t.Fatal(err) - } - assert.NotNil(t, single) - assert.Equal(t, "product2_updated", single.Name) -} - -func Test_Delete(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - - products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } - product := products.Items[0] - - id, err := uuid.FromString(product.ID) - if err != nil { - t.Fatal(err) - } + single, err := c.productRepositoryWithDataModel.GetById(ctx, id) + c.Require().NoError(err) - err = repository.Delete(ctx, id) - if err != nil { - return - } - - single, err := repository.GetById(ctx, id) - assert.Nil(t, single) + c.Assert().NotNil(single) + c.Assert().Equal("product2_updated", single.Name) } -func Test_Delete_With_Data_Model(t *testing.T) { +func (c *mongoGenericRepositoryTest) Test_Delete() { ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) - if err != nil { - t.Fatal(err) - } + products, err := c.productRepository.GetAll(ctx, utils.NewListQuery(10, 1)) + c.Require().NoError(err) + product := products.Items[0] id, err := uuid.FromString(product.ID) - if err != nil { - t.Fatal(err) - } - - err = repository.Delete(ctx, id) - if err != nil { - return - } - - single, err := repository.GetById(ctx, id) - assert.Nil(t, single) -} - -func Test_Count(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - - count := repository.Count(ctx) - assert.Equal(t, count, int64(2)) -} - -func Test_Count_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - - count := repository.Count(ctx) - assert.Equal(t, count, int64(2)) -} + c.Require().NoError(err) -func Test_Skip_Take(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - if err != nil { - t.Fatal(err) - } + err = c.productRepository.Delete(ctx, id) + c.Require().NoError(err) - entities, err := repository.SkipTake(ctx, 1, 1) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, len(entities), 1) + single, err := c.productRepository.GetById(ctx, id) + c.Assert().Nil(single) } -func Test_Skip_Take_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) - } - - entities, err := repository.SkipTake(ctx, 1, 1) - if err != nil { - t.Fatal(err) - } +//func Test_Delete_With_Data_Model(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) +// +// products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) +// if err != nil { +// t.Fatal(err) +// } +// product := products.Items[0] +// +// id, err := uuid.FromString(product.ID) +// if err != nil { +// t.Fatal(err) +// } +// +// err = repository.Delete(ctx, id) +// if err != nil { +// return +// } +// +// single, err := repository.GetById(ctx, id) +// assert.Nil(t, single) +//} +// +//func Test_Count(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericMongoRepository(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// count := repository.Count(ctx) +// assert.Equal(t, count, int64(2)) +//} +// +//func Test_Count_With_Data_Model(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// count := repository.Count(ctx) +// assert.Equal(t, count, int64(2)) +//} +// +//func Test_Skip_Take(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericMongoRepository(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// entities, err := repository.SkipTake(ctx, 1, 1) +// if err != nil { +// t.Fatal(err) +// } +// +// assert.Equal(t, len(entities), 1) +//} +// +//func Test_Skip_Take_With_Data_Model(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// entities, err := repository.SkipTake(ctx, 1, 1) +// if err != nil { +// t.Fatal(err) +// } +// +// assert.Equal(t, len(entities), 1) +//} +// +//func Test_Find(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericMongoRepository(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// entities, err := repository.Find( +// ctx, +// specification.And( +// specification.Equal("is_available", true), +// specification.Equal("name", "seed_product1"), +// ), +// ) +// if err != nil { +// return +// } +// assert.Equal(t, len(entities), 1) +//} +// +//func Test_Find_With_Data_Model(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// entities, err := repository.Find( +// ctx, +// specification.And( +// specification.Equal("is_available", true), +// specification.Equal("name", "seed_product1"), +// ), +// ) +// if err != nil { +// return +// } +// assert.Equal(t, len(entities), 1) +//} +// +//func setupGenericMongoRepositoryWithDataModel( +// ctx context.Context, +// t *testing.T, +//) (data.GenericRepositoryWithDataModel[*ProductMongo, *Product], error) { +// db, err := mongocontainer.NewMongoTestContainers(defaultLogger.GetLogger()). +// Start(ctx, t) +// if err != nil { +// return nil, err +// } +// +// err = seedData(ctx, db) +// if err != nil { +// return nil, err +// } +// +// return NewGenericMongoRepositoryWithDataModel[*ProductMongo, *Product]( +// db, +// DatabaseName, +// CollectionName, +// ), nil +//} + +func (c *mongoGenericRepositoryTest) cleanupMongoData() error { + collections := []string{c.collectionName} + err := cleanupCollections( + c.mongoClient, + collections, + c.databaseName, + ) - assert.Equal(t, len(entities), 1) + return err } -func Test_Find(t *testing.T) { +func cleanupCollections( + db *mongo.Client, + collections []string, + databaseName string, +) error { + database := db.Database(databaseName) ctx := context.Background() - repository, err := setupGenericMongoRepository(ctx, t) - if err != nil { - t.Fatal(err) - } - entities, err := repository.Find( - ctx, - specification.And(specification.Equal("is_available", true), specification.Equal("name", "seed_product1")), - ) - if err != nil { - return - } - assert.Equal(t, len(entities), 1) -} + // Iterate over the collections and delete all collections + for _, collection := range collections { + collection := database.Collection(collection) -func Test_Find_With_Data_Model(t *testing.T) { - ctx := context.Background() - repository, err := setupGenericMongoRepositoryWithDataModel(ctx, t) - if err != nil { - t.Fatal(err) + err := collection.Drop(ctx) + if err != nil { + return err + } } - entities, err := repository.Find( - ctx, - specification.And(specification.Equal("is_available", true), specification.Equal("name", "seed_product1")), - ) - if err != nil { - return - } - assert.Equal(t, len(entities), 1) + return nil } -func setupGenericMongoRepositoryWithDataModel( +func (c *mongoGenericRepositoryTest) seedData( ctx context.Context, - t *testing.T, -) (data.GenericRepositoryWithDataModel[*ProductMongo, *Product], error) { - defaultLogger.SetupDefaultLogger() - - db, err := mongo2.NewMongoTestContainers(defaultLogger.Logger).Start(ctx, t) - if err != nil { - return nil, err - } - - err = seedAndMigration(ctx, db) - if err != nil { - return nil, err - } - - return NewGenericMongoRepositoryWithDataModel[*ProductMongo, *Product](db, DatabaseName, CollectionName), nil -} - -func setupGenericMongoRepository(ctx context.Context, t *testing.T) (data.GenericRepository[*ProductMongo], error) { - defaultLogger.SetupDefaultLogger() - - db, err := mongo2.NewMongoTestContainers(defaultLogger.Logger).Start(ctx, t) - if err != nil { - return nil, err - } - - err = seedAndMigration(ctx, db) - if err != nil { - return nil, err - } - - return NewGenericMongoRepository[*ProductMongo](db, DatabaseName, CollectionName), nil -} - -func seedAndMigration(ctx context.Context, db *mongo.Client) error { +) ([]*ProductMongo, error) { seedProducts := []*ProductMongo{ { ID: uuid.NewV4(). @@ -595,11 +609,12 @@ func seedAndMigration(ctx context.Context, db *mongo.Client) error { data[i] = v } - collection := db.Database(DatabaseName).Collection(CollectionName) + collection := c.mongoClient.Database(c.databaseName). + Collection(c.collectionName) _, err := collection.InsertMany(ctx, data, &options.InsertManyOptions{}) if err != nil { - return err + return nil, err } - return nil + return seedProducts, nil } diff --git a/internal/pkg/otel/config/open_telemtry_options.go b/internal/pkg/otel/config/open_telemtry_options.go deleted file mode 100644 index c1e457f7..00000000 --- a/internal/pkg/otel/config/open_telemtry_options.go +++ /dev/null @@ -1,43 +0,0 @@ -package config - -import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" - - "github.com/iancoleman/strcase" -) - -var optionName = strcase.ToLowerCamel(typeMapper.GetTypeNameByT[OpenTelemetryOptions]()) - -type OpenTelemetryOptions struct { - Enabled bool `mapstructure:"enabled"` - ServiceName string `mapstructure:"serviceName"` - InstrumentationName string `mapstructure:"instrumentationName"` - Id int64 `mapstructure:"id"` - AlwaysOnSampler bool `mapstructure:"alwaysOnSampler"` - JaegerExporterOptions *JaegerExporterOptions `mapstructure:"jaegerExporterOptions"` - ZipkinExporterOptions *ZipkinExporterOptions `mapstructure:"zipkinExporterOptions"` - OTelMetricsOptions *OTelMetricsOptions `mapstructure:"otelMetricsOptions"` - UseStdout bool `mapstructure:"useStdout"` -} - -type JaegerExporterOptions struct { - AgentHost string `mapstructure:"agentHost"` - AgentPort string `mapstructure:"agentPort"` -} - -type ZipkinExporterOptions struct { - Url string `mapstructure:"url"` -} - -type OTelMetricsOptions struct { - Host string `mapstructure:"host"` - Port string `mapstructure:"port"` - Name string `mapstructure:"name"` - MetricsRoutePath string `mapstructure:"metricsRoutePath"` -} - -func ProvideOtelConfig(environment environemnt.Environment) (*OpenTelemetryOptions, error) { - return config.BindConfigKey[*OpenTelemetryOptions](optionName, environment) -} diff --git a/internal/pkg/otel/tracing/consts.go b/internal/pkg/otel/constants/consts.go similarity index 77% rename from internal/pkg/otel/tracing/consts.go rename to internal/pkg/otel/constants/consts.go index 251219d2..ab77589d 100644 --- a/internal/pkg/otel/tracing/consts.go +++ b/internal/pkg/otel/constants/consts.go @@ -1,4 +1,4 @@ -package tracing +package constants const ( TraceId = "general.trace-id" @@ -6,5 +6,4 @@ const ( ParentSpanId = "general.parent-span-id" Traceparent = "general.traceparent" Timestamp = "general.timestamp" - ErrorMessage = "general.error_message" ) diff --git a/internal/pkg/otel/constants/telemetrytags/telemetrytags.go b/internal/pkg/otel/constants/telemetrytags/telemetrytags.go new file mode 100644 index 00000000..a60edbd5 --- /dev/null +++ b/internal/pkg/otel/constants/telemetrytags/telemetrytags.go @@ -0,0 +1,133 @@ +package telemetrytags + +type app struct { + DefaultSourceName string + Consumer string + EventHandler string + Subscription string + Stream string + Partition string + Request string + RequestName string + RequestType string + RequestResultName string + RequestResult string + Command string + CommandName string + CommandType string + CommandResult string + CommandResultName string + Query string + QueryName string + QueryType string + QueryResult string + QueryResultName string + Event string + EventName string + EventType string + EventResult string + EventResultName string + Message string + MessageName string + MessageType string +} + +// https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/messaging/ +type messaging struct { + System string + Destination string + DestinationKind string + Url string + MessageId string + ConversationId string + CorrelationId string + CausationId string + Operation string +} + +type exceptions struct { + EventName string + Type string + Message string + Stacktrace string +} + +type general struct{} + +// https://opentelemetry.io/docs/specs/otel/trace/semantic_conventions/rpc/ +type grpc struct{} + +type netHttp struct { + Transport string + PeerIp string + PeerPort string + PeerName string + HostIp string + HostPort string + HostName string +} + +var App = app{ + DefaultSourceName: "app", + Consumer: "app.consumer", + EventHandler: "app.event-handler", + Subscription: "app.subscription", + Stream: "app.stream", + Partition: "app.partition", + Request: "app.request", + RequestName: "app.request_name", + RequestType: "app.request_type", + RequestResultName: "app.request_result_name", + RequestResult: "app.request_result", + Command: "app.command", + CommandName: "app.command_name", + CommandType: "app.command_type", + CommandResult: "app.command_result", + CommandResultName: "app.command_result_name", + Query: "app.query", + QueryName: "app.query_name", + QueryType: "app.query_type", + QueryResult: "app.query_result", + QueryResultName: "app.query_result_name", + Event: "app.event", + EventName: "app.event_name", + EventType: "app.event_type", + EventResult: "app.event_result", + EventResultName: "app.event_result_name", + Message: "app.message", + MessageName: "app.message_name", + MessageType: "app.message_type", +} + +var Exceptions = exceptions{ + EventName: "exception", + Type: "exception.type", + Message: "exception.message", + Stacktrace: "exception.stacktrace", +} + +var General = general{} + +var Grpc = grpc{} + +var Messaging = messaging{ + System: "messaging.system", + Destination: "messaging.destination", + DestinationKind: "messaging.destination_kind", + Url: "messaging.url", + MessageId: "messaging.message_id", + ConversationId: "messaging.conversation_id", + CorrelationId: "messaging.correlation_id", + CausationId: "messaging.causation_id", + Operation: "messaging.operation", +} + +var NetHttp = netHttp{ + Transport: "net.transport", + PeerIp: "net.peer.ip", + PeerPort: "net.peer.port", + PeerName: "net.peer.name", + HostIp: "net.host.ip", + HostPort: "net.host.port", + HostName: "net.host.name", +} diff --git a/internal/pkg/otel/constants/tracing/components/components.go b/internal/pkg/otel/constants/tracing/components/components.go new file mode 100644 index 00000000..3b1cd9d2 --- /dev/null +++ b/internal/pkg/otel/constants/tracing/components/components.go @@ -0,0 +1,8 @@ +package components + +const ( + CommandHandler = "command_handler" + RequestHandler = "request_handler" + QueryHandler = "query_handler" + EventHandler = "event_handler" +) diff --git a/internal/pkg/otel/constants/tracing/operations/operations.go b/internal/pkg/otel/constants/tracing/operations/operations.go new file mode 100644 index 00000000..4e5d524c --- /dev/null +++ b/internal/pkg/otel/constants/tracing/operations/operations.go @@ -0,0 +1,7 @@ +package operations + +const ( + Delete = "delete" + Read = "read" + Add = "add" +) diff --git a/internal/pkg/otel/metrics/custom_meter.go b/internal/pkg/otel/metrics/custom_meter.go new file mode 100644 index 00000000..47d2cbf5 --- /dev/null +++ b/internal/pkg/otel/metrics/custom_meter.go @@ -0,0 +1,22 @@ +package metrics + +import ( + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" +) + +type AppMetrics interface { + metric.Meter +} + +type appMetrics struct { + metric.Meter +} + +func NewAppMeter(name string, opts ...metric.MeterOption) AppMetrics { + // Meter can be a global/package variable. + // https://github.com/open-telemetry/opentelemetry-go/blob/46f2ce5ca6adaa264c37cdbba251c9184a06ed7f/metric.go#LL35C6-L35C11 + meter := otel.Meter(name, opts...) + + return &appMetrics{Meter: meter} +} diff --git a/internal/pkg/otel/metrics/mediatr/pipelines/config.go b/internal/pkg/otel/metrics/mediatr/pipelines/config.go new file mode 100644 index 00000000..9be241ff --- /dev/null +++ b/internal/pkg/otel/metrics/mediatr/pipelines/config.go @@ -0,0 +1,43 @@ +package pipelines + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" +) + +type config struct { + logger logger.Logger + serviceName string +} + +var defaultConfig = &config{ + serviceName: "app", + logger: defaultLogger.GetLogger(), +} + +// Option specifies instrumentation configuration options. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +func WithServiceName(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.serviceName != "" { + cfg.serviceName = v + } + }) +} + +func WithLogger(l logger.Logger) Option { + return optionFunc(func(cfg *config) { + if cfg.logger != nil { + cfg.logger = l + } + }) +} diff --git a/internal/pkg/otel/metrics/mediatr/pipelines/mediator_metrics_pipeline.go b/internal/pkg/otel/metrics/mediatr/pipelines/mediator_metrics_pipeline.go new file mode 100644 index 00000000..51f70006 --- /dev/null +++ b/internal/pkg/otel/metrics/mediatr/pipelines/mediator_metrics_pipeline.go @@ -0,0 +1,167 @@ +package pipelines + +import ( + "context" + "fmt" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/events" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/constants/telemetrytags" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics" + customAttribute "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/mehdihadeli/go-mediatr" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" +) + +type mediatorMetricsPipeline struct { + config *config + meter metrics.AppMetrics +} + +func NewMediatorMetricsPipeline( + appMetrics metrics.AppMetrics, + opts ...Option, +) mediatr.PipelineBehavior { + cfg := defaultConfig + for _, opt := range opts { + opt.apply(cfg) + } + + return &mediatorMetricsPipeline{ + config: cfg, + meter: appMetrics, + } +} + +func (r *mediatorMetricsPipeline) Handle( + ctx context.Context, + request interface{}, + next mediatr.RequestHandlerFunc, +) (interface{}, error) { + payloadSnakeTypeName := typemapper.GetSnakeTypeName(request) + typeName := typemapper.GetTypeName(request) + + nameTag := telemetrytags.App.RequestName + typeNameTag := telemetrytags.App.RequestType + payloadTag := telemetrytags.App.Request + resultSnakeTypeNameTag := telemetrytags.App.RequestResultName + resultTag := telemetrytags.App.RequestResult + + if cqrs.IsCommand(request) { + nameTag = telemetrytags.App.CommandName + typeNameTag = telemetrytags.App.CommandType + payloadTag = telemetrytags.App.Command + resultSnakeTypeNameTag = telemetrytags.App.CommandResultName + resultTag = telemetrytags.App.CommandResult + + } else if cqrs.IsQuery(request) { + nameTag = telemetrytags.App.QueryName + typeNameTag = telemetrytags.App.QueryType + payloadTag = telemetrytags.App.Query + resultSnakeTypeNameTag = telemetrytags.App.QueryResultName + resultTag = telemetrytags.App.QueryResult + + } else if events.IsEvent(request) { + nameTag = telemetrytags.App.EventName + typeNameTag = telemetrytags.App.EventType + payloadTag = telemetrytags.App.Event + resultSnakeTypeNameTag = telemetrytags.App.EventResultName + resultTag = telemetrytags.App.EventResult + } + + successRequestsCounter, err := r.meter.Int64Counter( + fmt.Sprintf("%s.success_total", payloadSnakeTypeName), + metric.WithUnit("count"), + metric.WithDescription( + fmt.Sprintf( + "Measures the number of success '%s' (%s)", + payloadSnakeTypeName, + typeName, + ), + ), + ) + if err != nil { + return nil, err + } + + failedRequestsCounter, err := r.meter.Int64Counter( + fmt.Sprintf("%s.failed_total", payloadSnakeTypeName), + metric.WithUnit("count"), + metric.WithDescription( + fmt.Sprintf( + "Measures the number of failed '%s' (%s)", + payloadSnakeTypeName, + typeName, + ), + ), + ) + if err != nil { + return nil, err + } + + totalRequestsCounter, err := r.meter.Int64Counter( + fmt.Sprintf("%s.total", payloadSnakeTypeName), + metric.WithUnit("count"), + metric.WithDescription( + fmt.Sprintf( + "Measures the total number of '%s' (%s)", + payloadSnakeTypeName, + typeName, + ), + ), + ) + if err != nil { + return nil, err + } + + durationValueRecorder, err := r.meter.Int64Histogram( + fmt.Sprintf("%s.duration", payloadSnakeTypeName), + metric.WithUnit("ms"), + metric.WithDescription( + fmt.Sprintf( + "Measures the duration of '%s' (%s)", + payloadSnakeTypeName, + typeName, + ), + ), + ) + if err != nil { + return nil, err + } + + // Start recording the duration + startTime := time.Now() + + response, err := next(ctx) + + // Calculate the duration + duration := time.Since(startTime).Milliseconds() + + // response will be nil if we have an error + responseSnakeName := typemapper.GetSnakeTypeName(response) + + opt := metric.WithAttributes( + attribute.String(nameTag, payloadSnakeTypeName), + attribute.String(typeNameTag, typeName), + customAttribute.Object(payloadTag, request), + attribute.String(resultSnakeTypeNameTag, responseSnakeName), + customAttribute.Object(resultTag, response), + ) + + // Record metrics + totalRequestsCounter.Add(ctx, 1, opt) + + if err == nil { + successRequestsCounter.Add(ctx, 1, opt) + } else { + failedRequestsCounter.Add(ctx, 1, opt) + } + + durationValueRecorder.Record(ctx, duration, opt) + + return response, err +} diff --git a/internal/pkg/otel/metrics/messaging/pipeline/config.go b/internal/pkg/otel/metrics/messaging/pipeline/config.go new file mode 100644 index 00000000..9be241ff --- /dev/null +++ b/internal/pkg/otel/metrics/messaging/pipeline/config.go @@ -0,0 +1,43 @@ +package pipelines + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" +) + +type config struct { + logger logger.Logger + serviceName string +} + +var defaultConfig = &config{ + serviceName: "app", + logger: defaultLogger.GetLogger(), +} + +// Option specifies instrumentation configuration options. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +func WithServiceName(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.serviceName != "" { + cfg.serviceName = v + } + }) +} + +func WithLogger(l logger.Logger) Option { + return optionFunc(func(cfg *config) { + if cfg.logger != nil { + cfg.logger = l + } + }) +} diff --git a/internal/pkg/otel/metrics/messaging/pipeline/messaging_metrics_pipeline.go b/internal/pkg/otel/metrics/messaging/pipeline/messaging_metrics_pipeline.go new file mode 100644 index 00000000..294473e7 --- /dev/null +++ b/internal/pkg/otel/metrics/messaging/pipeline/messaging_metrics_pipeline.go @@ -0,0 +1,134 @@ +package pipelines + +import ( + "context" + "fmt" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/pipeline" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/constants/telemetrytags" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics" + attribute2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + + "github.com/iancoleman/strcase" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" +) + +type messagingMetricsPipeline struct { + config *config + meter metrics.AppMetrics +} + +func NewMessagingMetricsPipeline( + appMetrics metrics.AppMetrics, + opts ...Option, +) pipeline.ConsumerPipeline { + cfg := defaultConfig + for _, opt := range opts { + opt.apply(cfg) + } + + return &messagingMetricsPipeline{ + config: cfg, + meter: appMetrics, + } +} + +func (m *messagingMetricsPipeline) Handle( + ctx context.Context, + consumerContext types2.MessageConsumeContext, + next pipeline.ConsumerHandlerFunc, +) error { + message := consumerContext.Message() + messageTypeName := message.GetMessageTypeName() + snakeTypeName := strcase.ToSnake(messageTypeName) + + successRequestsCounter, err := m.meter.Int64Counter( + fmt.Sprintf("%s.success_total", snakeTypeName), + metric.WithUnit("count"), + metric.WithDescription( + fmt.Sprintf( + "Measures the number of success '%s' (%s)", + snakeTypeName, + messageTypeName, + ), + ), + ) + if err != nil { + return err + } + + failedRequestsCounter, err := m.meter.Int64Counter( + fmt.Sprintf("%s.failed_total", snakeTypeName), + metric.WithUnit("count"), + metric.WithDescription( + fmt.Sprintf( + "Measures the number of failed '%s' (%s)", + snakeTypeName, + messageTypeName, + ), + ), + ) + if err != nil { + return err + } + + totalRequestsCounter, err := m.meter.Int64Counter( + fmt.Sprintf("%s.total", snakeTypeName), + metric.WithUnit("count"), + metric.WithDescription( + fmt.Sprintf( + "Measures the total number of '%s' (%s)", + snakeTypeName, + messageTypeName, + ), + ), + ) + if err != nil { + return err + } + + durationValueRecorder, err := m.meter.Int64Histogram( + fmt.Sprintf("%s.duration", snakeTypeName), + metric.WithUnit("ms"), + metric.WithDescription( + fmt.Sprintf( + "Measures the duration of '%s' (%s)", + snakeTypeName, + messageTypeName, + ), + ), + ) + if err != nil { + return err + } + + // Start recording the duration + startTime := time.Now() + + err = next(ctx) + + // Calculate the duration + duration := time.Since(startTime).Milliseconds() + + opt := metric.WithAttributes( + attribute.String(telemetrytags.App.MessageType, messageTypeName), + attribute.String(telemetrytags.App.MessageName, snakeTypeName), + attribute2.Object(telemetrytags.App.Message, message), + ) + + // Record metrics + totalRequestsCounter.Add(ctx, 1, opt) + + if err == nil { + successRequestsCounter.Add(ctx, 1, opt) + } else { + failedRequestsCounter.Add(ctx, 1, opt) + } + + durationValueRecorder.Record(ctx, duration, opt) + + return nil +} diff --git a/internal/pkg/otel/metrics/metrics.go b/internal/pkg/otel/metrics/metrics.go index 9534b1ec..5087d89a 100644 --- a/internal/pkg/otel/metrics/metrics.go +++ b/internal/pkg/otel/metrics/metrics.go @@ -1,81 +1,310 @@ package metrics +// https://github.com/riferrei/otel-with-golang/blob/main/main.go +// https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go +// https://opentelemetry.io/docs/instrumentation/go/manual/#metrics + import ( "context" + "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "emperror.dev/errors" "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/samber/lo" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/prometheus" - api "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) type OtelMetrics struct { - Config *config.OpenTelemetryOptions - Logger logger.Logger - Meter api.Meter - Echo *echo.Echo + config *MetricsOptions + logger logger.Logger + appMetrics AppMetrics + environment environment.Environment + provider *metric.MeterProvider } // NewOtelMetrics adds otel metrics -// https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go func NewOtelMetrics( - config *config.OpenTelemetryOptions, + config *MetricsOptions, logger logger.Logger, + environment environment.Environment, ) (*OtelMetrics, error) { if config == nil { return nil, errors.New("metrics config can't be nil") } - e := echo.New() - e.HideBanner = false + otelMetrics := &OtelMetrics{ + config: config, + logger: logger, + environment: environment, + } + + resource, err := otelMetrics.newResource() + if err != nil { + return nil, errors.WrapIf(err, "failed to create resource") + } + + appMetrics, err := otelMetrics.initMetrics(resource) + if err != nil { + return nil, err + } + + otelMetrics.appMetrics = appMetrics + + return otelMetrics, nil +} + +func (o *OtelMetrics) Shutdown(ctx context.Context) error { + return o.provider.Shutdown(ctx) +} + +func (o *OtelMetrics) newResource() (*resource.Resource, error) { + // https://github.com/uptrace/uptrace-go/blob/master/example/otlp-traces/main.go#L49C1-L56C5 + resource, err := resource.New( + context.Background(), + resource.WithFromEnv(), + resource.WithTelemetrySDK(), + resource.WithHost(), + resource.WithOS(), + resource.WithSchemaURL(semconv.SchemaURL), + resource.WithAttributes( + semconv.ServiceName(o.config.ServiceName), + semconv.ServiceVersion(o.config.Version), + attribute.String("environment", o.environment.GetEnvironmentName()), + semconv.TelemetrySDKVersionKey.String("v1.21.0"), // semconv version + semconv.TelemetrySDKLanguageGo, + )) - // The exporter embeds a default OpenTelemetry Reader and - // implements prometheus.Collector, allowing it to be used as - // both a Reader and Collector. - exporter, err := prometheus.New() + return resource, err +} + +func (o *OtelMetrics) initMetrics( + resource *resource.Resource, +) (AppMetrics, error) { + metricsExporter, err := o.configExporters() if err != nil { - logger.Fatal(err) + return nil, err } - provider := metric.NewMeterProvider(metric.WithReader(exporter)) - meter := provider.Meter( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/metrics", + + batchExporters := lo.Map( + metricsExporter, + func(item metric.Reader, index int) metric.Option { + return metric.WithReader(item) + }, ) - return &OtelMetrics{Config: config, Meter: meter, Logger: logger, Echo: e}, nil + // https://opentelemetry.io/docs/instrumentation/go/exporting_data/#resources + // Resources are a special type of attribute that apply to all spans generated by a process + opts := append( + batchExporters, + metric.WithResource(resource), + ) + + // otel library collects metrics and send this metrics to some exporter like console or prometheus + provider := metric.NewMeterProvider(opts...) + + // Register our MeterProvider as the global so any imported + // instrumentation in the future will default to using it. + otel.SetMeterProvider(provider) + o.provider = provider + + appMeter := NewAppMeter(o.config.InstrumentationName) + + return appMeter, nil } -func (o *OtelMetrics) Run() error { - o.Echo.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{ - StackSize: 1 << 10, // 1 KB - DisablePrintStack: true, - DisableStackAll: true, - })) +func (o *OtelMetrics) configExporters() ([]metric.Reader, error) { + ctx := context.Background() - var metricsPath string - if o.Config.OTelMetricsOptions.MetricsRoutePath == "" { - metricsPath = "/metrics" - } else { - metricsPath = o.Config.OTelMetricsOptions.MetricsRoutePath + var exporters []metric.Reader + + // use some otel collector endpoints + metricOpts := []otlpmetricgrpc.Option{ + otlpmetricgrpc.WithTimeout(5 * time.Second), + otlpmetricgrpc.WithInsecure(), } - o.Echo.GET(metricsPath, echo.WrapHandler(promhttp.Handler())) - o.Logger.Infof("serving metrics at localhost:%s/metrics", o.Config.OTelMetricsOptions.Port) - err := o.Echo.Start(o.Config.OTelMetricsOptions.Port) + if !o.config.UseOTLP { //nolint:nestif + + if o.config.UseStdout { + console, err := stdoutmetric.New() + if err != nil { + return nil, errors.WrapIf( + err, + "error creating console exporter", + ) + } + + consoleMetricExporter := metric.NewPeriodicReader( + console, + // Default is 1m. Set to 3s for demonstrative purposes. + metric.WithInterval(3*time.Second)) + + exporters = append(exporters, consoleMetricExporter) + } + + if o.config.ElasticApmExporterOptions != nil { + // https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html + // https://www.elastic.co/guide/en/apm/guide/current/open-telemetry-direct.html#instrument-apps-otel + // https://github.com/anilsenay/go-opentelemetry-examples/blob/elastic/cmd/main.go#L35 + metricOpts = append( + metricOpts, + otlpmetricgrpc.WithEndpoint( + o.config.ElasticApmExporterOptions.OTLPEndpoint, + ), + otlpmetricgrpc.WithHeaders( + o.config.ElasticApmExporterOptions.OTLPHeaders, + ), + ) + + // send otel traces to jaeger builtin collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + exporter, err := otlpmetricgrpc.New(ctx, metricOpts...) + if err != nil { + return nil, errors.WrapIf( + err, + "failed to create otlpmetric exporter for elastic-apm", + ) + } + + elasticApmExporter := metric.NewPeriodicReader( + exporter, + // Default is 1m. Set to 3s for demonstrative purposes. + metric.WithInterval(3*time.Second)) + + exporters = append(exporters, elasticApmExporter) + } + + if o.config.UptraceExporterOptions != nil { + // https://github.com/uptrace/uptrace-go/blob/master/example/otlp-traces/main.go#L49C1-L56C5 + // https://uptrace.dev/get/opentelemetry-go.html#exporting-traces + // https://uptrace.dev/get/opentelemetry-go.html#exporting-metrics + metricOpts = append( + metricOpts, + otlpmetricgrpc.WithEndpoint( + o.config.UptraceExporterOptions.OTLPEndpoint, + ), + otlpmetricgrpc.WithHeaders( + o.config.UptraceExporterOptions.OTLPHeaders, + ), + ) + + // send otel traces to jaeger builtin collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + exporter, err := otlpmetricgrpc.New(ctx, metricOpts...) + if err != nil { + return nil, errors.WrapIf( + err, + "failed to create otlpmetric exporter for uptrace", + ) + } + + uptraceExporter := metric.NewPeriodicReader( + exporter, + // Default is 1m. Set to 3s for demonstrative purposes. + metric.WithInterval(3*time.Second)) + + exporters = append(exporters, uptraceExporter) + } + if o.config.SignozExporterOptions != nil { + // https://signoz.io/docs/instrumentation/golang/#instrumentation-of-a-sample-golang-application + // https://signoz.io/blog/distributed-tracing-golang/ + metricOpts = append( + metricOpts, + otlpmetricgrpc.WithEndpoint( + o.config.SignozExporterOptions.OTLPEndpoint, + ), + otlpmetricgrpc.WithHeaders( + o.config.SignozExporterOptions.OTLPHeaders, + ), + ) + + // send otel traces to jaeger builtin collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + exporter, err := otlpmetricgrpc.New(ctx, metricOpts...) + if err != nil { + return nil, errors.WrapIf( + err, + "failed to create otlpmetric exporter for signoz", + ) + } - return err + signozExporter := metric.NewPeriodicReader( + exporter, + // Default is 1m. Set to 3s for demonstrative purposes. + metric.WithInterval(3*time.Second)) + + exporters = append(exporters, signozExporter) + } else { + // https://prometheus.io/docs/prometheus/latest/getting_started/ + // https://prometheus.io/docs/guides/go-application/ + // prometheus exporter will collect otel metrics in prometheus registry + // all prometheus exporters will add to a singleton `prometheus.DefaultRegisterer` registry in newConfig method and this registry will use via `promhttp.Handler` through http endpoint on `/metrics` and calls `Collect` on prometheus Reader interface inner signature prometheus.DefaultRegisterer + prometheusExporter, err := prometheus.New() + if err != nil { + return nil, errors.WrapIf( + err, + "error creating prometheus exporter", + ) + } + exporters = append(exporters, prometheusExporter) + } + } else { + for _, oltpProvider := range o.config.OTLPProviders { + if !oltpProvider.Enabled { + continue + } + + metricOpts = append(metricOpts, otlpmetricgrpc.WithEndpoint(oltpProvider.OTLPEndpoint), otlpmetricgrpc.WithHeaders(oltpProvider.OTLPHeaders)) + + // send otel metrics to an otel collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + // https://github.com/uptrace/uptrace-go/blob/master/example/otlp-metrics/main.go#L28 + // https://github.com/open-telemetry/opentelemetry-go/blob/main/exporters/otlp/otlpmetric/otlpmetricgrpc/example_test.go + exporter, err := otlpmetricgrpc.New(ctx, metricOpts...) + if err != nil { + return nil, errors.WrapIf(err, "failed to create otlptracegrpc exporter") + } + metricExporter := metric.NewPeriodicReader( + exporter, + // Default is 1m. Set to 3s for demonstrative purposes. + metric.WithInterval(3*time.Second)) + + exporters = append(exporters, metricExporter) + } + } + + return exporters, nil } -func (o *OtelMetrics) GracefulShutdown(ctx context.Context) error { - err := o.Echo.Shutdown(ctx) - if err != nil { - return err +// we could also use our existing server app port and a new /metrics endpoint instead of a new server with different port for our app metrics + +func (o *OtelMetrics) RegisterMetricsEndpoint( + server contracts.EchoHttpServer, +) { + if o.config.UseOTLP { + return + } + + var metricsPath string + if o.config.MetricsRoutePath == "" { + metricsPath = "metrics" + } else { + metricsPath = o.config.MetricsRoutePath } - return nil + // when we send request to /metrics endpoint, this handler gets singleton `prometheus.DefaultRegisterer` registry with calling `Collect` method on registered prometheus reader and get all get metrics and write them in /metrics endpoint output + server.GetEchoInstance(). + GET(metricsPath, echo.WrapHandler(promhttp.Handler())) } diff --git a/internal/pkg/otel/metrics/metrics_fx.go b/internal/pkg/otel/metrics/metrics_fx.go new file mode 100644 index 00000000..2de79d2b --- /dev/null +++ b/internal/pkg/otel/metrics/metrics_fx.go @@ -0,0 +1,92 @@ +package metrics + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + + "go.opentelemetry.io/contrib/instrumentation/host" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + "go.uber.org/fx" +) + +var ( + // Module provided to fxlog + // https://uber-go.github.io/fx/modules.html + Module = fx.Module( //nolint:gochecknoglobals + "otelmetrixfx", + metricsProviders, + metricsInvokes, + ) + + metricsProviders = fx.Options(fx.Provide( //nolint:gochecknoglobals + ProvideMetricsConfig, + NewOtelMetrics, + fx.Annotate( + provideMeter, + fx.ParamTags(`optional:"true"`), + fx.As(new(AppMetrics)), + fx.As(new(metric.Meter))), + )) + + metricsInvokes = fx.Options( //nolint:gochecknoglobals + fx.Invoke(registerHooks), + fx.Invoke(func(m *OtelMetrics, server contracts.EchoHttpServer) { + m.RegisterMetricsEndpoint(server) + }), + ) +) + +func provideMeter(otelMetrics *OtelMetrics) AppMetrics { + return otelMetrics.appMetrics +} + +// we don't want to register any dependencies here, its func body should execute always even we don't request for that, so we should use `invoke` +func registerHooks( + lc fx.Lifecycle, + metrics *OtelMetrics, + logger logger.Logger, +) { + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + if metrics.appMetrics == nil { + return nil + } + + if metrics.config.EnableHostMetrics { + // https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/host + // we changed default meter provider in metrics setup + logger.Info("Starting host instrumentation:") + err := host.Start( + host.WithMeterProvider(otel.GetMeterProvider()), + ) + if err != nil { + logger.Errorf( + "error starting host instrumentation: %s", + err, + ) + } + } + + return nil + }, + OnStop: func(ctx context.Context) error { + if metrics.appMetrics == nil { + return nil + } + + if err := metrics.Shutdown(ctx); err != nil { + logger.Errorf( + "error in shutting down metrics provider: %v", + err, + ) + } else { + logger.Info("metrics provider shutdown gracefully") + } + + return nil + }, + }) +} diff --git a/internal/pkg/otel/metrics/metrics_options.go b/internal/pkg/otel/metrics/metrics_options.go new file mode 100644 index 00000000..844ab6a2 --- /dev/null +++ b/internal/pkg/otel/metrics/metrics_options.go @@ -0,0 +1,42 @@ +package metrics + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/iancoleman/strcase" +) + +type OTLPProvider struct { + Name string `mapstructure:"name"` + Enabled bool `mapstructure:"enabled"` + OTLPEndpoint string `mapstructure:"otlpEndpoint"` + OTLPHeaders map[string]string `mapstructure:"otlpHeaders"` +} + +type MetricsOptions struct { + Host string `mapstructure:"host"` + Port string `mapstructure:"port"` + ServiceName string `mapstructure:"serviceName"` + Version string `mapstructure:"version"` + MetricsRoutePath string `mapstructure:"metricsRoutePath"` + EnableHostMetrics bool `mapstructure:"enableHostMetrics"` + UseStdout bool `mapstructure:"useStdout"` + InstrumentationName string `mapstructure:"instrumentationName"` + UseOTLP bool `mapstructure:"useOTLP"` + OTLPProviders []OTLPProvider `mapstructure:"otlpProviders"` + ElasticApmExporterOptions *OTLPProvider `mapstructure:"elasticApmExporterOptions"` + UptraceExporterOptions *OTLPProvider `mapstructure:"uptraceExporterOptions"` + SignozExporterOptions *OTLPProvider `mapstructure:"signozExporterOptions"` +} + +func ProvideMetricsConfig( + environment environment.Environment, +) (*MetricsOptions, error) { + optionName := strcase.ToLowerCamel( + typeMapper.GetGenericTypeNameByT[MetricsOptions](), + ) + + return config.BindConfigKey[*MetricsOptions](optionName, environment) +} diff --git a/internal/pkg/otel/metrics/metrics_test.go b/internal/pkg/otel/metrics/metrics_test.go new file mode 100644 index 00000000..3d2795bc --- /dev/null +++ b/internal/pkg/otel/metrics/metrics_test.go @@ -0,0 +1,69 @@ +package metrics_test + +import ( + "fmt" + "io" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics" + + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestHealth(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "/health suite") +} + +var _ = Describe("/", Ordered, func() { + var ( + url string + err error + res *http.Response + ) + + BeforeAll(func() { + var cfg *metrics.MetricsOptions + + fxtest.New( + GinkgoT(), + zap.Module, + fxlog.FxLogger, + config.Module, + customEcho.Module, + + metrics.Module, + + fx.Populate(&cfg), + ).RequireStart() + + url = fmt.Sprintf("http://%s:%s/metrics", cfg.Host, cfg.Port) + }) + + BeforeEach(func() { + res, err = http.Get(url) + }) + It("returns status OK", func() { + Expect(err).To(BeNil()) + Expect(res.StatusCode).To(Equal(http.StatusOK)) + }) + + It("returns how many requests were made", func() { + b, err := io.ReadAll(res.Body) + Expect(err).To(BeNil()) + + Expect( + b, + ).To(ContainSubstring(`promhttp_metric_handler_requests_total{code="200"} 1`)) + }) +}) diff --git a/internal/pkg/otel/otel_fx.go b/internal/pkg/otel/otel_fx.go deleted file mode 100644 index 52b2295c..00000000 --- a/internal/pkg/otel/otel_fx.go +++ /dev/null @@ -1,107 +0,0 @@ -package otel - -import ( - "context" - "net/http" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/metrics" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing" - - "emperror.dev/errors" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/trace" - "go.uber.org/fx" -) - -var ( - // Module provided to fxlog - // https://uber-go.github.io/fx/modules.html - Module = fx.Module( //nolint:gochecknoglobals - "otelfx", - otelProviders, - otelInvokes, - ) - - otelProviders = fx.Options(fx.Provide( //nolint:gochecknoglobals - config.ProvideOtelConfig, - metrics.NewOtelMetrics, - tracing.NewOtelTracing, - fx.Annotate( - provideMeter, - fx.As(new(metric.Meter))), - fx.Annotate( - provideTracer, - fx.As(new(tracing.AppTracer)), - fx.As(new(trace.Tracer)), - ), - )) - - otelInvokes = fx.Options(fx.Invoke(registerHooks)) //nolint:gochecknoglobals -) - -func provideMeter(otelMetrics *metrics.OtelMetrics) metric.Meter { - return otelMetrics.Meter -} - -func provideTracer(tracingOtel *tracing.TracingOpenTelemetry) tracing.AppTracer { - return tracingOtel.AppTracer -} - -// we don't want to register any dependencies here, its func body should execute always even we don't request for that, so we should use `invoke` -func registerHooks( - lc fx.Lifecycle, - metrics *metrics.OtelMetrics, - logger logger.Logger, - tracingOtel *tracing.TracingOpenTelemetry, -) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - if metrics.Meter == nil { - return nil - } - - go func() { - // https://medium.com/@mokiat/proper-http-shutdown-in-go-bd3bfaade0f2 - // When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return ErrServerClosed. Make sure the program doesn’t exit and waits instead for Shutdown to return. - if err := metrics.Run(); !errors.Is(err, http.ErrServerClosed) { - // do a fatal for running OnStop hook - logger.Fatalf( - "(metrics.RunHttpServer) error in running metrics server: {%v}", - err, - ) - } - }() - logger.Infof( - "Metrics server %s is listening on Host:{%s} Http PORT: {%s}", - metrics.Config.OTelMetricsOptions.Name, - metrics.Config.OTelMetricsOptions.Host, - metrics.Config.OTelMetricsOptions.Port, - ) - - return nil - }, - OnStop: func(ctx context.Context) error { - if err := tracingOtel.TracerProvider.Shutdown(ctx); err != nil { - logger.Errorf("error in shutting down trace provider: %v", err) - } else { - logger.Info("trace provider shutdown gracefully") - } - - if metrics.Meter == nil { - return nil - } - // https://github.com/uber-go/fx/blob/v1.20.0/app.go#L573 - // this ctx is just for stopping callbacks or OnStop callbacks, and it has short timeout 15s, and it is not alive in whole lifetime app - // https://medium.com/@mokiat/proper-http-shutdown-in-go-bd3bfaade0f2 - // When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return ErrServerClosed. Make sure the program doesn’t exit and waits instead for Shutdown to return. - if err := metrics.GracefulShutdown(ctx); err != nil { - logger.Errorf("error shutting down metrics server: %v", err) - } else { - logger.Info("metrics server shutdown gracefully") - } - return nil - }, - }) -} diff --git a/internal/pkg/otel/tracing/custom_tracer.go b/internal/pkg/otel/tracing/custom_tracer.go index 3c86d914..0bc84c49 100644 --- a/internal/pkg/otel/tracing/custom_tracer.go +++ b/internal/pkg/otel/tracing/custom_tracer.go @@ -3,6 +3,8 @@ package tracing import ( "context" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" ) @@ -22,7 +24,7 @@ func (c *appTracer) Start( ) (context.Context, trace.Span) { parentSpan := trace.SpanFromContext(ctx) if parentSpan != nil { - ContextWithParentSpan(ctx, parentSpan) + utils.ContextWithParentSpan(ctx, parentSpan) } return c.Tracer.Start(ctx, spanName, opts...) diff --git a/internal/pkg/otel/tracing/mediatr/pipelines/config.go b/internal/pkg/otel/tracing/mediatr/pipelines/config.go new file mode 100644 index 00000000..9be241ff --- /dev/null +++ b/internal/pkg/otel/tracing/mediatr/pipelines/config.go @@ -0,0 +1,43 @@ +package pipelines + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" +) + +type config struct { + logger logger.Logger + serviceName string +} + +var defaultConfig = &config{ + serviceName: "app", + logger: defaultLogger.GetLogger(), +} + +// Option specifies instrumentation configuration options. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +func WithServiceName(v string) Option { + return optionFunc(func(cfg *config) { + if cfg.serviceName != "" { + cfg.serviceName = v + } + }) +} + +func WithLogger(l logger.Logger) Option { + return optionFunc(func(cfg *config) { + if cfg.logger != nil { + cfg.logger = l + } + }) +} diff --git a/internal/pkg/otel/tracing/mediatr/pipelines/mediator_tracing_pipeline.go b/internal/pkg/otel/tracing/mediatr/pipelines/mediator_tracing_pipeline.go new file mode 100644 index 00000000..12cace35 --- /dev/null +++ b/internal/pkg/otel/tracing/mediatr/pipelines/mediator_tracing_pipeline.go @@ -0,0 +1,105 @@ +package pipelines + +import ( + "context" + "fmt" + "strings" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/constants/telemetrytags" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/constants/tracing/components" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + customAttribute "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/mehdihadeli/go-mediatr" + "go.opentelemetry.io/otel/attribute" +) + +type mediatorTracingPipeline struct { + config *config + tracer tracing.AppTracer +} + +func NewMediatorTracingPipeline( + appTracer tracing.AppTracer, + opts ...Option, +) mediatr.PipelineBehavior { + cfg := defaultConfig + for _, opt := range opts { + opt.apply(cfg) + } + + return &mediatorTracingPipeline{ + config: cfg, + tracer: appTracer, + } +} + +func (r *mediatorTracingPipeline) Handle( + ctx context.Context, + request interface{}, + next mediatr.RequestHandlerFunc, +) (interface{}, error) { + requestName := typeMapper.GetSnakeTypeName(request) + + componentName := components.RequestHandler + requestNameTag := telemetrytags.App.RequestName + requestTag := telemetrytags.App.Request + requestResultNameTag := telemetrytags.App.RequestResultName + requestResultTag := telemetrytags.App.RequestResult + + switch { + case strings.Contains(typeMapper.GetPackageName(request), "command") || strings.Contains(typeMapper.GetPackageName(request), "commands"): + componentName = components.CommandHandler + requestNameTag = telemetrytags.App.CommandName + requestTag = telemetrytags.App.Command + requestResultNameTag = telemetrytags.App.CommandResultName + requestResultTag = telemetrytags.App.CommandResult + case strings.Contains(typeMapper.GetPackageName(request), "query") || strings.Contains(typeMapper.GetPackageName(request), "queries"): + componentName = components.QueryHandler + requestNameTag = telemetrytags.App.QueryName + requestTag = telemetrytags.App.Query + requestResultNameTag = telemetrytags.App.QueryResultName + requestResultTag = telemetrytags.App.QueryResult + case strings.Contains(typeMapper.GetPackageName(request), "event") || strings.Contains(typeMapper.GetPackageName(request), "events"): + componentName = components.EventHandler + requestNameTag = telemetrytags.App.EventName + requestTag = telemetrytags.App.Event + requestResultNameTag = telemetrytags.App.EventResultName + requestResultTag = telemetrytags.App.EventResult + } + + operationName := fmt.Sprintf("%s_handler", requestName) + spanName := fmt.Sprintf( + "%s.%s/%s", + componentName, + operationName, + requestName, + ) // by convention + + // https://golang.org/pkg/context/#Context + newCtx, span := r.tracer.Start(ctx, spanName) + + defer span.End() + + span.SetAttributes( + attribute.String(requestNameTag, requestName), + customAttribute.Object(requestTag, request), + ) + + response, err := next(newCtx) + + responseName := typeMapper.GetSnakeTypeName(response) + span.SetAttributes( + attribute.String(requestResultNameTag, responseName), + customAttribute.Object(requestResultTag, response), + ) + + err = utils.TraceStatusFromSpan( + span, + err, + ) + + return response, err +} diff --git a/internal/pkg/otel/tracing/tracing.go b/internal/pkg/otel/tracing/tracing.go index 3bd46d39..97735424 100644 --- a/internal/pkg/otel/tracing/tracing.go +++ b/internal/pkg/otel/tracing/tracing.go @@ -1,91 +1,103 @@ package tracing +// https://opentelemetry.io/docs/reference/specification/ +// https://opentelemetry.io/docs/instrumentation/go/getting-started/ +// https://opentelemetry.io/docs/instrumentation/go/manual/ +// https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/ +// https://uptrace.dev/opentelemetry/go-tracing.html +// https://lightstep.com/blog/opentelemetry-go-all-you-need-to-know +// https://trstringer.com/otel-part2-instrumentation/ +// https://trstringer.com/otel-part5-propagation/ +// https://github.com/tedsuo/otel-go-basics/blob/main/server.go +// https://github.com/riferrei/otel-with-golang/blob/main/main.go + import ( - "fmt" - "log" + "context" "os" + "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - config2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" "emperror.dev/errors" + "github.com/samber/lo" "go.opentelemetry.io/contrib/propagators/ot" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/exporters/zipkin" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" - - _ "go.opentelemetry.io/otel" ) -// https://opentelemetry.io/docs/reference/specification/ -// https://opentelemetry.io/docs/instrumentation/go/getting-started/ -// https://opentelemetry.io/docs/instrumentation/go/manual/ -// https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/ -// https://uptrace.dev/opentelemetry/go-tracing.html -// https://lightstep.com/blog/opentelemetry-go-all-you-need-to-know -// https://trstringer.com/otel-part2-instrumentation/ -// https://trstringer.com/otel-part5-propagation/ -// https://github.com/tedsuo/otel-go-basics/blob/main/server.go - type TracingOpenTelemetry struct { - config *config2.OpenTelemetryOptions - jaegerExporter tracesdk.SpanExporter - zipkinExporter tracesdk.SpanExporter - stdExporter tracesdk.SpanExporter - environment environemnt.Environment - TracerProvider *tracesdk.TracerProvider - AppTracer AppTracer + config *TracingOptions + environment environment.Environment + appTracer AppTracer + provider *tracesdk.TracerProvider } // Create one tracer per package // NOTE: You only need a tracer if you are creating your own spans func NewOtelTracing( - config *config2.OpenTelemetryOptions, - environment environemnt.Environment, + config *TracingOptions, + environment environment.Environment, ) (*TracingOpenTelemetry, error) { - openTel := &TracingOpenTelemetry{config: config, environment: environment} + otelTracing := &TracingOpenTelemetry{ + config: config, + environment: environment, + } - err := openTel.configExporters() + resource, err := otelTracing.newResource() if err != nil { - return nil, errors.WrapIf(err, "error in config exporter") + return nil, errors.WrapIf(err, "failed to create resource") } - // https://opentelemetry.io/docs/instrumentation/go/manual/#initializing-a-new-tracer - err = openTel.configTracerProvider() + appTracer, err := otelTracing.initTracer(resource) if err != nil { return nil, err } - // https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/propagators/ot/ot_propagator.go - // https://github.com/open-telemetry/opentelemetry-go/blob/main/propagation/trace_context.go - // https://github.com/open-telemetry/opentelemetry-go/blob/main/propagation/baggage.go/ - // https://trstringer.com/otel-part5-propagation/ - propagators := []propagation.TextMapPropagator{ - ot.OT{}, // should be placed before `TraceContext` for preventing conflict - propagation.TraceContext{}, - propagation.Baggage{}, - } + otelTracing.appTracer = appTracer - // Register our TracerProvider as the global so any imported - // instrumentation in the future will default to using it. - otel.SetTracerProvider(openTel.TracerProvider) - otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagators...)) + return otelTracing, nil +} - // https://trstringer.com/otel-part2-instrumentation/ - // Finally, set the tracer that can be used for this package. global app tracer - openTel.AppTracer = NewAppTracer(config.InstrumentationName) +func (o *TracingOpenTelemetry) Shutdown(ctx context.Context) error { + return o.provider.Shutdown(ctx) +} + +func (o *TracingOpenTelemetry) newResource() (*resource.Resource, error) { + // https://github.com/uptrace/uptrace-go/blob/master/example/otlp-traces/main.go#L49C1-L56C5 + resource, err := resource.New(context.Background(), + resource.WithFromEnv(), + resource.WithTelemetrySDK(), + resource.WithHost(), + resource.WithOS(), + resource.WithSchemaURL(semconv.SchemaURL), + resource.WithAttributes( + semconv.ServiceName(o.config.ServiceName), + semconv.ServiceVersion(o.config.Version), + attribute.Int64("ID", o.config.Id), + attribute.String("environment", o.environment.GetEnvironmentName()), + semconv.TelemetrySDKVersionKey.String("v1.21.0"), // semconv version + semconv.TelemetrySDKLanguageGo, + )) - return openTel, nil + return resource, err } -func (o *TracingOpenTelemetry) configTracerProvider() error { +func (o *TracingOpenTelemetry) initTracer( + resource *resource.Resource, +) (AppTracer, error) { + exporters, err := o.configExporters() + if err != nil { + return nil, err + } + var sampler tracesdk.Sampler if o.config.AlwaysOnSampler { sampler = tracesdk.AlwaysSample() @@ -93,68 +105,246 @@ func (o *TracingOpenTelemetry) configTracerProvider() error { sampler = tracesdk.NeverSample() } - // https://github.com/open-telemetry/opentelemetry-go/blob/main/example/fib/main.go#L44 - // Ensure default SDK resources and the required service name are set. - r, err := resource.Merge( - resource.Default(), - resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceNameKey.String(o.config.ServiceName), - attribute.Int64("ID", o.config.Id), - attribute.String("environment", o.environment.GetEnvironmentName()), - ), + batchExporters := lo.Map( + exporters, + func(item tracesdk.SpanExporter, index int) tracesdk.TracerProviderOption { + return tracesdk.WithBatcher(item) + }, ) - if err != nil { - return err - } - tp := tracesdk.NewTracerProvider( - // Always be sure to batch in production. - tracesdk.WithBatcher(o.jaegerExporter), - tracesdk.WithBatcher(o.zipkinExporter), - tracesdk.WithBatcher(o.stdExporter), + // https://opentelemetry.io/docs/instrumentation/go/exporting_data/#resources + // Resources are a special type of attribute that apply to all spans generated by a process + opts := append( + batchExporters, + tracesdk.WithResource(resource), tracesdk.WithSampler(sampler), + ) + + provider := tracesdk.NewTracerProvider(opts...) + + // Register our tracerProvider as the global so any imported + // instrumentation in the future will default to using it. + otel.SetTracerProvider(provider) + o.provider = provider - // https://opentelemetry.io/docs/instrumentation/go/exporting_data/#resources - // Resources are a special type of attribute that apply to all spans generated by a process - tracesdk.WithResource(r), + // https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/propagators/ot/ot_propagator.go + // https://github.com/open-telemetry/opentelemetry-go/blob/main/propagation/trace_context.go + // https://github.com/open-telemetry/opentelemetry-go/blob/main/propagation/baggage.go/ + // https://trstringer.com/otel-part5-propagation/ + otel.SetTextMapPropagator( + propagation.NewCompositeTextMapPropagator( + ot.OT{}, // should be placed before `TraceContext` for preventing conflict + propagation.Baggage{}, + propagation.TraceContext{}, + ), ) - o.TracerProvider = tp - return nil + + // https://trstringer.com/otel-part2-instrumentation/ + // Finally, set the tracer that can be used for this package. global app tracer + appTracer := NewAppTracer(o.config.InstrumentationName) + + return appTracer, nil } -func (o *TracingOpenTelemetry) configExporters() error { - logger := log.New(os.Stderr, "otel_log", log.Ldate|log.Ltime|log.Llongfile) +func (o *TracingOpenTelemetry) configExporters() ([]tracesdk.SpanExporter, error) { + ctx := context.Background() + traceOpts := []otlptracegrpc.Option{ + otlptracegrpc.WithTimeout(5 * time.Second), + otlptracegrpc.WithInsecure(), + } - if o.config.JaegerExporterOptions != nil { - // Create the Jaeger exporter - jaegerExporter, err := jaeger.New(jaeger.WithAgentEndpoint( - jaeger.WithAgentHost(o.config.JaegerExporterOptions.AgentHost), - jaeger.WithAgentPort(o.config.JaegerExporterOptions.AgentPort), - jaeger.WithLogger(logger), - )) - if err != nil { - return err + var exporters []tracesdk.SpanExporter + + if !o.config.UseOTLP { //nolint:nestif + + // jaeger exporter removed from otel spec (it used jaeger agent and jaeger agent port), now we should use OTLP which supports by jaeger now by its built-in `collector` + // https://medium.com/jaegertracing/introducing-native-support-for-opentelemetry-in-jaeger-eb661be8183c + // https://www.jaegertracing.io/docs/1.38/apis/#opentelemetry-protocol-stable + // https://deploy-preview-1892--opentelemetry.netlify.app/blog/2022/jaeger-native-otlp/ + // https://www.jaegertracing.io/docs/1.49/getting-started/ + // https://opentelemetry.io/docs/instrumentation/go/exporters/ + // https://opentelemetry.io/docs/specs/otlp/ + // https://github.com/open-telemetry/opentelemetry-go/pull/4467 + if o.config.JaegerExporterOptions != nil { + traceOpts = append( + traceOpts, + otlptracegrpc.WithEndpoint( + o.config.JaegerExporterOptions.OTLPEndpoint, + ), + otlptracegrpc.WithHeaders( + o.config.JaegerExporterOptions.OTLPHeaders, + ), + ) + + // send otel traces to jaeger builtin collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + jaegerTraceExporter, err := otlptracegrpc.New(ctx, traceOpts...) + if err != nil { + return nil, errors.WrapIf( + err, + "failed to create oltptrace exporter for jaeger", + ) + } + + exporters = append(exporters, jaegerTraceExporter) } - o.jaegerExporter = jaegerExporter - } - if o.config.ZipkinExporterOptions != nil { - zipkinExporter, err := zipkin.New( - o.config.ZipkinExporterOptions.Url, - ) - if err != nil { - return err + + // https://medium.com/adevinta-tech-blog/distributed-tracing-with-opentelemetry-in-your-go-python-microservices-1782cd0a1e77 + // https://grafana.com/docs/tempo/latest/getting-started/ + if o.config.TempoExporterOptions != nil { + traceOpts = append( + traceOpts, + otlptracegrpc.WithEndpoint( + o.config.TempoExporterOptions.OTLPEndpoint, + ), + otlptracegrpc.WithHeaders( + o.config.TempoExporterOptions.OTLPHeaders, + ), + ) + + // send otel traces to jaeger builtin collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + grafanaTempoTraceExporter, err := otlptracegrpc.New( + ctx, + traceOpts...) + if err != nil { + return nil, errors.WrapIf( + err, + "failed to create oltptrace exporter for grafana-tempo", + ) + } + + exporters = append(exporters, grafanaTempoTraceExporter) } - o.zipkinExporter = zipkinExporter - } - if o.config.UseStdout { - stdExporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) - if err != nil { - return fmt.Errorf("creating stdout exporter: %w", err) + if o.config.ZipkinExporterOptions != nil { + zipkinExporter, err := zipkin.New( + o.config.ZipkinExporterOptions.Url, + ) + if err != nil { + return nil, errors.WrapIf( + err, + "failed to create exporter for zipkin", + ) + } + + exporters = append(exporters, zipkinExporter) + } + + if o.config.ElasticApmExporterOptions != nil { + // https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html + // https://www.elastic.co/guide/en/apm/guide/current/open-telemetry-direct.html#instrument-apps-otel + // https://github.com/anilsenay/go-opentelemetry-examples/blob/elastic/cmd/main.go#L35 + traceOpts = append( + traceOpts, + otlptracegrpc.WithEndpoint( + o.config.ElasticApmExporterOptions.OTLPEndpoint, + ), + otlptracegrpc.WithHeaders( + o.config.ElasticApmExporterOptions.OTLPHeaders, + ), + ) + + // send otel traces to jaeger builtin collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + elasticApmExporter, err := otlptracegrpc.New(ctx, traceOpts...) + if err != nil { + return nil, errors.WrapIf( + err, + "failed to create oltptrace exporter for elastic-apm", + ) + } + + exporters = append(exporters, elasticApmExporter) + } + + if o.config.UptraceExporterOptions != nil { + // https://github.com/uptrace/uptrace-go/blob/master/example/otlp-traces/main.go#L49C1-L56C5 + // https://uptrace.dev/get/opentelemetry-go.html#exporting-traces + // https://uptrace.dev/get/opentelemetry-go.html#exporting-metrics + traceOpts = append( + traceOpts, + otlptracegrpc.WithEndpoint( + o.config.UptraceExporterOptions.OTLPEndpoint, + ), + otlptracegrpc.WithHeaders( + o.config.UptraceExporterOptions.OTLPHeaders, + ), + ) + + // send otel traces to jaeger builtin collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + uptraceExporter, err := otlptracegrpc.New(ctx, traceOpts...) + if err != nil { + return nil, errors.WrapIf( + err, + "failed to create oltptrace exporter for uptrace", + ) + } + + exporters = append(exporters, uptraceExporter) + } + + if o.config.SignozExporterOptions != nil { + // https://signoz.io/docs/instrumentation/golang/#instrumentation-of-a-sample-golang-application + // https://signoz.io/blog/distributed-tracing-golang/ + traceOpts = append( + traceOpts, + otlptracegrpc.WithEndpoint( + o.config.SignozExporterOptions.OTLPEndpoint, + ), + otlptracegrpc.WithHeaders( + o.config.SignozExporterOptions.OTLPHeaders, + ), + ) + + // send otel traces to jaeger builtin collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + signozExporter, err := otlptracegrpc.New(ctx, traceOpts...) + if err != nil { + return nil, errors.WrapIf( + err, + "failed to create oltptrace exporter for signoz", + ) + } + + exporters = append(exporters, signozExporter) + } + + if o.config.UseStdout { + stdExporter, err := stdouttrace.New( + stdouttrace.WithWriter( + os.Stdout, + ), // stdExporter default is `stdouttrace.WithWriter(os.Stdout)`, we can remove this also + stdouttrace.WithPrettyPrint(), // make output json with pretty printing + ) + if err != nil { + return nil, errors.WrapIf(err, "creating stdout exporter") + } + + exporters = append(exporters, stdExporter) + } + } else { + // use some otel collector endpoints + for _, oltpProvider := range o.config.OTLPProviders { + if !oltpProvider.Enabled { + continue + } + + traceOpts = append(traceOpts, otlptracegrpc.WithEndpoint(oltpProvider.OTLPEndpoint), otlptracegrpc.WithHeaders(oltpProvider.OTLPHeaders)) + + // send otel metrics to an otel collector endpoint (default grpc port: 4317) + // https://opentelemetry.io/docs/collector/ + // https://github.com/uptrace/uptrace-go/blob/master/example/otlp-traces/main.go#L29 + // https://github.com/open-telemetry/opentelemetry-go/blob/main/exporters/otlp/otlptrace/otlptracehttp/example_test.go#L70 + traceExporter, err := otlptracegrpc.New(ctx, traceOpts...) + if err != nil { + return nil, errors.WrapIf(err, "failed to create otlptracegrpc exporter") + } + + exporters = append(exporters, traceExporter) } - o.stdExporter = stdExporter } - return nil + return exporters, nil } diff --git a/internal/pkg/otel/tracing/tracing_fx.go b/internal/pkg/otel/tracing/tracing_fx.go new file mode 100644 index 00000000..67b4fb73 --- /dev/null +++ b/internal/pkg/otel/tracing/tracing_fx.go @@ -0,0 +1,60 @@ +package tracing + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + + "go.opentelemetry.io/otel/trace" + "go.uber.org/fx" +) + +var ( + // Module provided to fxlog + // https://uber-go.github.io/fx/modules.html + Module = fx.Module( //nolint:gochecknoglobals + "oteltracingfx", + tracingProviders, + tracingInvokes, + ) + + tracingProviders = fx.Options(fx.Provide( //nolint:gochecknoglobals + ProvideTracingConfig, + NewOtelTracing, + fx.Annotate( + provideTracer, + fx.ParamTags(`optional:"true"`), + fx.As(new(AppTracer)), + fx.As(new(trace.Tracer)), + ), + )) + + tracingInvokes = fx.Options( + fx.Invoke(registerHooks), + ) //nolint:gochecknoglobals +) + +func provideTracer( + tracingOtel *TracingOpenTelemetry, +) AppTracer { + return tracingOtel.appTracer +} + +// we don't want to register any dependencies here, its func body should execute always even we don't request for that, so we should use `invoke` +func registerHooks( + lc fx.Lifecycle, + logger logger.Logger, + tracingOtel *TracingOpenTelemetry, +) { + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + if err := tracingOtel.Shutdown(ctx); err != nil { + logger.Errorf("error in shutting down trace provider: %v", err) + } else { + logger.Info("trace provider shutdown gracefully") + } + + return nil + }, + }) +} diff --git a/internal/pkg/otel/tracing/tracing_headers/metadata_tracing_extensions.go b/internal/pkg/otel/tracing/tracing_headers/metadata_tracing_extensions.go index a6d4cd41..d307b278 100644 --- a/internal/pkg/otel/tracing/tracing_headers/metadata_tracing_extensions.go +++ b/internal/pkg/otel/tracing/tracing_headers/metadata_tracing_extensions.go @@ -1,7 +1,7 @@ package tracingHeaders import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" ) func GetTracingTraceId(m metadata.Metadata) string { diff --git a/internal/pkg/otel/tracing/tracing_options.go b/internal/pkg/otel/tracing/tracing_options.go new file mode 100644 index 00000000..4856ddbf --- /dev/null +++ b/internal/pkg/otel/tracing/tracing_options.go @@ -0,0 +1,48 @@ +package tracing + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/iancoleman/strcase" +) + +type OTLPProvider struct { + Name string `mapstructure:"name"` + Enabled bool `mapstructure:"enabled"` + OTLPEndpoint string `mapstructure:"otlpEndpoint"` + OTLPHeaders map[string]string `mapstructure:"otlpHeaders"` +} + +type TracingOptions struct { + Enabled bool `mapstructure:"enabled"` + ServiceName string `mapstructure:"serviceName"` + Version string `mapstructure:"version"` + InstrumentationName string `mapstructure:"instrumentationName"` + Id int64 `mapstructure:"id"` + AlwaysOnSampler bool `mapstructure:"alwaysOnSampler"` + ZipkinExporterOptions *ZipkinExporterOptions `mapstructure:"zipkinExporterOptions"` + JaegerExporterOptions *OTLPProvider `mapstructure:"jaegerExporterOptions"` + ElasticApmExporterOptions *OTLPProvider `mapstructure:"elasticApmExporterOptions"` + UptraceExporterOptions *OTLPProvider `mapstructure:"uptraceExporterOptions"` + SignozExporterOptions *OTLPProvider `mapstructure:"signozExporterOptions"` + TempoExporterOptions *OTLPProvider `mapstructure:"tempoExporterOptions"` + UseStdout bool `mapstructure:"useStdout"` + UseOTLP bool `mapstructure:"useOTLP"` + OTLPProviders []OTLPProvider `mapstructure:"otlpProviders"` +} + +type ZipkinExporterOptions struct { + Url string `mapstructure:"url"` +} + +func ProvideTracingConfig( + environment environment.Environment, +) (*TracingOptions, error) { + optionName := strcase.ToLowerCamel( + typeMapper.GetGenericTypeNameByT[TracingOptions](), + ) + + return config.BindConfigKey[*TracingOptions](optionName, environment) +} diff --git a/internal/pkg/otel/tracing/utils.go b/internal/pkg/otel/tracing/utils.go deleted file mode 100644 index 6ca589e7..00000000 --- a/internal/pkg/otel/tracing/utils.go +++ /dev/null @@ -1,159 +0,0 @@ -package tracing - -import ( - "context" - "reflect" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" - - "github.com/ahmetb/go-linq/v3" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - trace2 "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/trace" -) - -type traceContextKeyType int - -const parentSpanKey traceContextKeyType = iota + 1 - -func TraceErrFromContext(ctx context.Context, err error) error { - // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors - span := trace.SpanFromContext(ctx) - defer span.End() - - if err != nil { - stackTraceError := errorUtils.ErrorsWithStack(err) - span.SetStatus(codes.Error, "") - span.SetAttributes(attribute.String(ErrorMessage, stackTraceError)) - span.RecordError(err) - } - - return err -} - -func TraceErrFromSpan(span trace.Span, err error) error { - if err != nil { - stackTraceError := errorUtils.ErrorsWithStack(err) - span.SetStatus(codes.Error, "") - span.SetAttributes(attribute.String(ErrorMessage, stackTraceError)) - span.RecordError(err) - } - - return err -} - -func GetParentSpanContext(span trace.Span) trace.SpanContext { - readWriteSpan, ok := span.(trace2.ReadWriteSpan) - if !ok { - return *new(trace.SpanContext) - } - - return readWriteSpan.Parent() -} - -func ContextWithParentSpan(parent context.Context, span trace.Span) context.Context { - return context.WithValue(parent, parentSpanKey, span) -} - -func ParentSpanFromContext(ctx context.Context) trace.Span { - _, nopSpan := trace.NewNoopTracerProvider().Tracer("").Start(ctx, "") - if ctx == nil { - return nopSpan - } - if span, ok := ctx.Value(parentSpanKey).(trace.Span); ok { - return span - } - return nopSpan -} - -func CopyFromParentSpanAttribute( - ctx context.Context, - span trace.Span, - attributeName string, - parentAttributeName string, -) { - parentAtt := GetParentSpanAttribute(ctx, parentAttributeName) - if reflect.ValueOf(parentAtt).IsZero() { - return - } - span.SetAttributes(attribute.String(attributeName, parentAtt.Value.AsString())) -} - -func CopyFromParentSpanAttributeIfNotSet( - ctx context.Context, - span trace.Span, - attributeName string, - attributeValue string, - parentAttributeName string, -) { - if attributeValue != "" { - span.SetAttributes(attribute.String(attributeName, attributeValue)) - return - } - CopyFromParentSpanAttribute(ctx, span, attributeName, parentAttributeName) -} - -func GetParentSpanAttribute(ctx context.Context, parentAttributeName string) attribute.KeyValue { - parentSpan := ParentSpanFromContext(ctx) - readWriteSpan, ok := parentSpan.(trace2.ReadWriteSpan) - if !ok { - return *new(attribute.KeyValue) - } - att := linq.From(readWriteSpan.Attributes()). - FirstWithT(func(att attribute.KeyValue) bool { return string(att.Key) == parentAttributeName }) - - return att.(attribute.KeyValue) -} - -func GetSpanAttributeFromCurrentContext(ctx context.Context, attributeName string) attribute.KeyValue { - span := trace.SpanFromContext(ctx) - readWriteSpan, ok := span.(trace2.ReadWriteSpan) - if !ok { - return *new(attribute.KeyValue) - } - att := linq.From(readWriteSpan.Attributes()). - FirstWithT(func(att attribute.KeyValue) bool { return string(att.Key) == attributeName }) - - return att.(attribute.KeyValue) -} - -func GetSpanAttribute(span trace.Span, attributeName string) attribute.KeyValue { - readWriteSpan, ok := span.(trace2.ReadWriteSpan) - if !ok { - return *new(attribute.KeyValue) - } - att := linq.From(readWriteSpan.Attributes()). - FirstWithT(func(att attribute.KeyValue) bool { return string(att.Key) == attributeName }) - - return att.(attribute.KeyValue) -} - -func MapsToAttributes(maps map[string]interface{}) []attribute.KeyValue { - var att []attribute.KeyValue - for key, val := range maps { - switch val.(type) { - case string: - att = append(att, attribute.String(key, val.(string))) - case int64: - att = append(att, attribute.Int64(key, val.(int64))) - case int, int32: - att = append(att, attribute.Int(key, val.(int))) - case float64, float32: - att = append(att, attribute.Float64(key, val.(float64))) - case bool: - att = append(att, attribute.Bool(key, val.(bool))) - } - } - - return att -} - -func MetadataToSet(meta metadata.Metadata) attribute.Set { - var keyValue []attribute.KeyValue - for key, val := range meta { - keyValue = append(keyValue, attribute.String(key, val.(string))) - } - return attribute.NewSet(keyValue...) -} diff --git a/internal/pkg/otel/tracing/utils/utils.go b/internal/pkg/otel/tracing/utils/utils.go new file mode 100644 index 00000000..96dcacbd --- /dev/null +++ b/internal/pkg/otel/tracing/utils/utils.go @@ -0,0 +1,365 @@ +package utils + +import ( + "context" + "net/http" + "reflect" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc/grpcerrors" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + problemdetails "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/problemdetails" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/constants/telemetrytags" + errorUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils/errorutils" + + "github.com/ahmetb/go-linq/v3" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + trace2 "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/semconv/v1.20.0/httpconv" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" +) + +type traceContextKeyType int + +const parentSpanKey traceContextKeyType = iota + 1 + +// HttpTraceStatusFromSpan create an error span if we have an error and a successful span when error is nil +func HttpTraceStatusFromSpan(span trace.Span, err error) error { + isError := err != nil + + if customErrors.IsCustomError(err) { + httpError := problemdetails.ParseError(err) + + return HttpTraceStatusFromSpanWithCode( + span, + err, + httpError.GetStatus(), + ) + } + + var ( + status int + code codes.Code + description = "" + ) + + if isError { + status = http.StatusInternalServerError + code = codes.Error + description = err.Error() + } else { + status = http.StatusOK + code = codes.Ok + } + + span.SetStatus(code, description) + span.SetAttributes( + semconv.HTTPStatusCode(status), + ) + + if isError { + stackTraceError := errorUtils.ErrorsWithStack(err) + + // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors + span.SetAttributes( + attribute.String(telemetrytags.Exceptions.Message, err.Error()), + attribute.String(telemetrytags.Exceptions.Stacktrace, stackTraceError), + ) + span.RecordError(err) + } + + return err +} + +func TraceStatusFromSpan(span trace.Span, err error) error { + isError := err != nil + + var ( + code codes.Code + description = "" + ) + + if isError { + code = codes.Error + description = err.Error() + } else { + code = codes.Ok + } + + span.SetStatus(code, description) + + if isError { + stackTraceError := errorUtils.ErrorsWithStack(err) + + // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors + span.SetAttributes( + attribute.String(telemetrytags.Exceptions.Message, err.Error()), + attribute.String(telemetrytags.Exceptions.Stacktrace, stackTraceError), + ) + span.RecordError(err) + } + + return err +} + +func TraceErrStatusFromSpan(span trace.Span, err error) error { + isError := err != nil + + span.SetStatus(codes.Error, err.Error()) + + if isError { + stackTraceError := errorUtils.ErrorsWithStack(err) + + // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors + span.SetAttributes( + attribute.String(telemetrytags.Exceptions.Message, err.Error()), + attribute.String(telemetrytags.Exceptions.Stacktrace, stackTraceError), + ) + span.RecordError(err) + } + + return err +} + +// HttpTraceStatusFromSpanWithCode create an error span with specific status code if we have an error and a successful span when error is nil with a specific status +func HttpTraceStatusFromSpanWithCode( + span trace.Span, + err error, + code int, +) error { + if err != nil { + stackTraceError := errorUtils.ErrorsWithStack(err) + + // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors + span.SetAttributes( + attribute.String(telemetrytags.Exceptions.Message, err.Error()), + attribute.String(telemetrytags.Exceptions.Stacktrace, stackTraceError), + ) + span.RecordError(err) + } + + if code > 0 { + // httpconv doesn't exist in semconv v1.21.0, and it moved to `opentelemetry-go-contrib` pkg + // https://github.com/open-telemetry/opentelemetry-go/pull/4362 + // https://github.com/open-telemetry/opentelemetry-go/issues/4081 + // using ClientStatus instead of ServerStatus for consideration of 4xx status as error + span.SetStatus(httpconv.ClientStatus(code)) + span.SetAttributes(semconv.HTTPStatusCode(code)) + } else { + span.SetStatus(codes.Error, "") + span.SetAttributes(semconv.HTTPStatusCode(http.StatusInternalServerError)) + } + + return err +} + +// HttpTraceStatusFromContext create an error span if we have an error and a successful span when error is nil +func HttpTraceStatusFromContext(ctx context.Context, err error) error { + // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors + span := trace.SpanFromContext(ctx) + + defer span.End() + + return HttpTraceStatusFromSpan(span, err) +} + +func TraceStatusFromContext(ctx context.Context, err error) error { + // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors + span := trace.SpanFromContext(ctx) + + defer span.End() + + return TraceStatusFromSpan(span, err) +} + +func TraceErrStatusFromContext(ctx context.Context, err error) error { + // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors + span := trace.SpanFromContext(ctx) + + defer span.End() + + return TraceErrStatusFromSpan(span, err) +} + +// GrpcTraceErrFromSpan setting span with status error with error message +func GrpcTraceErrFromSpan(span trace.Span, err error) error { + isError := err != nil + + span.SetStatus(codes.Error, err.Error()) + + if isError { + stackTraceError := errorUtils.ErrorsWithStack(err) + // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors + span.SetAttributes( + attribute.String(telemetrytags.Exceptions.Message, err.Error()), + attribute.String(telemetrytags.Exceptions.Stacktrace, stackTraceError), + ) + + if customErrors.IsCustomError(err) { + grpcErr := grpcerrors.ParseError(err) + span.SetAttributes( + semconv.RPCGRPCStatusCodeKey.Int(int(grpcErr.GetStatus())), + ) + } + + span.RecordError(err) + } + + return err +} + +// GrpcTraceErrFromSpanWithCode setting span with status error with error message +func GrpcTraceErrFromSpanWithCode(span trace.Span, err error, code int) error { + isError := err != nil + + span.SetStatus(codes.Error, err.Error()) + + if isError { + stackTraceError := errorUtils.ErrorsWithStack(err) + // https://opentelemetry.io/docs/instrumentation/go/manual/#record-errors + span.SetAttributes( + attribute.String(telemetrytags.Exceptions.Message, err.Error()), + attribute.String(telemetrytags.Exceptions.Stacktrace, stackTraceError), + ) + span.SetAttributes(semconv.RPCGRPCStatusCodeKey.Int(code)) + span.RecordError(err) + } + + return err +} + +func GetParentSpanContext(span trace.Span) trace.SpanContext { + readWriteSpan, ok := span.(trace2.ReadWriteSpan) + if !ok { + return *new(trace.SpanContext) + } + + return readWriteSpan.Parent() +} + +func ContextWithParentSpan( + parent context.Context, + span trace.Span, +) context.Context { + return context.WithValue(parent, parentSpanKey, span) +} + +func ParentSpanFromContext(ctx context.Context) trace.Span { + _, nopSpan := trace.NewNoopTracerProvider().Tracer("").Start(ctx, "") + if ctx == nil { + return nopSpan + } + + if span, ok := ctx.Value(parentSpanKey).(trace.Span); ok { + return span + } + + return nopSpan +} + +func CopyFromParentSpanAttribute( + ctx context.Context, + span trace.Span, + attributeName string, + parentAttributeName string, +) { + parentAtt := GetParentSpanAttribute(ctx, parentAttributeName) + if reflect.ValueOf(parentAtt).IsZero() { + return + } + + span.SetAttributes( + attribute.String(attributeName, parentAtt.Value.AsString()), + ) +} + +func CopyFromParentSpanAttributeIfNotSet( + ctx context.Context, + span trace.Span, + attributeName string, + attributeValue string, + parentAttributeName string, +) { + if attributeValue != "" { + span.SetAttributes(attribute.String(attributeName, attributeValue)) + return + } + CopyFromParentSpanAttribute(ctx, span, attributeName, parentAttributeName) +} + +func GetParentSpanAttribute( + ctx context.Context, + parentAttributeName string, +) attribute.KeyValue { + parentSpan := ParentSpanFromContext(ctx) + readWriteSpan, ok := parentSpan.(trace2.ReadWriteSpan) + if !ok { + return *new(attribute.KeyValue) + } + att := linq.From(readWriteSpan.Attributes()). + FirstWithT(func(att attribute.KeyValue) bool { return string(att.Key) == parentAttributeName }) + + return att.(attribute.KeyValue) +} + +func GetSpanAttributeFromCurrentContext( + ctx context.Context, + attributeName string, +) attribute.KeyValue { + span := trace.SpanFromContext(ctx) + readWriteSpan, ok := span.(trace2.ReadWriteSpan) + if !ok { + return *new(attribute.KeyValue) + } + att := linq.From(readWriteSpan.Attributes()). + FirstWithT(func(att attribute.KeyValue) bool { return string(att.Key) == attributeName }) + + return att.(attribute.KeyValue) +} + +func GetSpanAttribute( + span trace.Span, + attributeName string, +) attribute.KeyValue { + readWriteSpan, ok := span.(trace2.ReadWriteSpan) + if !ok { + return *new(attribute.KeyValue) + } + + att := linq.From(readWriteSpan.Attributes()). + FirstWithT(func(att attribute.KeyValue) bool { return string(att.Key) == attributeName }) + + return att.(attribute.KeyValue) +} + +func MapsToAttributes(maps map[string]interface{}) []attribute.KeyValue { + var att []attribute.KeyValue + + for key, val := range maps { + switch val.(type) { + case string: + att = append(att, attribute.String(key, val.(string))) + case int64: + att = append(att, attribute.Int64(key, val.(int64))) + case int, int32: + att = append(att, attribute.Int(key, val.(int))) + case float64, float32: + att = append(att, attribute.Float64(key, val.(float64))) + case bool: + att = append(att, attribute.Bool(key, val.(bool))) + } + } + + return att +} + +func MetadataToSet(meta metadata.Metadata) attribute.Set { + var keyValue []attribute.KeyValue + for key, val := range meta { + keyValue = append(keyValue, attribute.String(key, val.(string))) + } + + return attribute.NewSet(keyValue...) +} diff --git a/internal/pkg/postgresgorm/constants/constants.go b/internal/pkg/postgresgorm/constants/constants.go new file mode 100644 index 00000000..d45da51a --- /dev/null +++ b/internal/pkg/postgresgorm/constants/constants.go @@ -0,0 +1,5 @@ +package constants + +type contextKey string + +const TxKey contextKey = "tx_key" diff --git a/internal/pkg/postgresgorm/contracts/gorm_context.go b/internal/pkg/postgresgorm/contracts/gorm_context.go new file mode 100644 index 00000000..3acbd734 --- /dev/null +++ b/internal/pkg/postgresgorm/contracts/gorm_context.go @@ -0,0 +1,12 @@ +package contracts + +import ( + "context" + + "gorm.io/gorm" +) + +type GormContext struct { + Tx *gorm.DB + context.Context +} diff --git a/internal/pkg/postgresgorm/contracts/gorm_dbcontext.go b/internal/pkg/postgresgorm/contracts/gorm_dbcontext.go new file mode 100644 index 00000000..6a78611b --- /dev/null +++ b/internal/pkg/postgresgorm/contracts/gorm_dbcontext.go @@ -0,0 +1,16 @@ +package contracts + +import ( + "context" + + "gorm.io/gorm" +) + +type GormDBContext interface { + WithTx(ctx context.Context) (GormDBContext, error) + WithTxIfExists(ctx context.Context) GormDBContext + RunInTx(ctx context.Context, action ActionFunc) error + DB() *gorm.DB +} + +type ActionFunc func(ctx context.Context, gormContext GormDBContext) error diff --git a/internal/pkg/postgresgorm/gorm_db.go b/internal/pkg/postgresgorm/gorm_db.go new file mode 100644 index 00000000..0d772e1e --- /dev/null +++ b/internal/pkg/postgresgorm/gorm_db.go @@ -0,0 +1,155 @@ +package postgresgorm + +import ( + "database/sql" + "fmt" + + defaultlogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/gromlog" + + "emperror.dev/errors" + "github.com/glebarez/sqlite" + gormPostgres "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/plugin/opentelemetry/tracing" +) + +func NewGorm(cfg *GormOptions) (*gorm.DB, error) { + if cfg.DBName == "" { + return nil, errors.New("DBName is required in the config.") + } + + if cfg.UseSQLLite { + db, err := createSQLLiteDB(cfg.Dns()) + + return db, err + } + + // InMemory doesn't work correctly with transactions - seems when we `Begin` a transaction on gorm.DB (with SQLLite in-memory) our previous gormDB before transaction will remove and the new gormDB with tx will go on the memory + if cfg.UseInMemory { + db, err := createInMemoryDB() + + return db, err + } + + err := createPostgresDB(cfg) + if err != nil { + return nil, err + } + + dataSourceName := fmt.Sprintf( + "host=%s port=%d user=%s dbname=%s password=%s", + cfg.Host, + cfg.Port, + cfg.User, + cfg.DBName, + cfg.Password, + ) + + gormDb, err := gorm.Open( + gormPostgres.Open(dataSourceName), + &gorm.Config{ + Logger: gromlog.NewGormCustomLogger(defaultlogger.GetLogger()), + }, + ) + if err != nil { + return nil, err + } + + // add tracing to gorm + if cfg.EnableTracing { + err = gormDb.Use(tracing.NewPlugin()) + } + + return gormDb, nil +} + +func createInMemoryDB() (*gorm.DB, error) { + // https://gorm.io/docs/connecting_to_the_database.html#SQLite + // https://github.com/glebarez/sqlite + // https://www.connectionstrings.com/sqlite/ + db, err := gorm.Open( + sqlite.Open(":memory:"), + &gorm.Config{ + Logger: gromlog.NewGormCustomLogger(defaultlogger.GetLogger()), + }) + + return db, err +} + +func createSQLLiteDB(dbFilePath string) (*gorm.DB, error) { + // https://gorm.io/docs/connecting_to_the_database.html#SQLite + // https://github.com/glebarez/sqlite + // https://www.connectionstrings.com/sqlite/ + gormSQLLiteDB, err := gorm.Open( + sqlite.Open(dbFilePath), + &gorm.Config{ + Logger: gromlog.NewGormCustomLogger(defaultlogger.GetLogger()), + }) + + return gormSQLLiteDB, err +} + +func NewSQLDB(orm *gorm.DB) (*sql.DB, error) { + return orm.DB() +} + +func createPostgresDB(cfg *GormOptions) error { + var db *sql.DB + + // we should choose a default database in the connection, but because we don't have a database yet we specify postgres default database 'postgres' + dataSourceName := fmt.Sprintf( + "host=%s port=%d user=%s dbname=%s password=%s", + cfg.Host, + cfg.Port, + cfg.User, + "postgres", + cfg.Password, + ) + postgresGormDB, err := gorm.Open( + gormPostgres.Open(dataSourceName), + &gorm.Config{ + Logger: gromlog.NewGormCustomLogger(defaultlogger.GetLogger()), + }, + ) + if err != nil { + return err + } + + db, err = postgresGormDB.DB() + + if err != nil { + return err + } + + rows, err := db.Query( + fmt.Sprintf( + "SELECT 1 FROM pg_catalog.pg_database WHERE datname='%s'", + cfg.DBName, + ), + ) + if err != nil { + return err + } + + var exists int + if rows.Next() { + err = rows.Scan(&exists) + if err != nil { + return err + } + } + + if exists == 1 { + return nil + } + + _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", cfg.DBName)) + if err != nil { + return err + } + + defer db.Close() + + return nil +} diff --git a/internal/pkg/postgresgorm/gorm_options.go b/internal/pkg/postgresgorm/gorm_options.go new file mode 100644 index 00000000..260d8c6e --- /dev/null +++ b/internal/pkg/postgresgorm/gorm_options.go @@ -0,0 +1,53 @@ +package postgresgorm + +import ( + "fmt" + "path/filepath" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/iancoleman/strcase" +) + +var optionName = strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[GormOptions]()) + +type GormOptions struct { + UseInMemory bool `mapstructure:"useInMemory"` + UseSQLLite bool `mapstructure:"useSqlLite"` + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + User string `mapstructure:"user"` + DBName string `mapstructure:"dbName"` + SSLMode bool `mapstructure:"sslMode"` + Password string `mapstructure:"password"` + EnableTracing bool `mapstructure:"enableTracing" default:"true"` +} + +func (h *GormOptions) Dns() string { + if h.UseInMemory { + return "" + } + + if h.UseSQLLite { + projectRootDir := environment.GetProjectRootWorkingDirectory() + dbFilePath := filepath.Join(projectRootDir, fmt.Sprintf("%s.db", h.DBName)) + + return dbFilePath + } + + datasource := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", + h.User, + h.Password, + h.Host, + h.Port, + h.DBName, + ) + + return datasource +} + +func provideConfig(environment environment.Environment) (*GormOptions, error) { + return config.BindConfigKey[*GormOptions](optionName, environment) +} diff --git a/internal/pkg/gorm_postgres/gorm_options_test.go b/internal/pkg/postgresgorm/gorm_options_test.go similarity index 87% rename from internal/pkg/gorm_postgres/gorm_options_test.go rename to internal/pkg/postgresgorm/gorm_options_test.go index 3240d833..359360cd 100644 --- a/internal/pkg/gorm_postgres/gorm_options_test.go +++ b/internal/pkg/postgresgorm/gorm_options_test.go @@ -1,4 +1,4 @@ -package gormPostgres +package postgresgorm import ( "testing" diff --git a/internal/pkg/gorm_postgres/gorm_postgres_fx.go b/internal/pkg/postgresgorm/gorm_postgres_fx.go similarity index 68% rename from internal/pkg/gorm_postgres/gorm_postgres_fx.go rename to internal/pkg/postgresgorm/gorm_postgres_fx.go index b25441bb..af28109b 100644 --- a/internal/pkg/gorm_postgres/gorm_postgres_fx.go +++ b/internal/pkg/postgresgorm/gorm_postgres_fx.go @@ -1,9 +1,9 @@ -package gormPostgres +package postgresgorm import ( "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/health" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" "go.uber.org/fx" ) @@ -16,9 +16,10 @@ var Module = fx.Module( provideConfig, NewGorm, NewSQLDB, + fx.Annotate( NewGormHealthChecker, - fx.As(new(health.Health)), + fx.As(new(contracts.Health)), fx.ResultTags(fmt.Sprintf(`group:"%s"`, "healths")), ), ), diff --git a/internal/pkg/postgresgorm/gormdbcontext/gorm_dbcontext.go b/internal/pkg/postgresgorm/gormdbcontext/gorm_dbcontext.go new file mode 100644 index 00000000..723c28cd --- /dev/null +++ b/internal/pkg/postgresgorm/gormdbcontext/gorm_dbcontext.go @@ -0,0 +1,93 @@ +package gormdbcontext + +import ( + "context" + + defaultlogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/helpers/gormextensions" + + "gorm.io/gorm" +) + +type gormDBContext struct { + db *gorm.DB +} + +func NewGormDBContext(db *gorm.DB) contracts.GormDBContext { + c := &gormDBContext{db: db} + + return c +} + +func (c *gormDBContext) DB() *gorm.DB { + return c.db +} + +// WithTx creates a transactional DBContext with getting tx-gorm from the ctx. This will throw an error if the transaction does not exist. +func (c *gormDBContext) WithTx( + ctx context.Context, +) (contracts.GormDBContext, error) { + tx, err := gormextensions.GetTxFromContext(ctx) + if err != nil { + return nil, err + } + + return NewGormDBContext(tx), nil +} + +// WithTxIfExists creates a transactional DBContext with getting tx-gorm from the ctx. not throw an error if the transaction is not existing and returns an existing database. +func (c *gormDBContext) WithTxIfExists( + ctx context.Context, +) contracts.GormDBContext { + tx := gormextensions.GetTxFromContextIfExists(ctx) + if tx == nil { + return c + } + + return NewGormDBContext(tx) +} + +func (c *gormDBContext) RunInTx( + ctx context.Context, + action contracts.ActionFunc, +) error { + // https://gorm.io/docs/transactions.html#Transaction + tx := c.DB().WithContext(ctx).Begin() + + defaultlogger.GetLogger().Info("beginning database transaction") + + gormContext := gormextensions.SetTxToContext(ctx, tx) + ctx = gormContext + + defer func() { + if r := recover(); r != nil { + tx.WithContext(ctx).Rollback() + + if err, _ := r.(error); err != nil { + defaultlogger.GetLogger().Errorf( + "panic tn the transaction, rolling back transaction with panic err: %+v", + err, + ) + } else { + defaultlogger.GetLogger().Errorf("panic tn the transaction, rolling back transaction with panic message: %+v", r) + } + } + }() + + err := action(ctx, c) + if err != nil { + defaultlogger.GetLogger().Error("rolling back transaction") + tx.WithContext(ctx).Rollback() + + return err + } + + defaultlogger.GetLogger().Info("committing transaction") + + if err = tx.WithContext(ctx).Commit().Error; err != nil { + defaultlogger.GetLogger().Errorf("transaction commit error: %+v", err) + } + + return err +} diff --git a/internal/pkg/postgresgorm/gormdbcontext/gorm_dbcontext_extensions.go b/internal/pkg/postgresgorm/gormdbcontext/gorm_dbcontext_extensions.go new file mode 100644 index 00000000..83bbbb55 --- /dev/null +++ b/internal/pkg/postgresgorm/gormdbcontext/gorm_dbcontext_extensions.go @@ -0,0 +1,277 @@ +package gormdbcontext + +import ( + "context" + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + defaultlogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/scopes" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/iancoleman/strcase" + uuid "github.com/satori/go.uuid" +) + +func Exists[TDataModel interface{}]( + ctx context.Context, + dbContext contracts.GormDBContext, + id uuid.UUID, +) bool { + var count int64 + + dataModel := typeMapper.GenericInstanceByT[TDataModel]() + + dbContext.DB().WithContext(ctx).Model(dataModel).Scopes(scopes.FilterByID(id)).Count(&count) + + return count > 0 +} + +func FindModelByID[TDataModel interface{}, TModel interface{}]( + ctx context.Context, + dbContext contracts.GormDBContext, + id uuid.UUID, +) (TModel, error) { + var dataModel TDataModel + + // https://gorm.io/docs/query.html#Retrieving-objects-with-primary-key + // https://gorm.io/docs/query.html#Struct-amp-Map-Conditions + // https://gorm.io/docs/query.html#Inline-Condition + // https://gorm.io/docs/advanced_query.html + // result := c.WithContext(ctx).First(&dataModel, "id = ?", id) + // result := c.WithContext(ctx).First(&TDataModel{Id: id}) + // result := c.WithContext(ctx).Scopes(scopes.FilterByID(id)).First(&dataModel) + + modelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TModel]()) + dataModelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TDataModel]()) + + result := dbContext.DB().WithContext(ctx).First(&dataModel, id) + if result.Error != nil { + return *new(TModel), customErrors.NewNotFoundErrorWrap( + result.Error, + fmt.Sprintf( + "%s with id `%s` not found in the database", + dataModelName, + id.String(), + ), + ) + } + + defaultlogger.GetLogger().Infof("Number of affected rows are: %d", result.RowsAffected) + + resultModel, err := mapper.Map[TModel](dataModel) + if err != nil { + return *new(TModel), customErrors.NewInternalServerErrorWrap( + err, + fmt.Sprintf("error in the mapping %s", modelName), + ) + } + + return resultModel, nil +} + +func FindDataModelByID[TDataModel interface{}]( + ctx context.Context, + dbContext contracts.GormDBContext, + id uuid.UUID, +) (TDataModel, error) { + var dataModel TDataModel + + // https://gorm.io/docs/query.html#Retrieving-objects-with-primary-key + // https://gorm.io/docs/query.html#Struct-amp-Map-Conditions + // https://gorm.io/docs/query.html#Inline-Condition + // https://gorm.io/docs/advanced_query.html + // result := c.WithContext(ctx).First(&dataModel, "id = ?", id) + // result := c.WithContext(ctx).First(&TDataModel{Id: id}) + // result := c.WithContext(ctx).Scopes(scopes.FilterByID(id)).First(&dataModel) + + dataModelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TDataModel]()) + + result := dbContext.DB().WithContext(ctx).First(&dataModel, id) + if result.Error != nil { + return *new(TDataModel), customErrors.NewNotFoundErrorWrap( + result.Error, + fmt.Sprintf( + "%s with id `%s` not found in the database", + dataModelName, + id.String(), + ), + ) + } + + defaultlogger.GetLogger().Infof("Number of affected rows are: %d", result.RowsAffected) + + return dataModel, nil +} + +// DeleteDataModelByID delete the data-model inner a tx if exists +func DeleteDataModelByID[TDataModel interface{}]( + ctx context.Context, + dbContext contracts.GormDBContext, + id uuid.UUID, +) error { + txDBContext := dbContext.WithTxIfExists(ctx) + + dataModelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TDataModel]()) + + exists := Exists[TDataModel](ctx, dbContext, id) + if !exists { + return customErrors.NewNotFoundError(fmt.Sprintf("%s with id `%s` not found in the database", + dataModelName, + id.String(), + )) + } + + dataModel := typeMapper.GenericInstanceByT[TDataModel]() + + // https://gorm.io/docs/delete.html#Delete-a-Record + // https://gorm.io/docs/delete.html#Find-soft-deleted-records + // result := dbContext.WithContext(ctx).Delete(&TDataModel{Id: id}) + result := txDBContext.DB().WithContext(ctx).Delete(dataModel, id) + if result.Error != nil { + return customErrors.NewInternalServerErrorWrap( + result.Error, + fmt.Sprintf( + "error in deleting %s with id `%s` in the database", + dataModelName, + id.String(), + ), + ) + } + + defaultlogger.GetLogger().Infof("Number of affected rows are: %d", result.RowsAffected) + + return nil +} + +// AddModel add the model inner a tx if exists +func AddModel[TDataModel interface{}, TModel interface{}]( + ctx context.Context, + dbContext contracts.GormDBContext, + model TModel, +) (TModel, error) { + txDBContext := dbContext.WithTxIfExists(ctx) + + dataModelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TDataModel]()) + modelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TModel]()) + + dataModel, err := mapper.Map[TDataModel](model) + if err != nil { + return *new(TModel), customErrors.NewInternalServerErrorWrap( + err, + fmt.Sprintf("error in the mapping %s", dataModelName), + ) + } + + // https://gorm.io/docs/create.html + result := txDBContext.DB().WithContext(ctx).Create(dataModel) + if result.Error != nil { + return *new(TModel), customErrors.NewConflictErrorWrap( + result.Error, + fmt.Sprintf("%s already exists", modelName), + ) + } + + defaultlogger.GetLogger().Infof("Number of affected rows are: %d", result.RowsAffected) + + resultModel, err := mapper.Map[TModel](dataModel) + if err != nil { + return *new(TModel), customErrors.NewInternalServerErrorWrap( + err, + fmt.Sprintf("error in the mapping %s", modelName), + ) + } + + return resultModel, err +} + +// AddDataModel add the data-model inner a tx if exists +func AddDataModel[TDataModel interface{}]( + ctx context.Context, + dbContext contracts.GormDBContext, + dataModel TDataModel, +) (TDataModel, error) { + txDBContext := dbContext.WithTxIfExists(ctx) + + dataModelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TDataModel]()) + + // https://gorm.io/docs/create.html + result := txDBContext.DB().WithContext(ctx).Create(dataModel) + if result.Error != nil { + return *new(TDataModel), customErrors.NewConflictErrorWrap( + result.Error, + fmt.Sprintf("%s already exists", dataModelName), + ) + } + + defaultlogger.GetLogger().Infof("Number of affected rows are: %d", result.RowsAffected) + + return dataModel, nil +} + +// UpdateModel update the model inner a tx if exists +func UpdateModel[TDataModel interface{}, TModel interface{}]( + ctx context.Context, + dbContext contracts.GormDBContext, + model TModel, +) (TModel, error) { + txDBContext := dbContext.WithTxIfExists(ctx) + + dataModelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TDataModel]()) + modelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TModel]()) + + dataModel, err := mapper.Map[TDataModel](model) + if err != nil { + return *new(TModel), customErrors.NewInternalServerErrorWrap( + err, + fmt.Sprintf("error in the mapping %s", dataModelName), + ) + } + + // https://gorm.io/docs/update.html + result := txDBContext.DB().WithContext(ctx).Updates(dataModel) + if result.Error != nil { + return *new(TModel), customErrors.NewInternalServerErrorWrap( + result.Error, + fmt.Sprintf("error in updating the %s", modelName), + ) + } + + defaultlogger.GetLogger().Infof("Number of affected rows are: %d", result.RowsAffected) + + modelResult, err := mapper.Map[TModel](dataModel) + if err != nil { + return *new(TModel), customErrors.NewInternalServerErrorWrap( + err, + fmt.Sprintf("error in the mapping %s", modelName), + ) + } + + return modelResult, err +} + +// UpdateDataModel update the data-model inner a tx if exists +func UpdateDataModel[TDataModel interface{}]( + ctx context.Context, + dbContext contracts.GormDBContext, + dataModel TDataModel, +) (TDataModel, error) { + txDBContext := dbContext.WithTxIfExists(ctx) + + dataModelName := strcase.ToSnake(typeMapper.GetGenericNonePointerTypeNameByT[TDataModel]()) + + // https://gorm.io/docs/update.html + result := txDBContext.DB().WithContext(ctx).Updates(dataModel) + if result.Error != nil { + return *new(TDataModel), customErrors.NewInternalServerErrorWrap( + result.Error, + fmt.Sprintf("error in updating the %s", dataModelName), + ) + } + + defaultlogger.GetLogger().Infof("Number of affected rows are: %d", result.RowsAffected) + + return dataModel, nil +} diff --git a/internal/pkg/postgresgorm/gormdbcontext/gorm_dbcontext_test.go b/internal/pkg/postgresgorm/gormdbcontext/gorm_dbcontext_test.go new file mode 100644 index 00000000..8c68a526 --- /dev/null +++ b/internal/pkg/postgresgorm/gormdbcontext/gorm_dbcontext_test.go @@ -0,0 +1,327 @@ +//go:build unit +// +build unit + +package gormdbcontext + +import ( + "context" + "os" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/scopes" + + "emperror.dev/errors" + "github.com/brianvoe/gofakeit/v6" + "github.com/goccy/go-json" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/suite" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + "gorm.io/gorm" +) + +// ProductDataModel data model +type ProductDataModel struct { + Id uuid.UUID `gorm:"primaryKey"` + Name string + Description string + Price float64 + CreatedAt time.Time `gorm:"default:current_timestamp"` + UpdatedAt time.Time + // for soft delete - https://gorm.io/docs/delete.html#Soft-Delete + gorm.DeletedAt +} + +// TableName overrides the table name used by ProductDataModel to `products` - https://gorm.io/docs/conventions.html#TableName +func (p *ProductDataModel) TableName() string { + return "products" +} + +func (p *ProductDataModel) String() string { + j, _ := json.Marshal(p) + + return string(j) +} + +// Product model +type Product struct { + Id uuid.UUID + Name string + Description string + Price float64 + CreatedAt time.Time + UpdatedAt time.Time +} + +// Define the suite +type GormDBContextTestSuite struct { + suite.Suite + items []*ProductDataModel + dbContext contracts.GormDBContext + app *fxtest.App + dbFilePath string +} + +// In order for 'go test' to run this suite, we need to create +// a normal test function and pass our suite to suite.Run +func TestGormDBContext(t *testing.T) { + suite.Run(t, new(GormDBContextTestSuite)) +} + +func (s *GormDBContextTestSuite) Test_FindProductByID() { + s.Require().NotNil(s.dbContext) + + id := s.items[0].Id + + p, err := FindModelByID[*ProductDataModel, *Product]( + context.Background(), + s.dbContext, + id, + ) + s.Require().NoError(err) + s.Require().NotNil(p) + + s.Assert().Equal(p.Id, id) +} + +func (s *GormDBContextTestSuite) Test_ExistsProductByID() { + s.Require().NotNil(s.dbContext) + + id := s.items[0].Id + + exist := Exists[*ProductDataModel]( + context.Background(), + s.dbContext, + id, + ) + + s.Require().True(exist) +} + +func (s *GormDBContextTestSuite) Test_NoneExistsProductByID() { + s.Require().NotNil(s.dbContext) + + id := uuid.NewV4() + + exist := Exists[*ProductDataModel]( + context.Background(), + s.dbContext, + id, + ) + + s.Require().False(exist) +} + +func (s *GormDBContextTestSuite) Test_DeleteProductByID() { + s.Require().NotNil(s.dbContext) + + id := s.items[0].Id + + err := DeleteDataModelByID[*ProductDataModel]( + context.Background(), + s.dbContext, + id, + ) + s.Require().NoError(err) + + p, err := FindModelByID[*ProductDataModel, *Product]( + context.Background(), + s.dbContext, + id, + ) + s.Require().Error(err) + s.Require().Nil(p) + + // https://gorm.io/docs/delete.html#Find-soft-deleted-records + var softDeletedProduct *ProductDataModel + s.dbContext.DB().Scopes(scopes.FilterAllItemsWithSoftDeleted).First(&softDeletedProduct, id) + s.Require().NotNil(softDeletedProduct) + + var deletedCount int64 + var allCount int64 + + // https://gorm.io/docs/advanced_query.html#Count + s.dbContext.DB().Model(&ProductDataModel{}).Scopes(scopes.FilterAllItemsWithSoftDeleted).Count(&allCount) + s.Equal(allCount, int64(2)) + + s.dbContext.DB().Model(&ProductDataModel{}).Scopes(scopes.SoftDeleted).Count(&deletedCount) + s.Equal(deletedCount, int64(1)) +} + +func (s *GormDBContextTestSuite) Test_CreateProduct() { + s.Require().NotNil(s.dbContext) + + item := &Product{ + Id: uuid.NewV4(), + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + } + + res, err := AddModel[*ProductDataModel, *Product](context.Background(), s.dbContext, item) + s.Require().NoError(err) + + p, err := FindModelByID[*ProductDataModel, *Product]( + context.Background(), + s.dbContext, + item.Id, + ) + s.Require().NoError(err) + s.Require().NotNil(p) + + s.Assert().Equal(p.Id, item.Id) + s.Assert().Equal(p.Id, res.Id) +} + +func (s *GormDBContextTestSuite) Test_UpdateProduct() { + s.Require().NotNil(s.dbContext) + + id := s.items[0].Id + + p, err := FindModelByID[*ProductDataModel, *Product]( + context.Background(), + s.dbContext, + id, + ) + s.Require().NoError(err) + + newName := gofakeit.Name() + item := p + item.Name = newName + + res, err := UpdateModel[*ProductDataModel, *Product](context.Background(), s.dbContext, item) + s.Require().NoError(err) + + p2, err := FindModelByID[*ProductDataModel, *Product]( + context.Background(), + s.dbContext, + id, + ) + s.Require().NoError(err) + + s.Assert().Equal(item.Name, p2.Name) + s.Assert().Equal(res.Name, p2.Name) +} + +// TestSuite Hooks + +func (s *GormDBContextTestSuite) SetupTest() { + err := ConfigureProductsMappings() + s.Require().NoError(err) + + var gormDBContext contracts.GormDBContext + var gormOptions *gormPostgres.GormOptions + + app := fxtest.New( + s.T(), + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + gormPostgres.Module, + fx.Provide(NewGormDBContext), + fx.Decorate( + func(cfg *gormPostgres.GormOptions) (*gormPostgres.GormOptions, error) { + // using sql-lite with a database file + cfg.UseSQLLite = true + + return cfg, nil + }, + ), + fx.Populate(&gormDBContext), + fx.Populate(&gormOptions), + ).RequireStart() + + s.app = app + s.dbContext = gormDBContext + s.dbFilePath = gormOptions.Dns() + + s.initDB() +} + +func (s *GormDBContextTestSuite) TearDownTest() { + err := s.cleanupDB() + s.Require().NoError(err) + + mapper.ClearMappings() + + s.app.RequireStop() +} + +func (s *GormDBContextTestSuite) initDB() { + err := migrateGorm(s.dbContext.DB()) + s.Require().NoError(err) + + products, err := seedData(s.dbContext.DB()) + s.Require().NoError(err) + + s.items = products +} + +func (s *GormDBContextTestSuite) cleanupDB() error { + sqldb, _ := s.dbContext.DB().DB() + e := sqldb.Close() + s.Require().NoError(e) + + // removing sql-lite file + err := os.Remove(s.dbFilePath) + + return err +} + +func migrateGorm(db *gorm.DB) error { + err := db.AutoMigrate(&ProductDataModel{}) + if err != nil { + return err + } + + return nil +} + +func seedData(gormDB *gorm.DB) ([]*ProductDataModel, error) { + products := []*ProductDataModel{ + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + } + + // seed data + err := gormDB.CreateInBatches(products, len(products)).Error + if err != nil { + return nil, errors.Wrap(err, "error in seed database") + } + + return products, nil +} + +func ConfigureProductsMappings() error { + err := mapper.CreateMap[*ProductDataModel, *Product]() + if err != nil { + return err + } + + err = mapper.CreateMap[*Product, *ProductDataModel]() + if err != nil { + return err + } + + return nil +} diff --git a/internal/pkg/gorm_postgres/health.go b/internal/pkg/postgresgorm/health.go similarity index 67% rename from internal/pkg/gorm_postgres/health.go rename to internal/pkg/postgresgorm/health.go index 16838f76..7e8326f0 100644 --- a/internal/pkg/gorm_postgres/health.go +++ b/internal/pkg/postgresgorm/health.go @@ -1,17 +1,17 @@ -package gormPostgres +package postgresgorm import ( "context" "database/sql" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/health" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" ) type gormHealthChecker struct { client *sql.DB } -func NewGormHealthChecker(client *sql.DB) health.Health { +func NewGormHealthChecker(client *sql.DB) contracts.Health { return &gormHealthChecker{client} } diff --git a/internal/pkg/postgresgorm/helpers/gormextensions/gorm_extensions.go b/internal/pkg/postgresgorm/helpers/gormextensions/gorm_extensions.go new file mode 100644 index 00000000..6b0f73ac --- /dev/null +++ b/internal/pkg/postgresgorm/helpers/gormextensions/gorm_extensions.go @@ -0,0 +1,74 @@ +package gormextensions + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/constants" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/scopes" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + + "emperror.dev/errors" + "gorm.io/gorm" +) + +func GetTxFromContext(ctx context.Context) (*gorm.DB, error) { + gCtx, gCtxOk := ctx.(*contracts.GormContext) + if gCtxOk { + return gCtx.Tx, nil + } + + tx, ok := ctx.Value(constants.TxKey).(*gorm.DB) + if !ok { + return nil, errors.New("Transaction not found in context") + } + + return tx, nil +} + +func GetTxFromContextIfExists(ctx context.Context) *gorm.DB { + gCtx, gCtxOk := ctx.(*contracts.GormContext) + if gCtxOk { + return gCtx.Tx + } + + tx, ok := ctx.Value(constants.TxKey).(*gorm.DB) + if !ok { + return nil + } + + return tx +} + +func SetTxToContext(ctx context.Context, tx *gorm.DB) *contracts.GormContext { + newCtx := context.WithValue(ctx, constants.TxKey, tx) + gormContext := &contracts.GormContext{Tx: tx, Context: newCtx} + ctx = gormContext + + return gormContext +} + +// Ref: https://dev.to/rafaelgfirmino/pagination-using-gorm-scopes-3k5f + +func Paginate[TDataModel any, TEntity any]( + ctx context.Context, + listQuery *utils.ListQuery, + db *gorm.DB, +) (*utils.ListResult[TEntity], error) { + var ( + items []TEntity + totalRows int64 + ) + + // https://gorm.io/docs/advanced_query.html#Smart-Select-Fields + if err := db.Scopes(scopes.FilterPaginate[TDataModel](ctx, listQuery)).Find(&items).Error; err != nil { + return nil, errors.WrapIf(err, "error in finding products.") + } + + return utils.NewListResult[TEntity]( + items, + listQuery.GetSize(), + listQuery.GetPage(), + totalRows, + ), nil +} diff --git a/internal/pkg/postgresgorm/pipelines/mediator_transaction_pipeline.go b/internal/pkg/postgresgorm/pipelines/mediator_transaction_pipeline.go new file mode 100644 index 00000000..9eab3fb7 --- /dev/null +++ b/internal/pkg/postgresgorm/pipelines/mediator_transaction_pipeline.go @@ -0,0 +1,94 @@ +package pipelines + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/helpers/gormextensions" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/mehdihadeli/go-mediatr" + "gorm.io/gorm" +) + +type mediatorTransactionPipeline struct { + logger logger.Logger + db *gorm.DB +} + +func NewMediatorTransactionPipeline( + l logger.Logger, + db *gorm.DB, +) mediatr.PipelineBehavior { + return &mediatorTransactionPipeline{ + logger: l, + db: db, + } +} + +func (m *mediatorTransactionPipeline) Handle( + ctx context.Context, + request interface{}, + next mediatr.RequestHandlerFunc, +) (interface{}, error) { + requestName := typeMapper.GetSnakeTypeName(request) + + _, ok := request.(cqrs.TxRequest) + if !ok { + return next(ctx) + } + + var result interface{} + + // https://gorm.io/docs/transactions.html#Transaction + tx := m.db.WithContext(ctx).Begin() + + m.logger.Infof( + "beginning database transaction for request `%s`", + requestName, + ) + + gormContext := gormextensions.SetTxToContext(ctx, tx) + ctx = gormContext + + defer func() { + if r := recover(); r != nil { + tx.WithContext(ctx).Rollback() + + if err, _ := r.(error); err != nil { + m.logger.Errorf( + "panic tn the transaction, rolling back transaction with panic err: %+v", + err, + ) + } else { + m.logger.Errorf("panic tn the transaction, rolling back transaction with panic message: %+v", r) + } + } + }() + + middlewareResponse, err := next(ctx) + result = middlewareResponse + + if err != nil { + m.logger.Errorf( + "rolling back transaction for request `%s`", + requestName, + ) + tx.WithContext(ctx).Rollback() + + return nil, err + } + + m.logger.Infof("committing transaction for request `%s`", requestName) + + if err = tx.WithContext(ctx).Commit().Error; err != nil { + m.logger.Errorf("transaction commit error: ", err) + } + + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/internal/pkg/gorm_postgres/repository/gorm_generic_repository.go b/internal/pkg/postgresgorm/repository/gorm_generic_repository.go similarity index 55% rename from internal/pkg/gorm_postgres/repository/gorm_generic_repository.go rename to internal/pkg/postgresgorm/repository/gorm_generic_repository.go index f0809770..48650a99 100644 --- a/internal/pkg/gorm_postgres/repository/gorm_generic_repository.go +++ b/internal/pkg/postgresgorm/repository/gorm_generic_repository.go @@ -4,15 +4,16 @@ import ( "context" "fmt" "reflect" + "strings" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/data/specification" - gormPostgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/gorm_postgres" - customErrors "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/custom_errors" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mapper" - reflectionHelper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/reflection_helper" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/data/specification" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/helpers/gormextensions" + reflectionHelper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/reflectionhelper" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" "emperror.dev/errors" "github.com/iancoleman/strcase" @@ -35,21 +36,27 @@ func NewGenericGormRepositoryWithDataModel[TDataModel interface{}, TEntity inter } // NewGenericGormRepository create new gorm generic repository -func NewGenericGormRepository[TEntity interface{}](db *gorm.DB) data.GenericRepository[TEntity] { +func NewGenericGormRepository[TEntity interface{}]( + db *gorm.DB, +) data.GenericRepository[TEntity] { return &gormGenericRepository[TEntity, TEntity]{ db: db, } } -func (r *gormGenericRepository[TDataModel, TEntity]) Add(ctx context.Context, entity TEntity) error { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() +func (r *gormGenericRepository[TDataModel, TEntity]) Add( + ctx context.Context, + entity TEntity, +) error { + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() if modelType == dataModelType { err := r.db.WithContext(ctx).Create(entity).Error if err != nil { return err } + return nil } else { dataModel, err := mapper.Map[TDataModel](entity) @@ -66,10 +73,14 @@ func (r *gormGenericRepository[TDataModel, TEntity]) Add(ctx context.Context, en } reflectionHelper.SetValue[TEntity](entity, e) } + return nil } -func (r *gormGenericRepository[TDataModel, TEntity]) AddAll(ctx context.Context, entities []TEntity) error { +func (r *gormGenericRepository[TDataModel, TEntity]) AddAll( + ctx context.Context, + entities []TEntity, +) error { for _, entity := range entities { err := r.Add(ctx, entity) if err != nil { @@ -80,23 +91,35 @@ func (r *gormGenericRepository[TDataModel, TEntity]) AddAll(ctx context.Context, return nil } -func (r *gormGenericRepository[TDataModel, TEntity]) GetById(ctx context.Context, id uuid.UUID) (TEntity, error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() +func (r *gormGenericRepository[TDataModel, TEntity]) GetById( + ctx context.Context, + id uuid.UUID, +) (TEntity, error) { + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() + if modelType == dataModelType { var model TEntity if err := r.db.WithContext(ctx).First(&model, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return *new(TEntity), customErrors.NewNotFoundErrorWrap( err, - fmt.Sprintf("can't find the entity with id %s into the database.", id.String()), + fmt.Sprintf( + "can't find the entity with id %s into the database.", + id.String(), + ), ) } + return *new(TEntity), errors.WrapIf( err, - fmt.Sprintf("can't find the entity with id %s into the database.", id.String()), + fmt.Sprintf( + "can't find the entity with id %s into the database.", + id.String(), + ), ) } + return model, nil } else { var dataModel TDataModel @@ -104,12 +127,14 @@ func (r *gormGenericRepository[TDataModel, TEntity]) GetById(ctx context.Context if errors.Is(err, gorm.ErrRecordNotFound) { return *new(TEntity), customErrors.NewNotFoundErrorWrap(err, fmt.Sprintf("can't find the entity with id %s into the database.", id.String())) } + return *new(TEntity), errors.WrapIf(err, fmt.Sprintf("can't find the entity with id %s into the database.", id.String())) } entity, err := mapper.Map[TEntity](dataModel) if err != nil { return *new(TEntity), err } + return entity, nil } } @@ -118,25 +143,16 @@ func (r *gormGenericRepository[TDataModel, TEntity]) GetAll( ctx context.Context, listQuery *utils.ListQuery, ) (*utils.ListResult[TEntity], error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() - if modelType == dataModelType { - result, err := gormPostgres.Paginate[TEntity](ctx, listQuery, r.db) - if err != nil { - return nil, err - } - return result, nil - } else { - result, err := gormPostgres.Paginate[TDataModel](ctx, listQuery, r.db) - if err != nil { - return nil, err - } - models, err := utils.ListResultToListResultDto[TEntity](result) - if err != nil { - return nil, err - } - return models, nil + result, err := gormPostgres.Paginate[TDataModel, TEntity]( + ctx, + listQuery, + r.db, + ) + if err != nil { + return nil, err } + + return result, nil } func (r *gormGenericRepository[TDataModel, TEntity]) Search( @@ -144,56 +160,40 @@ func (r *gormGenericRepository[TDataModel, TEntity]) Search( searchTerm string, listQuery *utils.ListQuery, ) (*utils.ListResult[TEntity], error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() - if modelType == dataModelType { - fields := reflectionHelper.GetAllFields(typeMapper.GetTypeFromGeneric[TEntity]()) - query := r.db + fields := reflectionHelper.GetAllFields( + typeMapper.GetGenericTypeByT[TDataModel](), + ) + query := r.db - for _, field := range fields { - if field.Type.Kind() != reflect.String { - continue - } - f := strcase.ToSnake(field.Name) - whereQuery := fmt.Sprintf("%s IN (?)", f) - query = r.db.Where(whereQuery, searchTerm) + for _, field := range fields { + if field.Type.Kind() != reflect.String { + continue } - result, err := gormPostgres.Paginate[TEntity](ctx, listQuery, query) - if err != nil { - return nil, err - } - return result, nil - } else { - query := r.db - fields := reflectionHelper.GetAllFields(typeMapper.GetTypeFromGeneric[TDataModel]()) + query = query.Or( + fmt.Sprintf("%s LIKE ?", strcase.ToSnake(field.Name)), + "%"+strings.ToLower(searchTerm)+"%", + ) + } - for _, field := range fields { - if field.Type.Kind() != reflect.String { - continue - } - f := strcase.ToSnake(field.Name) - whereQuery := fmt.Sprintf("%s IN (?)", f) - query = r.db.WithContext(ctx).Where(whereQuery, searchTerm) - } - result, err := gormPostgres.Paginate[TDataModel](ctx, listQuery, query) - if err != nil { - return nil, err - } - models, err := utils.ListResultToListResultDto[TEntity](result) - if err != nil { - return nil, err - } - return models, nil + result, err := gormPostgres.Paginate[TDataModel, TEntity]( + ctx, + listQuery, + query, + ) + if err != nil { + return nil, err } + + return result, nil } func (r *gormGenericRepository[TDataModel, TEntity]) GetByFilter( ctx context.Context, filters map[string]interface{}, ) ([]TEntity, error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() if modelType == dataModelType { var models []TEntity err := r.db.WithContext(ctx).Where(filters).Find(&models).Error @@ -229,9 +229,12 @@ func (r *gormGenericRepository[TDataModel, TEntity]) FirstOrDefault( return *new(TEntity), nil } -func (r *gormGenericRepository[TDataModel, TEntity]) Update(ctx context.Context, entity TEntity) error { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() +func (r *gormGenericRepository[TDataModel, TEntity]) Update( + ctx context.Context, + entity TEntity, +) error { + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() if modelType == dataModelType { err := r.db.WithContext(ctx).Save(entity).Error if err != nil { @@ -256,7 +259,10 @@ func (r *gormGenericRepository[TDataModel, TEntity]) Update(ctx context.Context, return nil } -func (r gormGenericRepository[TDataModel, TEntity]) UpdateAll(ctx context.Context, entities []TEntity) error { +func (r *gormGenericRepository[TDataModel, TEntity]) UpdateAll( + ctx context.Context, + entities []TEntity, +) error { for _, e := range entities { err := r.Update(ctx, e) if err != nil { @@ -267,7 +273,10 @@ func (r gormGenericRepository[TDataModel, TEntity]) UpdateAll(ctx context.Contex return nil } -func (r *gormGenericRepository[TDataModel, TEntity]) Delete(ctx context.Context, id uuid.UUID) error { +func (r *gormGenericRepository[TDataModel, TEntity]) Delete( + ctx context.Context, + id uuid.UUID, +) error { entity, err := r.GetById(ctx, id) if err != nil { return err @@ -286,11 +295,15 @@ func (r *gormGenericRepository[TDataModel, TEntity]) SkipTake( skip int, take int, ) ([]TEntity, error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() if modelType == dataModelType { var models []TEntity - err := r.db.WithContext(ctx).Offset(skip).Limit(take).Find(&models).Error + err := r.db.WithContext(ctx). + Offset(skip). + Limit(take). + Find(&models). + Error if err != nil { return nil, err } @@ -309,7 +322,9 @@ func (r *gormGenericRepository[TDataModel, TEntity]) SkipTake( } } -func (r *gormGenericRepository[TDataModel, TEntity]) Count(ctx context.Context) int64 { +func (r *gormGenericRepository[TDataModel, TEntity]) Count( + ctx context.Context, +) int64 { var dataModel TDataModel var count int64 r.db.WithContext(ctx).Model(&dataModel).Count(&count) @@ -320,11 +335,14 @@ func (r *gormGenericRepository[TDataModel, TEntity]) Find( ctx context.Context, specification specification.Specification, ) ([]TEntity, error) { - dataModelType := typeMapper.GetTypeFromGeneric[TDataModel]() - modelType := typeMapper.GetTypeFromGeneric[TEntity]() + dataModelType := typeMapper.GetGenericTypeByT[TDataModel]() + modelType := typeMapper.GetGenericTypeByT[TEntity]() if modelType == dataModelType { var models []TEntity - err := r.db.WithContext(ctx).Where(specification.GetQuery(), specification.GetValues()...).Find(&models).Error + err := r.db.WithContext(ctx). + Where(specification.GetQuery(), specification.GetValues()...). + Find(&models). + Error if err != nil { return nil, err } diff --git a/internal/pkg/postgresgorm/repository/gorm_generic_repository_test.go b/internal/pkg/postgresgorm/repository/gorm_generic_repository_test.go new file mode 100644 index 00000000..4f962d77 --- /dev/null +++ b/internal/pkg/postgresgorm/repository/gorm_generic_repository_test.go @@ -0,0 +1,496 @@ +package repository + +import ( + "context" + "log" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/data" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + gorm2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/gorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "gorm.io/gorm" + + _ "github.com/lib/pq" // postgres driver +) + +// Product is a domain_events entity +type Product struct { + ID uuid.UUID + Name string + Weight int + IsAvailable bool +} + +// ProductGorm is DTO used to map Product entity to database +type ProductGorm struct { + ID uuid.UUID `gorm:"primaryKey;column:id"` + Name string `gorm:"column:name"` + Weight int `gorm:"column:weight"` + IsAvailable bool `gorm:"column:is_available"` +} + +func (v *ProductGorm) TableName() string { + return "products_gorm" +} + +type gormGenericRepositoryTest struct { + suite.Suite + DB *gorm.DB + productRepository data.GenericRepository[*ProductGorm] + productRepositoryWithDataModel data.GenericRepositoryWithDataModel[*ProductGorm, *Product] + products []*ProductGorm +} + +func TestGormGenericRepository(t *testing.T) { + suite.Run( + t, + &gormGenericRepositoryTest{}, + ) +} + +func (c *gormGenericRepositoryTest) SetupSuite() { + opts, err := gorm2.NewGormTestContainers(defaultLogger.GetLogger()). + PopulateContainerOptions(context.Background(), c.T()) + c.Require().NoError(err) + + gormDB, err := postgresgorm.NewGorm(opts) + c.Require().NoError(err) + c.DB = gormDB + + err = migrationDatabase(gormDB) + c.Require().NoError(err) + + c.productRepository = NewGenericGormRepository[*ProductGorm](gormDB) + c.productRepositoryWithDataModel = NewGenericGormRepositoryWithDataModel[*ProductGorm, *Product]( + gormDB, + ) + + err = mapper.CreateMap[*ProductGorm, *Product]() + if err != nil { + log.Fatal(err) + } + + err = mapper.CreateMap[*Product, *ProductGorm]() + if err != nil { + log.Fatal(err) + } +} + +func (c *gormGenericRepositoryTest) SetupTest() { + p, err := seedData(context.Background(), c.DB) + c.Require().NoError(err) + c.products = p +} + +func (c *gormGenericRepositoryTest) TearDownTest() { + err := c.cleanupPostgresData() + c.Require().NoError(err) +} + +func (c *gormGenericRepositoryTest) Test_Add() { + ctx := context.Background() + + product := &ProductGorm{ + ID: uuid.NewV4(), + Name: gofakeit.Name(), + Weight: gofakeit.Number(100, 1000), + IsAvailable: true, + } + + err := c.productRepository.Add(ctx, product) + c.Require().NoError(err) + + p, err := c.productRepository.GetById(ctx, product.ID) + if err != nil { + return + } + + c.Assert().NotNil(p) + c.Assert().Equal(product.ID, p.ID) +} + +func (c *gormGenericRepositoryTest) Test_Add_With_Data_Model() { + ctx := context.Background() + + product := &Product{ + ID: uuid.NewV4(), + Name: gofakeit.Name(), + Weight: gofakeit.Number(100, 1000), + IsAvailable: true, + } + + err := c.productRepositoryWithDataModel.Add(ctx, product) + c.Require().NoError(err) + + p, err := c.productRepositoryWithDataModel.GetById(ctx, product.ID) + if err != nil { + return + } + + c.Assert().NotNil(p) + c.Assert().Equal(product.ID, p.ID) +} + +func (c *gormGenericRepositoryTest) Test_Get_By_Id() { + ctx := context.Background() + + all, err := c.productRepository.GetAll(ctx, utils.NewListQuery(10, 1)) + c.Require().NoError(err) + + p := all.Items[0] + + testCases := []struct { + Name string + ProductId uuid.UUID + ExpectResult *ProductGorm + }{ + { + Name: p.Name, + ProductId: p.ID, + ExpectResult: p, + }, + { + Name: "NonExistingProduct", + ProductId: uuid.NewV4(), + ExpectResult: nil, + }, + } + + for _, s := range testCases { + c.T().Run(s.Name, func(t *testing.T) { + t.Parallel() + res, err := c.productRepository.GetById(ctx, s.ProductId) + if s.ExpectResult == nil { + assert.Error(t, err) + assert.True(t, customErrors.IsNotFoundError(err)) + assert.Nil(t, res) + } else { + assert.NoError(t, err) + assert.NotNil(t, res) + assert.Equal(t, p.ID, res.ID) + } + }) + } +} + +func (c *gormGenericRepositoryTest) Test_Get_By_Id_With_Data_Model() { + ctx := context.Background() + + all, err := c.productRepositoryWithDataModel.GetAll( + ctx, + utils.NewListQuery(10, 1), + ) + if err != nil { + return + } + p := all.Items[0] + + testCases := []struct { + Name string + ProductId uuid.UUID + ExpectResult *Product + }{ + { + Name: p.Name, + ProductId: p.ID, + ExpectResult: p, + }, + { + Name: "NonExistingProduct", + ProductId: uuid.NewV4(), + ExpectResult: nil, + }, + } + + for _, s := range testCases { + c.T().Run(s.Name, func(t *testing.T) { + t.Parallel() + res, err := c.productRepositoryWithDataModel.GetById( + ctx, + s.ProductId, + ) + + if s.ExpectResult == nil { + assert.Error(t, err) + assert.True(t, customErrors.IsNotFoundError(err)) + assert.Nil(t, res) + } else { + assert.NoError(t, err) + assert.NotNil(t, res) + assert.Equal(t, p.ID, res.ID) + } + }) + } +} + +func (c *gormGenericRepositoryTest) Test_Get_All() { + ctx := context.Background() + + models, err := c.productRepository.GetAll(ctx, utils.NewListQuery(10, 1)) + c.Require().NoError(err) + + c.Assert().NotEmpty(models.Items) +} + +func (c *gormGenericRepositoryTest) Test_Get_All_With_Data_Model() { + ctx := context.Background() + + models, err := c.productRepositoryWithDataModel.GetAll( + ctx, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) + + c.Assert().NotEmpty(models.Items) +} + +func (c *gormGenericRepositoryTest) Test_Search() { + ctx := context.Background() + + models, err := c.productRepository.Search( + ctx, + c.products[0].Name, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) + + c.Assert().NotEmpty(models.Items) + c.Assert().Equal(len(models.Items), 1) +} + +func (c *gormGenericRepositoryTest) Test_Search_With_Data_Model() { + ctx := context.Background() + + models, err := c.productRepositoryWithDataModel.Search( + ctx, + c.products[0].Name, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) + + c.Assert().NotEmpty(models.Items) + c.Assert().Equal(len(models.Items), 1) +} + +func (c *gormGenericRepositoryTest) Test_Where() { + ctx := context.Background() + + models, err := c.productRepository.GetByFilter( + ctx, + map[string]interface{}{"name": c.products[0].Name}, + ) + c.Require().NoError(err) + + c.Assert().NotEmpty(models) + c.Assert().Equal(len(models), 1) +} + +func (c *gormGenericRepositoryTest) Test_Where_With_Data_Model() { + ctx := context.Background() + + models, err := c.productRepositoryWithDataModel.GetByFilter( + ctx, + map[string]interface{}{"name": c.products[0].Name}, + ) + c.Require().NoError(err) + + c.Assert().NotEmpty(models) + c.Assert().Equal(len(models), 1) +} + +func (c *gormGenericRepositoryTest) Test_Update() { + ctx := context.Background() + + products, err := c.productRepository.GetAll(ctx, utils.NewListQuery(10, 1)) + c.Require().NoError(err) + + product := products.Items[0] + + product.Name = "product2_updated" + err = c.productRepository.Update(ctx, product) + c.Require().NoError(err) + + single, err := c.productRepository.GetById(ctx, product.ID) + c.Require().NoError(err) + + c.Assert().NotNil(single) + c.Assert().Equal("product2_updated", single.Name) +} + +func (c *gormGenericRepositoryTest) Test_Update_With_Data_Model() { + ctx := context.Background() + + products, err := c.productRepositoryWithDataModel.GetAll( + ctx, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) + + product := products.Items[0] + + product.Name = "product2_updated" + err = c.productRepositoryWithDataModel.Update(ctx, product) + c.Require().NoError(err) + + single, err := c.productRepositoryWithDataModel.GetById(ctx, product.ID) + c.Require().NoError(err) + + c.Assert().NotNil(single) + c.Assert().Equal("product2_updated", single.Name) +} + +//func Test_Delete(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericGormRepository(ctx, t) +// +// products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) +// if err != nil { +// t.Fatal(err) +// } +// product := products.Items[0] +// +// err = repository.Delete(ctx, product.ID) +// if err != nil { +// return +// } +// +// single, err := repository.GetById(ctx, product.ID) +// assert.Nil(t, single) +//} +// +//func Test_Delete_With_Data_Model(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) +// +// products, err := repository.GetAll(ctx, utils.NewListQuery(10, 1)) +// if err != nil { +// t.Fatal(err) +// } +// product := products.Items[0] +// +// err = repository.Delete(ctx, product.ID) +// if err != nil { +// return +// } +// +// single, err := repository.GetById(ctx, product.ID) +// assert.Nil(t, single) +//} +// +//func Test_Count(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericGormRepository(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// count := repository.Count(ctx) +// +// assert.Equal(t, count, int64(2)) +//} +// +//func Test_Count_With_Data_Model(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// count := repository.Count(ctx) +// +// assert.Equal(t, count, int64(2)) +//} +// +//func Test_Find(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericGormRepository(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// entities, err := repository.Find( +// ctx, +// specification.And( +// specification.Equal("is_available", true), +// specification.Equal("name", "seed_product1"), +// ), +// ) +// if err != nil { +// return +// } +// assert.Equal(t, len(entities), 1) +//} +// +//func Test_Find_With_Data_Model(t *testing.T) { +// ctx := context.Background() +// repository, err := setupGenericGormRepositoryWithDataModel(ctx, t) +// if err != nil { +// t.Fatal(err) +// } +// +// entities, err := repository.Find( +// ctx, +// specification.And( +// specification.Equal("is_available", true), +// specification.Equal("name", "seed_product1"), +// ), +// ) +// if err != nil { +// return +// } +// assert.Equal(t, len(entities), 1) +//} + +func (c *gormGenericRepositoryTest) cleanupPostgresData() error { + tables := []string{"products_gorm"} + // Iterate over the tables and delete all records + for _, table := range tables { + err := c.DB.Exec("DELETE FROM " + table).Error + + return err + } + + return nil +} + +func migrationDatabase(db *gorm.DB) error { + err := db.AutoMigrate(ProductGorm{}) + if err != nil { + return err + } + + return nil +} + +func seedData(ctx context.Context, db *gorm.DB) ([]*ProductGorm, error) { + seedProducts := []*ProductGorm{ + { + ID: uuid.NewV4(), + Name: "seed_product1", + Weight: 100, + IsAvailable: true, + }, + { + ID: uuid.NewV4(), + Name: "seed_product2", + Weight: 100, + IsAvailable: true, + }, + } + + err := db.WithContext(ctx).Create(seedProducts).Error + if err != nil { + return nil, err + } + + return seedProducts, nil +} diff --git a/internal/pkg/postgresgorm/scopes/scopes.go b/internal/pkg/postgresgorm/scopes/scopes.go new file mode 100644 index 00000000..e44d47e5 --- /dev/null +++ b/internal/pkg/postgresgorm/scopes/scopes.go @@ -0,0 +1,88 @@ +package scopes + +import ( + "context" + "fmt" + "strings" + + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + + uuid "github.com/satori/go.uuid" + "gorm.io/gorm" +) + +// https://gorm.io/docs/advanced_query.html#Scopes +// https://gorm.io/docs/scopes.html + +// After scopes, we should have a runner function like Find, Update, Delete + +func AmountGreaterThan1000(db *gorm.DB) *gorm.DB { + return db.Where("amount > ?", 1000) +} + +// FilterAllItemsWithSoftDeleted returns soft-deleted and none soft-deleted items +func FilterAllItemsWithSoftDeleted(db *gorm.DB) *gorm.DB { + // https://gorm.io/docs/delete.html#Find-soft-deleted-records + return db.Unscoped() +} + +// SoftDeleted returns only soft-deleted items +func SoftDeleted(db *gorm.DB) *gorm.DB { + return db.Unscoped().Where("deleted_at IS NOT NULL") +} + +func FilterByTitle(title string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where("title = ?", title) + } +} + +func FilterByID(id uuid.UUID) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Where("id = ?", id) + } +} + +func FilterPaginate[TDataModel any]( + ctx context.Context, + listQuery *utils.ListQuery, +) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + var totalRows int64 + + dataModel := typeMapper.GenericInstanceByT[TDataModel]() + // https://gorm.io/docs/advanced_query.html + db.WithContext(ctx).Model(dataModel).Count(&totalRows) + + // generate where query + query := db.WithContext(ctx). + Model(dataModel). + Offset(listQuery.GetOffset()). + Limit(listQuery.GetLimit()). + Order(listQuery.GetOrderBy()) + + if listQuery.Filters != nil { + for _, filter := range listQuery.Filters { + column := filter.Field + action := filter.Comparison + value := filter.Value + + switch action { + case "equals": + whereQuery := fmt.Sprintf("%s = ?", column) + query = query.Where(whereQuery, value) + case "contains": + whereQuery := fmt.Sprintf("%s LIKE ?", column) + query = query.Where(whereQuery, "%"+value+"%") + case "in": + whereQuery := fmt.Sprintf("%s IN (?)", column) + queryArray := strings.Split(value, ",") + query = query.Where(whereQuery, queryArray) + } + } + } + + return query + } +} diff --git a/internal/pkg/postgresmessaging/messagepersistence/message_persistence_dbcontext.go b/internal/pkg/postgresmessaging/messagepersistence/message_persistence_dbcontext.go new file mode 100644 index 00000000..e452507c --- /dev/null +++ b/internal/pkg/postgresmessaging/messagepersistence/message_persistence_dbcontext.go @@ -0,0 +1,22 @@ +package messagepersistence + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + + "gorm.io/gorm" +) + +type PostgresMessagePersistenceDBContext struct { + // our dbcontext base + contracts.GormDBContext +} + +func NewPostgresMessagePersistenceDBContext( + db *gorm.DB, +) *PostgresMessagePersistenceDBContext { + // initialize base GormContext + c := &PostgresMessagePersistenceDBContext{GormDBContext: gormdbcontext.NewGormDBContext(db)} + + return c +} diff --git a/internal/pkg/postgresmessaging/messagepersistence/postgres_message_service.go b/internal/pkg/postgresmessaging/messagepersistence/postgres_message_service.go new file mode 100644 index 00000000..8658b291 --- /dev/null +++ b/internal/pkg/postgresmessaging/messagepersistence/postgres_message_service.go @@ -0,0 +1,286 @@ +package messagepersistence + +import ( + "context" + "errors" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/persistmessage" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + + uuid "github.com/satori/go.uuid" +) + +type postgresMessagePersistenceService struct { + messagingDBContext *PostgresMessagePersistenceDBContext + messageSerializer serializer.MessageSerializer + logger logger.Logger +} + +func (m *postgresMessagePersistenceService) Process(messageID string, ctx context.Context) error { + // TODO implement me + panic("implement me") +} + +func (m *postgresMessagePersistenceService) ProcessAll(ctx context.Context) error { + // TODO implement me + panic("implement me") +} + +func (m *postgresMessagePersistenceService) AddPublishMessage( + messageEnvelope types.MessageEnvelope, + ctx context.Context, +) error { + // TODO implement me + panic("implement me") +} + +func (m *postgresMessagePersistenceService) AddReceivedMessage( + messageEnvelope types.MessageEnvelope, + ctx context.Context, +) error { + // TODO implement me + panic("implement me") +} + +func (m *postgresMessagePersistenceService) AddMessageCore( + ctx context.Context, + messageEnvelope types.MessageEnvelope, + deliveryType persistmessage.MessageDeliveryType, +) error { + if messageEnvelope.Message == nil { + return errors.New("messageEnvelope.Message is nil") + } + + var id string + switch message := messageEnvelope.Message.(type) { + case types.IMessage: + id = message.GeMessageId() + // case IInternalCommand: + // id = message.InternalCommandId + default: + id = uuid.NewV4().String() + } + + data, err := m.messageSerializer.SerializeEnvelop(messageEnvelope) + if err != nil { + return err + } + + uuidId, err := uuid.FromString(id) + if err != nil { + return err + } + + storeMessage := persistmessage.NewStoreMessage( + uuidId, + messageEnvelope.Message.GetMessageFullTypeName(), + string(data.Data), + deliveryType, + ) + + err = m.Add(ctx, storeMessage) + if err != nil { + return err + } + + m.logger.Infof( + "Message with id: %v and delivery type: %v saved in persistence message store", + id, + deliveryType, + ) + + return nil +} + +func NewPostgresMessageService( + postgresMessagePersistenceDBContext *PostgresMessagePersistenceDBContext, + l logger.Logger, +) persistmessage.MessagePersistenceService { + return &postgresMessagePersistenceService{ + messagingDBContext: postgresMessagePersistenceDBContext, + logger: l, + } +} + +func (m *postgresMessagePersistenceService) Add( + ctx context.Context, + storeMessage *persistmessage.StoreMessage, +) error { + dbContext := m.messagingDBContext.WithTxIfExists(ctx) + + // https://gorm.io/docs/create.html + result := dbContext.DB().Create(storeMessage) + if result.Error != nil { + return customErrors.NewConflictErrorWrap( + result.Error, + "storeMessage already exists", + ) + } + + m.logger.Infof("Number of affected rows are: %d", result.RowsAffected) + + return nil +} + +func (m *postgresMessagePersistenceService) Update( + ctx context.Context, + storeMessage *persistmessage.StoreMessage, +) error { + dbContext := m.messagingDBContext.WithTxIfExists(ctx) + + // https://gorm.io/docs/update.html + result := dbContext.DB().Updates(storeMessage) + if result.Error != nil { + return customErrors.NewInternalServerErrorWrap( + result.Error, + "error in updating the storeMessage", + ) + } + + m.logger.Infof("Number of affected rows are: %d", result.RowsAffected) + + return nil +} + +func (m *postgresMessagePersistenceService) ChangeState( + ctx context.Context, + messageID uuid.UUID, + status persistmessage.MessageStatus, +) error { + storeMessage, err := m.GetById(ctx, messageID) + if err != nil { + return customErrors.NewNotFoundErrorWrap( + err, + fmt.Sprintf( + "storeMessage with id `%s` not found in the database", + messageID.String(), + ), + ) + } + + storeMessage.MessageStatus = status + err = m.Update(ctx, storeMessage) + + return err +} + +func (m *postgresMessagePersistenceService) GetAllActive( + ctx context.Context, +) ([]*persistmessage.StoreMessage, error) { + var storeMessages []*persistmessage.StoreMessage + + predicate := func(sm *persistmessage.StoreMessage) bool { + return sm.MessageStatus == persistmessage.Stored + } + + dbContext := m.messagingDBContext.WithTxIfExists(ctx) + result := dbContext.DB().Where(predicate).Find(&storeMessages) + if result.Error != nil { + return nil, result.Error + } + + return storeMessages, nil +} + +func (m *postgresMessagePersistenceService) GetByFilter( + ctx context.Context, + predicate func(*persistmessage.StoreMessage) bool, +) ([]*persistmessage.StoreMessage, error) { + var storeMessages []*persistmessage.StoreMessage + + dbContext := m.messagingDBContext.WithTxIfExists(ctx) + result := dbContext.DB().Where(predicate).Find(&storeMessages) + + if result.Error != nil { + return nil, result.Error + } + + return storeMessages, nil +} + +func (m *postgresMessagePersistenceService) GetById( + ctx context.Context, + id uuid.UUID, +) (*persistmessage.StoreMessage, error) { + var storeMessage *persistmessage.StoreMessage + + // https://gorm.io/docs/query.html#Retrieving-objects-with-primary-key + // https://gorm.io/docs/query.html#Struct-amp-Map-Conditions + // https://gorm.io/docs/query.html#Inline-Condition + // https://gorm.io/docs/advanced_query.html + result := m.messagingDBContext.DB().Find(&storeMessage, id) + if result.Error != nil { + return nil, customErrors.NewNotFoundErrorWrap( + result.Error, + fmt.Sprintf( + "storeMessage with id `%s` not found in the database", + id.String(), + ), + ) + } + + m.logger.Infof("Number of affected rows are: %d", result.RowsAffected) + + return storeMessage, nil +} + +func (m *postgresMessagePersistenceService) Remove( + ctx context.Context, + storeMessage *persistmessage.StoreMessage, +) (bool, error) { + id := storeMessage.ID + + storeMessage, err := m.GetById(ctx, id) + if err != nil { + return false, customErrors.NewNotFoundErrorWrap( + err, + fmt.Sprintf( + "storeMessage with id `%s` not found in the database", + id.String(), + ), + ) + } + + dbContext := m.messagingDBContext.WithTxIfExists(ctx) + + result := dbContext.DB().Delete(storeMessage, id) + if result.Error != nil { + return false, customErrors.NewInternalServerErrorWrap( + result.Error, + fmt.Sprintf( + "error in deleting storeMessage with id `%s` in the database", + id.String(), + ), + ) + } + + m.logger.Infof("Number of affected rows are: %d", result.RowsAffected) + + return true, nil +} + +func (m *postgresMessagePersistenceService) CleanupMessages( + ctx context.Context, +) error { + predicate := func(sm *persistmessage.StoreMessage) bool { + return sm.MessageStatus == persistmessage.Processed + } + + dbContext := m.messagingDBContext.WithTxIfExists(ctx) + + result := dbContext.DB(). + Where(predicate). + Delete(&persistmessage.StoreMessage{}) + + if result.Error != nil { + return result.Error + } + + m.logger.Infof("Number of affected rows are: %d", result.RowsAffected) + + return nil +} diff --git a/internal/pkg/postgresmessaging/messagepersistence/postgres_message_service_test.go b/internal/pkg/postgresmessaging/messagepersistence/postgres_message_service_test.go new file mode 100644 index 00000000..fa5b3e27 --- /dev/null +++ b/internal/pkg/postgresmessaging/messagepersistence/postgres_message_service_test.go @@ -0,0 +1,222 @@ +//go:build unit +// +build unit + +package messagepersistence + +import ( + "context" + "os" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/persistmessage" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/helpers/gormextensions" + + "emperror.dev/errors" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/suite" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + "gorm.io/gorm" +) + +type postgresMessageServiceTest struct { + suite.Suite + DB *gorm.DB + logger logger.Logger + messagingRepository persistmessage.MessagePersistenceService + dbContext *PostgresMessagePersistenceDBContext + storeMessages []*persistmessage.StoreMessage + ctx context.Context + dbFilePath string + app *fxtest.App +} + +func TestPostgresMessageService(t *testing.T) { + suite.Run( + t, + &postgresMessageServiceTest{logger: defaultLogger.GetLogger()}, + ) +} + +//func (c *postgresMessageServiceTest) SetupSuite() { +// opts, err := gorm2.NewGormTestContainers(defaultLogger.GetLogger()). +// PopulateContainerOptions(context.Background(), c.T()) +// c.Require().NoError(err) +// +// gormDB, err := postgresgorm.NewGorm(opts) +// c.Require().NoError(err) +// c.DB = gormDB +// +// err = migrationDatabase(gormDB) +// c.Require().NoError(err) +// +// c.dbContext = NewPostgresMessagePersistenceDBContext(gormDB) +// c.messagingRepository = NewPostgresMessageService( +// c.dbContext, +// defaultLogger.GetLogger(), +// ) +//} + +func (c *postgresMessageServiceTest) SetupTest() { + var gormDBContext *PostgresMessagePersistenceDBContext + var gormOptions *postgresgorm.GormOptions + + app := fxtest.New( + c.T(), + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + core.Module, + postgresgorm.Module, + fx.Decorate( + func(cfg *postgresgorm.GormOptions) (*postgresgorm.GormOptions, error) { + // using sql-lite with a database file + cfg.UseSQLLite = true + + return cfg, nil + }, + ), + fx.Provide(NewPostgresMessagePersistenceDBContext), + fx.Populate(&gormDBContext), + fx.Populate(&gormOptions), + ).RequireStart() + + c.dbContext = gormDBContext + c.dbFilePath = gormOptions.Dns() + c.app = app + + c.initDB() +} + +func (c *postgresMessageServiceTest) TearDownTest() { + err := c.cleanupDB() + c.Require().NoError(err) + + mapper.ClearMappings() + + c.app.RequireStop() +} + +//func (c *postgresMessageServiceTest) SetupTest() { +// ctx := context.Background() +// c.ctx = ctx +// p, err := seedData(context.Background(), c.DB) +// c.Require().NoError(err) +// c.storeMessages = p +//} +// +//func (c *postgresMessageServiceTest) TearDownTest() { +// err := c.cleanupPostgresData() +// c.Require().NoError(err) +//} + +func (c *postgresMessageServiceTest) BeginTx() { + c.logger.Info("starting transaction") + tx := c.dbContext.DB().Begin() + gormContext := gormextensions.SetTxToContext(c.ctx, tx) + c.ctx = gormContext +} + +func (c *postgresMessageServiceTest) CommitTx() { + tx := gormextensions.GetTxFromContextIfExists(c.ctx) + if tx != nil { + c.logger.Info("committing transaction") + tx.Commit() + } +} + +func (c *postgresMessageServiceTest) Test_Add() { + message := &persistmessage.StoreMessage{ + ID: uuid.NewV4(), + MessageStatus: persistmessage.Processed, + Data: "test data 3", + DataType: "string", + CreatedAt: time.Now(), + DeliveryType: persistmessage.Outbox, + } + + c.BeginTx() + err := c.messagingRepository.Add(c.ctx, message) + c.CommitTx() + + c.Require().NoError(err) + + m, err := c.messagingRepository.GetById(c.ctx, message.ID) + if err != nil { + return + } + + c.Assert().NotNil(m) + c.Assert().Equal(message.ID, m.ID) +} + +func (c *postgresMessageServiceTest) initDB() { + err := migrateGorm(c.dbContext.DB()) + c.Require().NoError(err) + + storeMessages, err := seedData(c.dbContext.DB()) + c.Require().NoError(err) + + c.storeMessages = storeMessages +} + +func (c *postgresMessageServiceTest) cleanupDB() error { + sqldb, _ := c.dbContext.DB().DB() + e := sqldb.Close() + c.Require().NoError(e) + + // removing sql-lite file + err := os.Remove(c.dbFilePath) + + return err +} + +func migrateGorm(db *gorm.DB) error { + err := db.AutoMigrate(&persistmessage.StoreMessage{}) + if err != nil { + return err + } + + return nil +} + +func seedData( + db *gorm.DB, +) ([]*persistmessage.StoreMessage, error) { + messages := []*persistmessage.StoreMessage{ + { + ID: uuid.NewV4(), + MessageStatus: persistmessage.Processed, + Data: "test data", + DataType: "string", + CreatedAt: time.Now(), + DeliveryType: persistmessage.Outbox, + }, + { + ID: uuid.NewV4(), + MessageStatus: persistmessage.Processed, + Data: "test data 2", + DataType: "string", + CreatedAt: time.Now(), + DeliveryType: persistmessage.Outbox, + }, + } + + // seed data + err := db.CreateInBatches(messages, len(messages)).Error + if err != nil { + return nil, errors.Wrap(err, "error in seed database") + } + + return messages, nil +} diff --git a/internal/pkg/postgresmessaging/postgres_messaging_fx.go b/internal/pkg/postgresmessaging/postgres_messaging_fx.go new file mode 100644 index 00000000..5ffc7ef2 --- /dev/null +++ b/internal/pkg/postgresmessaging/postgres_messaging_fx.go @@ -0,0 +1,24 @@ +package postgresmessaging + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/persistmessage" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresmessaging/messagepersistence" + + "go.uber.org/fx" + "gorm.io/gorm" +) + +var Module = fx.Module( + "postgresmessagingfx", + fx.Provide( + messagepersistence.NewPostgresMessagePersistenceDBContext, + messagepersistence.NewPostgresMessageService, + ), + fx.Invoke(migrateMessaging), +) + +func migrateMessaging(db *gorm.DB) error { + err := db.Migrator().AutoMigrate(&persistmessage.StoreMessage{}) + + return err +} diff --git a/internal/pkg/postgres_pgx/interface.go b/internal/pkg/postgrespgx/interface.go similarity index 100% rename from internal/pkg/postgres_pgx/interface.go rename to internal/pkg/postgrespgx/interface.go diff --git a/internal/pkg/postgres_pgx/postgres.go b/internal/pkg/postgrespgx/postgres.go similarity index 100% rename from internal/pkg/postgres_pgx/postgres.go rename to internal/pkg/postgrespgx/postgres.go diff --git a/internal/pkg/postgres_pgx/postgres_pgx_fx.go b/internal/pkg/postgrespgx/postgres_pgx_fx.go similarity index 86% rename from internal/pkg/postgres_pgx/postgres_pgx_fx.go rename to internal/pkg/postgrespgx/postgres_pgx_fx.go index e2b5492d..428eff9d 100644 --- a/internal/pkg/postgres_pgx/postgres_pgx_fx.go +++ b/internal/pkg/postgrespgx/postgres_pgx_fx.go @@ -3,7 +3,7 @@ package postgres import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "go.uber.org/fx" ) diff --git a/internal/pkg/postgres_pgx/postgres_pgx_options.go b/internal/pkg/postgrespgx/postgres_pgx_options.go similarity index 53% rename from internal/pkg/postgres_pgx/postgres_pgx_options.go rename to internal/pkg/postgrespgx/postgres_pgx_options.go index 936e50dd..4aff6f62 100644 --- a/internal/pkg/postgres_pgx/postgres_pgx_options.go +++ b/internal/pkg/postgrespgx/postgres_pgx_options.go @@ -1,14 +1,14 @@ package postgres import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) -var optionName = strcase.ToLowerCamel(typeMapper.GetTypeNameByT[PostgresPgxOptions]()) +var optionName = strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[PostgresPgxOptions]()) type PostgresPgxOptions struct { Host string `mapstructure:"host"` @@ -20,6 +20,6 @@ type PostgresPgxOptions struct { LogLevel int `mapstructure:"logLevel"` } -func provideConfig(environment environemnt.Environment) (*PostgresPgxOptions, error) { +func provideConfig(environment environment.Environment) (*PostgresPgxOptions, error) { return config.BindConfigKey[*PostgresPgxOptions](optionName, environment) } diff --git a/internal/pkg/postgres_sqlx/postgres.go b/internal/pkg/postgressqlx/postgres.go similarity index 100% rename from internal/pkg/postgres_sqlx/postgres.go rename to internal/pkg/postgressqlx/postgres.go diff --git a/internal/pkg/postgres_sqlx/postgres_sqlx_fx.go b/internal/pkg/postgressqlx/postgres_sqlx_fx.go similarity index 86% rename from internal/pkg/postgres_sqlx/postgres_sqlx_fx.go rename to internal/pkg/postgressqlx/postgres_sqlx_fx.go index f4ec3cff..39fee8c9 100644 --- a/internal/pkg/postgres_sqlx/postgres_sqlx_fx.go +++ b/internal/pkg/postgressqlx/postgres_sqlx_fx.go @@ -3,7 +3,7 @@ package postgressqlx import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" "go.uber.org/fx" ) diff --git a/internal/pkg/postgres_sqlx/postgres_sqlx_options.go b/internal/pkg/postgressqlx/postgres_sqlx_options.go similarity index 51% rename from internal/pkg/postgres_sqlx/postgres_sqlx_options.go rename to internal/pkg/postgressqlx/postgres_sqlx_options.go index 79519c97..8330b046 100644 --- a/internal/pkg/postgres_sqlx/postgres_sqlx_options.go +++ b/internal/pkg/postgressqlx/postgres_sqlx_options.go @@ -1,14 +1,14 @@ package postgressqlx import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) -var optionName = strcase.ToLowerCamel(typeMapper.GetTypeNameByT[PostgresSqlxOptions]()) +var optionName = strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[PostgresSqlxOptions]()) type PostgresSqlxOptions struct { Host string `mapstructure:"host"` @@ -19,6 +19,6 @@ type PostgresSqlxOptions struct { Password string `mapstructure:"password"` } -func provideConfig(environment environemnt.Environment) (*PostgresSqlxOptions, error) { +func provideConfig(environment environment.Environment) (*PostgresSqlxOptions, error) { return config.BindConfigKey[*PostgresSqlxOptions](optionName, environment) } diff --git a/internal/pkg/queue/client.go b/internal/pkg/queue/client.go index e36318fa..72c2f942 100644 --- a/internal/pkg/queue/client.go +++ b/internal/pkg/queue/client.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - redis2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/redis" + redis2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/redis" "github.com/hibiken/asynq" "go.uber.org/fx" diff --git a/internal/pkg/queue/server.go b/internal/pkg/queue/server.go index 2f15cf4f..2bc109c1 100644 --- a/internal/pkg/queue/server.go +++ b/internal/pkg/queue/server.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - redis2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/redis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + redis2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/redis" "github.com/hibiken/asynq" "go.uber.org/fx" diff --git a/internal/pkg/rabbitmq/bus/rabbitmq-bus.go b/internal/pkg/rabbitmq/bus/rabbitmq-bus.go index 42dec919..702d0eaa 100644 --- a/internal/pkg/rabbitmq/bus/rabbitmq-bus.go +++ b/internal/pkg/rabbitmq/bus/rabbitmq-bus.go @@ -6,23 +6,20 @@ import ( "reflect" "sync" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/bus" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/producer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/utils" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/configurations" - consumer2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer" - consumerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer/configurations" - producer2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/producer" - producerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/producer/configurations" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/rabbitmqErrors" - types2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/bus" + consumer2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/producer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + consumerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/consumercontracts" + producerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/producercontracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/rabbitmqErrors" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "emperror.dev/errors" "github.com/samber/lo" @@ -34,23 +31,21 @@ type RabbitmqBus interface { } type rabbitmqBus struct { - messageTypeConsumers map[reflect.Type][]consumer.Consumer + messageTypeConsumers map[reflect.Type][]consumer2.Consumer producer producer.Producer rabbitmqConfiguration *configurations.RabbitMQConfiguration - rabbitmqConfig *config.RabbitmqOptions rabbitmqConfigBuilder configurations.RabbitMQConfigurationBuilder logger logger.Logger - serializer serializer.EventSerializer - rabbitmqConnection types2.IConnection + consumerFactory consumercontracts.ConsumerFactory + producerFactory producercontracts.ProducerFactory isConsumedNotifications []func(message types.IMessage) isProducedNotifications []func(message types.IMessage) } func NewRabbitmqBus( - cfg *config.RabbitmqOptions, - serializer serializer.EventSerializer, logger logger.Logger, - connection types2.IConnection, + consumerFactory consumercontracts.ConsumerFactory, + producerFactory producercontracts.ProducerFactory, rabbitmqBuilderFunc configurations.RabbitMQConfigurationBuilderFuc, ) (RabbitmqBus, error) { builder := configurations.NewRabbitMQConfigurationBuilder() @@ -61,15 +56,16 @@ func NewRabbitmqBus( rabbitmqConfiguration := builder.Build() rabbitBus := &rabbitmqBus{ logger: logger, - serializer: serializer, rabbitmqConfiguration: rabbitmqConfiguration, - rabbitmqConfig: cfg, + consumerFactory: consumerFactory, + producerFactory: producerFactory, rabbitmqConfigBuilder: builder, - messageTypeConsumers: map[reflect.Type][]consumer.Consumer{}, - rabbitmqConnection: connection, + messageTypeConsumers: map[reflect.Type][]consumer2.Consumer{}, } - producersConfigurationMap := make(map[string]*producerConfigurations.RabbitMQProducerConfiguration) + producersConfigurationMap := make( + map[string]*producerConfigurations.RabbitMQProducerConfiguration, + ) lo.ForEach( rabbitBus.rabbitmqConfiguration.ProducersConfigurations, func(config *producerConfigurations.RabbitMQProducerConfiguration, index int) { @@ -78,7 +74,9 @@ func NewRabbitmqBus( }, ) - consumersConfigurationMap := make(map[string]*consumerConfigurations.RabbitMQConsumerConfiguration) + consumersConfigurationMap := make( + map[string]*consumerConfigurations.RabbitMQConsumerConfiguration, + ) lo.ForEach( rabbitBus.rabbitmqConfiguration.ConsumersConfigurations, func(config *consumerConfigurations.RabbitMQConsumerConfiguration, index int) { @@ -88,11 +86,8 @@ func NewRabbitmqBus( ) for _, consumerConfiguration := range consumersConfigurationMap { - mqConsumer, err := consumer2.NewRabbitMQConsumer( - rabbitBus.rabbitmqConnection, + mqConsumer, err := consumerFactory.CreateConsumer( consumerConfiguration, - rabbitBus.serializer, - rabbitBus.logger, // IsConsumed Notification func(message types.IMessage) { if rabbitBus.isConsumedNotifications != nil { @@ -111,11 +106,8 @@ func NewRabbitmqBus( ) } - mqProducer, err := producer2.NewRabbitMQProducer( - rabbitBus.rabbitmqConnection, + mqProducer, err := producerFactory.CreateProducer( producersConfigurationMap, - rabbitBus.logger, - rabbitBus.serializer, // IsProduced Notification func(message types.IMessage) { if rabbitBus.isProducedNotifications != nil { @@ -144,11 +136,14 @@ func (r *rabbitmqBus) IsProduced(h func(message types.IMessage)) { // ConnectConsumer Add a new consumer to existing message type consumers. if there is no consumer, will create a new consumer for the message type func (r *rabbitmqBus) ConnectConsumer( messageType types.IMessage, - consumer consumer.Consumer, + consumer consumer2.Consumer, ) error { typeName := utils.GetMessageBaseReflectType(messageType) - r.messageTypeConsumers[typeName] = append(r.messageTypeConsumers[typeName], consumer) + r.messageTypeConsumers[typeName] = append( + r.messageTypeConsumers[typeName], + consumer, + ) return nil } @@ -160,16 +155,15 @@ func (r *rabbitmqBus) ConnectRabbitMQConsumer( ) error { typeName := utils.GetMessageBaseReflectType(messageType) - builder := consumerConfigurations.NewRabbitMQConsumerConfigurationBuilder(messageType) + builder := consumerConfigurations.NewRabbitMQConsumerConfigurationBuilder( + messageType, + ) if consumerBuilderFunc != nil { consumerBuilderFunc(builder) } consumerConfig := builder.Build() - mqConsumer, err := consumer2.NewRabbitMQConsumer( - r.rabbitmqConnection, + mqConsumer, err := r.consumerFactory.CreateConsumer( consumerConfig, - r.serializer, - r.logger, // IsConsumed Notification func(message types.IMessage) { if len(r.isConsumedNotifications) > 0 { @@ -185,7 +179,10 @@ func (r *rabbitmqBus) ConnectRabbitMQConsumer( return err } - r.messageTypeConsumers[typeName] = append(r.messageTypeConsumers[typeName], mqConsumer) + r.messageTypeConsumers[typeName] = append( + r.messageTypeConsumers[typeName], + mqConsumer, + ) return nil } @@ -193,7 +190,7 @@ func (r *rabbitmqBus) ConnectRabbitMQConsumer( // ConnectConsumerHandler Add handler to existing consumer. creates new consumer if not exist func (r *rabbitmqBus) ConnectConsumerHandler( messageType types.IMessage, - consumerHandler consumer.ConsumerHandler, + consumerHandler consumer2.ConsumerHandler, ) error { typeName := utils.GetMessageBaseReflectType(messageType) @@ -206,15 +203,12 @@ func (r *rabbitmqBus) ConnectConsumerHandler( } else { // if there is no consumer for a message type, we should create new one and add handler to the consumer consumerBuilder := consumerConfigurations.NewRabbitMQConsumerConfigurationBuilder(messageType) - consumerBuilder.WithHandlers(func(builder consumer.ConsumerHandlerConfigurationBuilder) { + consumerBuilder.WithHandlers(func(builder consumer2.ConsumerHandlerConfigurationBuilder) { builder.AddHandler(consumerHandler) }) consumerConfig := consumerBuilder.Build() - mqConsumer, err := consumer2.NewRabbitMQConsumer( - r.rabbitmqConnection, + mqConsumer, err := r.consumerFactory.CreateConsumer( consumerConfig, - r.serializer, - r.logger, // IsConsumed Notification func(message types.IMessage) { if len(r.isConsumedNotifications) > 0 { @@ -238,7 +232,7 @@ func (r *rabbitmqBus) ConnectConsumerHandler( func (r *rabbitmqBus) Start(ctx context.Context) error { r.logger.Infof( "rabbitmq is running on host: %s", - r.rabbitmqConnection.Raw().LocalAddr().String(), + r.consumerFactory.Connection().Raw().LocalAddr().String(), ) for messageType, consumers := range r.messageTypeConsumers { @@ -286,7 +280,7 @@ func (r *rabbitmqBus) Stop() error { for _, c := range consumers { waitGroup.Add(1) - go func(c consumer.Consumer) { + go func(c consumer2.Consumer) { defer waitGroup.Done() err := c.Stop() @@ -323,5 +317,10 @@ func (r *rabbitmqBus) PublishMessageWithTopicName( meta metadata.Metadata, topicOrExchangeName string, ) error { - return r.producer.PublishMessageWithTopicName(ctx, message, meta, topicOrExchangeName) + return r.producer.PublishMessageWithTopicName( + ctx, + message, + meta, + topicOrExchangeName, + ) } diff --git a/internal/pkg/rabbitmq/bus/rabbitmq_bus_test.go b/internal/pkg/rabbitmq/bus/rabbitmq_bus_test.go index 57258b13..382967e2 100644 --- a/internal/pkg/rabbitmq/bus/rabbitmq_bus_test.go +++ b/internal/pkg/rabbitmq/bus/rabbitmq_bus_test.go @@ -5,19 +5,21 @@ import ( "fmt" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer/json" - defaultLogger2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - messageConsumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/pipeline" - types2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/configurations" - consumerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer/configurations" - producerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/producer/configurations" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/messaging/consumer" - testUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/utils" + messageConsumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + pipeline2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/pipeline" + types3 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer/json" + defaultlogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + consumerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/factory" + producerfactory "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer" + producerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging/consumer" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/assert" @@ -26,42 +28,76 @@ import ( func Test_AddRabbitMQ(t *testing.T) { testUtils.SkipCI(t) + ctx := context.Background() fakeConsumer2 := consumer.NewRabbitMQFakeTestConsumerHandler[*ProducerConsumerMessage]() fakeConsumer3 := consumer.NewRabbitMQFakeTestConsumerHandler[*ProducerConsumerMessage]() - defaultLogger2.SetupDefaultLogger() - serializer := serializer.NewDefaultEventSerializer(json.NewDefaultSerializer()) + serializer := json.NewDefaultMessageJsonSerializer( + json.NewDefaultJsonSerializer(), + ) - rabbitmqOptions := &config.RabbitmqOptions{ - RabbitmqHostOptions: &config.RabbitmqHostOptions{ - UserName: "guest", - Password: "guest", - HostName: "localhost", - Port: 5672, - }, + //rabbitmqOptions := &config.RabbitmqOptions{ + // RabbitmqHostOptions: &config.RabbitmqHostOptions{ + // UserName: "guest", + // Password: "guest", + // HostName: "localhost", + // Port: 5672, + // }, + //} + + rabbitmqHostOption, err := rabbitmq.NewRabbitMQTestContainers(defaultlogger.GetLogger()). + PopulateContainerOptions(ctx, t) + require.NoError(t, err) + + options := &config.RabbitmqOptions{ + RabbitmqHostOptions: rabbitmqHostOption, } - conn, err := types.NewRabbitMQConnection(rabbitmqOptions) + + conn, err := types.NewRabbitMQConnection(options) require.NoError(t, err) - b, err := NewRabbitmqBus(rabbitmqOptions, serializer, defaultLogger2.Logger, conn, + consumerFactory := factory.NewConsumerFactory( + options, + conn, + serializer, + defaultlogger.GetLogger(), + ) + producerFactory := producerfactory.NewProducerFactory( + options, + conn, + serializer, + defaultlogger.GetLogger(), + ) + + b, err := NewRabbitmqBus( + defaultlogger.GetLogger(), + consumerFactory, + producerFactory, func(builder configurations.RabbitMQConfigurationBuilder) { builder.AddProducer( ProducerConsumerMessage{}, func(builder producerConfigurations.RabbitMQProducerConfigurationBuilder) { }, ) - builder.AddConsumer(ProducerConsumerMessage{}, + builder.AddConsumer( + ProducerConsumerMessage{}, func(builder consumerConfigurations.RabbitMQConsumerConfigurationBuilder) { builder.WithHandlers(func(consumerHandlerBuilder messageConsumer.ConsumerHandlerConfigurationBuilder) { - consumerHandlerBuilder.AddHandler(NewTestMessageHandler()) - consumerHandlerBuilder.AddHandler(NewTestMessageHandler2()) + consumerHandlerBuilder.AddHandler( + NewTestMessageHandler(), + ) + consumerHandlerBuilder.AddHandler( + NewTestMessageHandler2(), + ) }). - WIthPipelines(func(consumerPipelineBuilder pipeline.ConsumerPipelineConfigurationBuilder) { + WIthPipelines(func(consumerPipelineBuilder pipeline2.ConsumerPipelineConfigurationBuilder) { consumerPipelineBuilder.AddPipeline(NewPipeline1()) }) - }) - }) + }, + ) + }, + ) require.NoError(t, err) @@ -78,7 +114,6 @@ func Test_AddRabbitMQ(t *testing.T) { err = b.ConnectConsumerHandler(&ProducerConsumerMessage{}, fakeConsumer3) require.NoError(t, err) - ctx := context.Background() err = b.Start(ctx) require.NoError(t, err) @@ -86,7 +121,7 @@ func Test_AddRabbitMQ(t *testing.T) { context.Background(), &ProducerConsumerMessage{ Data: "ssssssssss", - Message: types2.NewMessage(uuid.NewV4().String()), + Message: types3.NewMessage(uuid.NewV4().String()), }, nil, ) @@ -101,14 +136,14 @@ func Test_AddRabbitMQ(t *testing.T) { } type ProducerConsumerMessage struct { - *types2.Message + *types3.Message Data string } func NewProducerConsumerMessage(data string) *ProducerConsumerMessage { return &ProducerConsumerMessage{ Data: data, - Message: types2.NewMessage(uuid.NewV4().String()), + Message: types3.NewMessage(uuid.NewV4().String()), } } @@ -121,7 +156,7 @@ func NewTestMessageHandler() *TestMessageHandler { func (t *TestMessageHandler) Handle( ctx context.Context, - consumeContext types2.MessageConsumeContext, + consumeContext types3.MessageConsumeContext, ) error { message := consumeContext.Message().(*ProducerConsumerMessage) fmt.Println(message) @@ -133,7 +168,7 @@ type TestMessageHandler2 struct{} func (t *TestMessageHandler2) Handle( ctx context.Context, - consumeContext types2.MessageConsumeContext, + consumeContext types3.MessageConsumeContext, ) error { message := consumeContext.Message() fmt.Println(message) @@ -148,20 +183,23 @@ func NewTestMessageHandler2() *TestMessageHandler2 { // /////////////// ConsumerPipeline type Pipeline1 struct{} -func NewPipeline1() pipeline.ConsumerPipeline { +func NewPipeline1() pipeline2.ConsumerPipeline { return &Pipeline1{} } -func (p Pipeline1) Handle( +func (p *Pipeline1) Handle( ctx context.Context, - consumerContext types2.MessageConsumeContext, - next pipeline.ConsumerHandlerFunc, + consumerContext types3.MessageConsumeContext, + next pipeline2.ConsumerHandlerFunc, ) error { fmt.Println("PipelineBehaviourTest.Handled") - fmt.Printf("pipeline got a message with id '%s'", consumerContext.Message().GeMessageId()) + fmt.Printf( + "pipeline got a message with id '%s'", + consumerContext.Message().GeMessageId(), + ) - err := next() + err := next(ctx) if err != nil { return err } diff --git a/internal/pkg/rabbitmq/config/rabbitmq_options.go b/internal/pkg/rabbitmq/config/rabbitmq_options.go index fa3bb8f7..e80e8f98 100644 --- a/internal/pkg/rabbitmq/config/rabbitmq_options.go +++ b/internal/pkg/rabbitmq/config/rabbitmq_options.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) @@ -38,8 +38,8 @@ func (h *RabbitmqHostOptions) HttpEndPoint() string { return fmt.Sprintf("http://%s:%d", h.HostName, h.HttpPort) } -func ProvideConfig(environment environemnt.Environment) (*RabbitmqOptions, error) { - optionName := strcase.ToLowerCamel(typeMapper.GetTypeNameByT[RabbitmqOptions]()) +func ProvideConfig(environment environment.Environment) (*RabbitmqOptions, error) { + optionName := strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[RabbitmqOptions]()) cfg, err := config.BindConfigKey[*RabbitmqOptions](optionName, environment) return cfg, err diff --git a/internal/pkg/rabbitmq/configurations/rabbitmq_configuration.go b/internal/pkg/rabbitmq/configurations/rabbitmq_configuration.go index b11c183e..ce242a28 100644 --- a/internal/pkg/rabbitmq/configurations/rabbitmq_configuration.go +++ b/internal/pkg/rabbitmq/configurations/rabbitmq_configuration.go @@ -1,8 +1,8 @@ package configurations import ( - consumerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer/configurations" - producerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/producer/configurations" + consumerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + producerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/configurations" ) type RabbitMQConfiguration struct { diff --git a/internal/pkg/rabbitmq/configurations/rabbitmq_configuration_builder.go b/internal/pkg/rabbitmq/configurations/rabbitmq_configuration_builder.go index b60fbf51..5409cdc2 100644 --- a/internal/pkg/rabbitmq/configurations/rabbitmq_configuration_builder.go +++ b/internal/pkg/rabbitmq/configurations/rabbitmq_configuration_builder.go @@ -1,9 +1,9 @@ package configurations import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - consumerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer/configurations" - producerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/producer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + consumerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + producerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/configurations" "github.com/samber/lo" ) diff --git a/internal/pkg/rabbitmq/consumer/configurations/consumer_connector.go b/internal/pkg/rabbitmq/consumer/configurations/consumer_connector.go index 23b05c6e..ae4e908a 100644 --- a/internal/pkg/rabbitmq/consumer/configurations/consumer_connector.go +++ b/internal/pkg/rabbitmq/consumer/configurations/consumer_connector.go @@ -1,8 +1,8 @@ package configurations import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" ) type RabbitMQConsumerConnector interface { diff --git a/internal/pkg/rabbitmq/consumer/configurations/rabbitmq_consumer_configuration.go b/internal/pkg/rabbitmq/consumer/configurations/rabbitmq_consumer_configuration.go index df8ccdc0..13bca358 100644 --- a/internal/pkg/rabbitmq/consumer/configurations/rabbitmq_consumer_configuration.go +++ b/internal/pkg/rabbitmq/consumer/configurations/rabbitmq_consumer_configuration.go @@ -4,20 +4,20 @@ import ( "fmt" "reflect" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/pipeline" - types2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/utils" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer/options" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" + consumer2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/pipeline" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/options" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" ) type RabbitMQConsumerConfiguration struct { Name string ConsumerMessageType reflect.Type Pipelines []pipeline.ConsumerPipeline - Handlers []consumer.ConsumerHandler - *consumer.ConsumerOptions + Handlers []consumer2.ConsumerHandler + *consumer2.ConsumerOptions ConcurrencyLimit int // The prefetch count tells the Rabbit connection how many messages to retrieve from the server per request. PrefetchCount int @@ -35,7 +35,7 @@ func NewDefaultRabbitMQConsumerConfiguration( name := fmt.Sprintf("%s_consumer", utils.GetMessageName(messageType)) return &RabbitMQConsumerConfiguration{ - ConsumerOptions: &consumer.ConsumerOptions{ExitOnError: false, ConsumerId: ""}, + ConsumerOptions: &consumer2.ConsumerOptions{ExitOnError: false, ConsumerId: ""}, ConcurrencyLimit: 1, PrefetchCount: 4, // how many messages we can handle at once NoLocal: false, diff --git a/internal/pkg/rabbitmq/consumer/configurations/rabbitmq_consumer_configuration_builder.go b/internal/pkg/rabbitmq/consumer/configurations/rabbitmq_consumer_configuration_builder.go index 1e729899..9885f4ce 100644 --- a/internal/pkg/rabbitmq/consumer/configurations/rabbitmq_consumer_configuration_builder.go +++ b/internal/pkg/rabbitmq/consumer/configurations/rabbitmq_consumer_configuration_builder.go @@ -1,10 +1,10 @@ package configurations import ( - messageConsumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/pipeline" - types2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" + messageConsumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/pipeline" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" ) type RabbitMQConsumerConfigurationBuilder interface { diff --git a/internal/pkg/rabbitmq/consumer/consumercontracts/consumer_factory.go b/internal/pkg/rabbitmq/consumer/consumercontracts/consumer_factory.go new file mode 100644 index 00000000..b18ae4b1 --- /dev/null +++ b/internal/pkg/rabbitmq/consumer/consumercontracts/consumer_factory.go @@ -0,0 +1,17 @@ +package consumercontracts + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + messagingTypes "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" +) + +type ConsumerFactory interface { + CreateConsumer( + consumerConfiguration *configurations.RabbitMQConsumerConfiguration, + isConsumedNotifications ...func(message messagingTypes.IMessage), + ) (consumer.Consumer, error) + + Connection() types.IConnection +} diff --git a/internal/pkg/rabbitmq/consumer/factory/consumer_factory.go b/internal/pkg/rabbitmq/consumer/factory/consumer_factory.go new file mode 100644 index 00000000..a5ef2328 --- /dev/null +++ b/internal/pkg/rabbitmq/consumer/factory/consumer_factory.go @@ -0,0 +1,51 @@ +package factory + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + serializer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + rabbitmqconsumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer" + consumerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/consumercontracts" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" +) + +type consumerFactory struct { + connection types2.IConnection + eventSerializer serializer.MessageSerializer + logger logger.Logger + rabbitmqOptions *config.RabbitmqOptions +} + +func NewConsumerFactory( + rabbitmqOptions *config.RabbitmqOptions, + connection types2.IConnection, + eventSerializer serializer.MessageSerializer, + l logger.Logger, +) consumercontracts.ConsumerFactory { + return &consumerFactory{ + rabbitmqOptions: rabbitmqOptions, + logger: l, + eventSerializer: eventSerializer, + connection: connection, + } +} + +func (c *consumerFactory) CreateConsumer( + consumerConfiguration *consumerConfigurations.RabbitMQConsumerConfiguration, + isConsumedNotifications ...func(message types.IMessage), +) (consumer.Consumer, error) { + return rabbitmqconsumer.NewRabbitMQConsumer( + c.rabbitmqOptions, + c.connection, + consumerConfiguration, + c.eventSerializer, + c.logger, + isConsumedNotifications...) +} + +func (c *consumerFactory) Connection() types2.IConnection { + return c.connection +} diff --git a/internal/pkg/rabbitmq/consumer/options/rabbitmq_exchange_options.go b/internal/pkg/rabbitmq/consumer/options/rabbitmq_exchange_options.go index 22184cfc..d067ab2b 100644 --- a/internal/pkg/rabbitmq/consumer/options/rabbitmq_exchange_options.go +++ b/internal/pkg/rabbitmq/consumer/options/rabbitmq_exchange_options.go @@ -1,6 +1,6 @@ package options -import "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" type RabbitMQExchangeOptions struct { Name string diff --git a/internal/pkg/rabbitmq/consumer/rabbitmq_consumer.go b/internal/pkg/rabbitmq/consumer/rabbitmq_consumer.go index b7e152fb..a8de0fd4 100644 --- a/internal/pkg/rabbitmq/consumer/rabbitmq_consumer.go +++ b/internal/pkg/rabbitmq/consumer/rabbitmq_consumer.go @@ -6,18 +6,19 @@ import ( "reflect" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - consumeTracing "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/otel/tracing/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/pipeline" - messagingTypes "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/utils" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer/configurations" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/rabbitmqErrors" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + consumertracing "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/otel/tracing/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/pipeline" + messagingTypes "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/rabbitmqErrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" + errorutils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils/errorutils" "emperror.dev/errors" "github.com/ahmetb/go-linq/v3" @@ -44,8 +45,9 @@ type rabbitMQConsumer struct { handlerDefault consumer.ConsumerHandler channel *amqp091.Channel deliveryRoutines chan struct{} // chan should init before using channel - eventSerializer serializer.EventSerializer + messageSerializer serializer.MessageSerializer logger logger.Logger + rabbitmqOptions *config.RabbitmqOptions ErrChan chan error handlers []consumer.ConsumerHandler pipelines []pipeline.ConsumerPipeline @@ -54,9 +56,10 @@ type rabbitMQConsumer struct { // NewRabbitMQConsumer create a new generic RabbitMQ consumer func NewRabbitMQConsumer( + rabbitmqOptions *config.RabbitmqOptions, connection types.IConnection, consumerConfiguration *configurations.RabbitMQConsumerConfiguration, - eventSerializer serializer.EventSerializer, + messageSerializer serializer.MessageSerializer, logger logger.Logger, isConsumedNotifications ...func(message messagingTypes.IMessage), ) (consumer.Consumer, error) { @@ -65,12 +68,18 @@ func NewRabbitMQConsumer( } if consumerConfiguration.ConsumerMessageType == nil { - return nil, errors.New("consumer ConsumerMessageType property is required") + return nil, errors.New( + "consumer ConsumerMessageType property is required", + ) } - deliveryRoutines := make(chan struct{}, consumerConfiguration.ConcurrencyLimit) + deliveryRoutines := make( + chan struct{}, + consumerConfiguration.ConcurrencyLimit, + ) cons := &rabbitMQConsumer{ - eventSerializer: eventSerializer, + messageSerializer: messageSerializer, + rabbitmqOptions: rabbitmqOptions, logger: logger, rabbitmqConsumerOptions: consumerConfiguration, deliveryRoutines: deliveryRoutines, @@ -254,7 +263,7 @@ func (r *rabbitMQConsumer) GetName() string { func (r *rabbitMQConsumer) reConsumeOnDropConnection(ctx context.Context) { go func() { - defer errorUtils.HandlePanic() + defer errorutils.HandlePanic() for { select { case reconnect := <-r.connection.ReconnectedChannel(): @@ -262,10 +271,15 @@ func (r *rabbitMQConsumer) reConsumeOnDropConnection(ctx context.Context) { r.logger.Info("reconsume_on_drop_connection started") err := r.Start(ctx) if err != nil { - r.logger.Error("reconsume_on_drop_connection finished with error: %v", err) + r.logger.Error( + "reconsume_on_drop_connection finished with error: %v", + err, + ) continue } - r.logger.Info("reconsume_on_drop_connection finished successfully") + r.logger.Info( + "reconsume_on_drop_connection finished successfully", + ) return } } @@ -273,7 +287,10 @@ func (r *rabbitMQConsumer) reConsumeOnDropConnection(ctx context.Context) { }() } -func (r *rabbitMQConsumer) handleReceived(ctx context.Context, delivery amqp091.Delivery) { +func (r *rabbitMQConsumer) handleReceived( + ctx context.Context, + delivery amqp091.Delivery, +) { // for ensuring our handlers execute completely after shutdown r.deliveryRoutines <- struct{}{} @@ -284,7 +301,7 @@ func (r *rabbitMQConsumer) handleReceived(ctx context.Context, delivery amqp091. meta = metadata.MapToMetadata(delivery.Headers) } - consumerTraceOption := &consumeTracing.ConsumerTracingOptions{ + consumerTraceOption := &consumertracing.ConsumerTracingOptions{ MessagingSystem: "rabbitmq", DestinationKind: "queue", Destination: r.rabbitmqConsumerOptions.QueueOptions.Name, @@ -292,7 +309,7 @@ func (r *rabbitMQConsumer) handleReceived(ctx context.Context, delivery amqp091. semconv.MessagingRabbitmqDestinationRoutingKey(delivery.RoutingKey), }, } - ctx, beforeConsumeSpan := consumeTracing.StartConsumerSpan( + ctx, beforeConsumeSpan := consumertracing.StartConsumerSpan( ctx, &meta, string(delivery.Body), @@ -301,7 +318,9 @@ func (r *rabbitMQConsumer) handleReceived(ctx context.Context, delivery amqp091. consumeContext, err := r.createConsumeContext(delivery) if err != nil { - r.logger.Error(consumeTracing.FinishConsumerSpan(beforeConsumeSpan, err)) + r.logger.Error( + consumertracing.FinishConsumerSpan(beforeConsumeSpan, err), + ) return } @@ -314,11 +333,11 @@ func (r *rabbitMQConsumer) handleReceived(ctx context.Context, delivery amqp091. if err := delivery.Ack(false); err != nil { r.logger.Error( "error sending ACK to RabbitMQ consumer: %v", - consumeTracing.FinishConsumerSpan(beforeConsumeSpan, err), + consumertracing.FinishConsumerSpan(beforeConsumeSpan, err), ) return } - _ = consumeTracing.FinishConsumerSpan(beforeConsumeSpan, nil) + _ = consumertracing.FinishConsumerSpan(beforeConsumeSpan, nil) if len(r.isConsumedNotifications) > 0 { for _, notification := range r.isConsumedNotifications { if notification != nil { @@ -332,11 +351,11 @@ func (r *rabbitMQConsumer) handleReceived(ctx context.Context, delivery amqp091. if err := delivery.Nack(false, true); err != nil { r.logger.Error( "error in sending Nack to RabbitMQ consumer: %v", - consumeTracing.FinishConsumerSpan(beforeConsumeSpan, err), + consumertracing.FinishConsumerSpan(beforeConsumeSpan, err), ) return } - _ = consumeTracing.FinishConsumerSpan(beforeConsumeSpan, nil) + _ = consumertracing.FinishConsumerSpan(beforeConsumeSpan, nil) } } @@ -379,7 +398,7 @@ func (r *rabbitMQConsumer) runHandlersWithRetry( if r.pipelines != nil && len(r.pipelines) > 0 { reversPipes := r.reversOrder(r.pipelines) - lastHandler = func() error { + lastHandler = func(ctx context.Context) error { handler := handler.(consumer.ConsumerHandler) return handler.Handle(ctx, messageConsumeContext) } @@ -389,10 +408,14 @@ func (r *rabbitMQConsumer) runHandlersWithRetry( pipeValue := pipe nexValue := next - var handlerFunc pipeline.ConsumerHandlerFunc = func() error { + var handlerFunc pipeline.ConsumerHandlerFunc = func(ctx context.Context) error { genericContext, ok := messageConsumeContext.(messagingTypes.MessageConsumeContext) if ok { - return pipeValue.Handle(ctx, genericContext, nexValue) + return pipeValue.Handle( + ctx, + genericContext, + nexValue, + ) } return pipeValue.Handle( ctx, @@ -404,9 +427,12 @@ func (r *rabbitMQConsumer) runHandlersWithRetry( }) v := aggregateResult.(pipeline.ConsumerHandlerFunc) - err := v() + err := v(ctx) if err != nil { - return errors.Wrap(err, "error handling consumer handlers pipeline") + return errors.Wrap( + err, + "error handling consumer handlers pipeline", + ) } return nil } else { @@ -424,21 +450,11 @@ func (r *rabbitMQConsumer) runHandlersWithRetry( func (r *rabbitMQConsumer) createConsumeContext( delivery amqp091.Delivery, ) (messagingTypes.MessageConsumeContext, error) { - message := r.deserializeData(delivery.ContentType, delivery.Type, delivery.Body) - if reflect.ValueOf(message).IsZero() || reflect.ValueOf(message).IsNil() { - return *new(messagingTypes.MessageConsumeContext), errors.New( - "error in deserialization of payload", - ) - } - m, ok := message.(messagingTypes.IMessage) - if !ok || m.IsMessage() == false { - return nil, errors.New( - fmt.Sprintf( - "message %s is not a message type or message property is nil", - utils.GetMessageBaseReflectType(message), - ), - ) - } + message := r.deserializeData( + delivery.ContentType, + delivery.Type, + delivery.Body, + ) var meta metadata.Metadata if delivery.Headers != nil { @@ -446,7 +462,7 @@ func (r *rabbitMQConsumer) createConsumeContext( } consumeContext := messagingTypes.NewMessageConsumeContext( - message.(messagingTypes.IMessage), + message, meta, delivery.ContentType, delivery.Type, @@ -462,7 +478,7 @@ func (r *rabbitMQConsumer) deserializeData( contentType string, eventType string, body []byte, -) interface{} { +) messagingTypes.IMessage { if contentType == "" { contentType = "application/json" } @@ -474,15 +490,18 @@ func (r *rabbitMQConsumer) deserializeData( if contentType == "application/json" { // r.rabbitmqConsumerOptions.ConsumerMessageType --> actual type - // deserialize, err := r.eventSerializer.DeserializeType(body, r.rabbitmqConsumerOptions.ConsumerMessageType, contentType) - deserialize, err := r.eventSerializer.DeserializeMessage( + // deserialize, err := r.messageSerializer.DeserializeType(body, r.rabbitmqConsumerOptions.ConsumerMessageType, contentType) + deserialize, err := r.messageSerializer.Deserialize( body, eventType, contentType, ) // or this to explicit type deserialization if err != nil { r.logger.Errorf( - fmt.Sprintf("error in deserilizng of type '%s' in the consumer", eventType), + fmt.Sprintf( + "error in deserilizng of type '%s' in the consumer", + eventType, + ), ) return nil } diff --git a/internal/pkg/rabbitmq/consumer/rabbitmq_consumer_test.go b/internal/pkg/rabbitmq/consumer/rabbitmq_consumer_test.go index afeffa1e..1a36798d 100644 --- a/internal/pkg/rabbitmq/consumer/rabbitmq_consumer_test.go +++ b/internal/pkg/rabbitmq/consumer/rabbitmq_consumer_test.go @@ -4,131 +4,125 @@ import ( "context" "fmt" "testing" - - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer/json" - defaultLogger2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - messageConsumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/pipeline" - types2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - config2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer/configurations" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/producer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/messaging/consumer" - testUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/utils" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" + "time" + + messageConsumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/pipeline" + types3 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer/json" + defaultLogger2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/bus" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + rabbitmqConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/factory" + producerfactory "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging/consumer" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/require" ) -func Test_Consume_Message(t *testing.T) { +func Test_Consumer_With_Fake_Message(t *testing.T) { testUtils.SkipCI(t) - defer errorUtils.HandlePanic() ctx := context.Background() - defaultLogger2.SetupDefaultLogger() - eventSerializer := serializer.NewDefaultEventSerializer(json.NewDefaultSerializer()) - - tp, err := tracing.NewOtelTracing(&config2.OpenTelemetryOptions{ - ServiceName: "test", - Enabled: true, - AlwaysOnSampler: true, - JaegerExporterOptions: &config2.JaegerExporterOptions{ - AgentHost: "localhost", - AgentPort: "6831", - }, - ZipkinExporterOptions: &config2.ZipkinExporterOptions{ - Url: "http://localhost:9411/api/v2/spans", - }, - }, environemnt.Development) + + //options := &config.RabbitmqOptions{ + // RabbitmqHostOptions: &config.RabbitmqHostOptions{ + // UserName: "guest", + // Password: "guest", + // HostName: "localhost", + // Port: 5672, + // }, + //} + + rabbitmqHostOption, err := rabbitmq.NewRabbitMQTestContainers(defaultLogger2.GetLogger()). + PopulateContainerOptions(ctx, t) require.NoError(t, err) - defer tp.TracerProvider.Shutdown(ctx) + options := &config.RabbitmqOptions{ + RabbitmqHostOptions: rabbitmqHostOption, + } - conn, err := types.NewRabbitMQConnection(&config.RabbitmqOptions{ - RabbitmqHostOptions: &config.RabbitmqHostOptions{ - UserName: "guest", - Password: "guest", - HostName: "localhost", - Port: 5672, - }, - }) + conn, err := types.NewRabbitMQConnection(options) require.NoError(t, err) - fakeHandler := consumer.NewRabbitMQFakeTestConsumerHandler[ProducerConsumerMessage]() - builder := configurations.NewRabbitMQConsumerConfigurationBuilder(ProducerConsumerMessage{}) - builder.WithHandlers( - func(consumerHandlerBuilder messageConsumer.ConsumerHandlerConfigurationBuilder) { - consumerHandlerBuilder.AddHandler(NewTestMessageHandler()) - consumerHandlerBuilder.AddHandler(fakeHandler) - }, - ) - rabbitmqConsumer, err := NewRabbitMQConsumer( + eventSerializer := json.NewDefaultMessageJsonSerializer( + json.NewDefaultJsonSerializer(), + ) + consumerFactory := factory.NewConsumerFactory( + options, conn, - builder.Build(), eventSerializer, - defaultLogger2.Logger, + defaultLogger2.GetLogger(), + ) + producerFactory := producerfactory.NewProducerFactory( + options, + conn, + eventSerializer, + defaultLogger2.GetLogger(), ) - require.NoError(t, err) - if rabbitmqConsumer == nil { - t.Log("RabbitMQ consumer is nil") - return - } - err = rabbitmqConsumer.Start(ctx) - if err != nil { - rabbitmqConsumer.Stop() - } - require.NoError(t, err) + fakeHandler := consumer.NewRabbitMQFakeTestConsumerHandler[ProducerConsumerMessage]() + + rabbitmqBus, err := bus.NewRabbitmqBus( + defaultLogger2.GetLogger(), + consumerFactory, + producerFactory, + func(builder rabbitmqConfigurations.RabbitMQConfigurationBuilder) { + builder.AddConsumer( + ProducerConsumerMessage{}, + func(consumerBuilder configurations.RabbitMQConsumerConfigurationBuilder) { + consumerBuilder.WithHandlers( + func(consumerHandlerBuilder messageConsumer.ConsumerHandlerConfigurationBuilder) { + consumerHandlerBuilder.AddHandler(fakeHandler) + }, + ) + }, + ) + }, + ) + + rabbitmqBus.Start(ctx) + defer rabbitmqBus.Stop() + + time.Sleep(time.Second * 1) - rabbitmqProducer, err := producer.NewRabbitMQProducer( - conn, - nil, - defaultLogger2.Logger, - eventSerializer) require.NoError(t, err) - //time.Sleep(time.Second * 5) - // - //fmt.Println("closing connection") - //conn.Close() - //fmt.Println(conn.IsClosed()) - // - //time.Sleep(time.Second * 10) - //fmt.Println("after 10 second of closing connection") - //fmt.Println(conn.IsClosed()) - - err = rabbitmqProducer.PublishMessage(ctx, NewProducerConsumerMessage("test"), nil) + err = rabbitmqBus.PublishMessage( + ctx, + NewProducerConsumerMessage("test"), + nil, + ) for err != nil { - err = rabbitmqProducer.PublishMessage(ctx, NewProducerConsumerMessage("test"), nil) + err = rabbitmqBus.PublishMessage( + ctx, + NewProducerConsumerMessage("test"), + nil, + ) } err = testUtils.WaitUntilConditionMet(func() bool { return fakeHandler.IsHandled() }) - require.NoError(t, err) - rabbitmqConsumer.Stop() - conn.Close() - - fmt.Println(conn.IsClosed()) - fmt.Println(conn.IsConnected()) + require.NoError(t, err) } type ProducerConsumerMessage struct { - *types2.Message + *types3.Message Data string } func NewProducerConsumerMessage(data string) *ProducerConsumerMessage { return &ProducerConsumerMessage{ Data: data, - Message: types2.NewMessage(uuid.NewV4().String()), + Message: types3.NewMessage(uuid.NewV4().String()), } } @@ -137,7 +131,7 @@ type TestMessageHandler struct{} func (t *TestMessageHandler) Handle( ctx context.Context, - consumeContext types2.MessageConsumeContext, + consumeContext types3.MessageConsumeContext, ) error { message := consumeContext.Message().(*ProducerConsumerMessage) fmt.Println(message) @@ -153,7 +147,7 @@ type TestMessageHandler2 struct{} func (t *TestMessageHandler2) Handle( ctx context.Context, - consumeContext types2.MessageConsumeContext, + consumeContext types3.MessageConsumeContext, ) error { message := consumeContext.Message() fmt.Println(message) @@ -174,16 +168,19 @@ func NewPipeline1() pipeline.ConsumerPipeline { func (p Pipeline1) Handle( ctx context.Context, - consumerContext types2.MessageConsumeContext, + consumerContext types3.MessageConsumeContext, next pipeline.ConsumerHandlerFunc, ) error { fmt.Println("PipelineBehaviourTest.Handled") fmt.Println( - fmt.Sprintf("pipeline got a message with id '%s'", consumerContext.Message().GeMessageId()), + fmt.Sprintf( + "pipeline got a message with id '%s'", + consumerContext.Message().GeMessageId(), + ), ) - err := next() + err := next(ctx) if err != nil { return err } diff --git a/internal/pkg/rabbitmq/health.go b/internal/pkg/rabbitmq/health.go index 9650ad34..7f69441f 100644 --- a/internal/pkg/rabbitmq/health.go +++ b/internal/pkg/rabbitmq/health.go @@ -3,8 +3,8 @@ package rabbitmq import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/health" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" "emperror.dev/errors" ) @@ -13,7 +13,7 @@ type gormHealthChecker struct { connection types.IConnection } -func NewRabbitMQHealthChecker(connection types.IConnection) health.Health { +func NewRabbitMQHealthChecker(connection types.IConnection) contracts.Health { return &gormHealthChecker{connection} } diff --git a/internal/pkg/rabbitmq/producer/configurations/rabbitmq_producer_configuration.go b/internal/pkg/rabbitmq/producer/configurations/rabbitmq_producer_configuration.go index e30747f1..bee74474 100644 --- a/internal/pkg/rabbitmq/producer/configurations/rabbitmq_producer_configuration.go +++ b/internal/pkg/rabbitmq/producer/configurations/rabbitmq_producer_configuration.go @@ -3,10 +3,10 @@ package configurations import ( "reflect" - types2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/utils" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/producer/options" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/options" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" ) type RabbitMQProducerConfiguration struct { diff --git a/internal/pkg/rabbitmq/producer/configurations/rabitmq_producer_configuration_builder.go b/internal/pkg/rabbitmq/producer/configurations/rabitmq_producer_configuration_builder.go index 80145b0c..86f868ac 100644 --- a/internal/pkg/rabbitmq/producer/configurations/rabitmq_producer_configuration_builder.go +++ b/internal/pkg/rabbitmq/producer/configurations/rabitmq_producer_configuration_builder.go @@ -1,8 +1,8 @@ package configurations import ( - types2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" ) type RabbitMQProducerConfigurationBuilder interface { diff --git a/internal/pkg/rabbitmq/producer/options/rabbitmq_exchange_options.go b/internal/pkg/rabbitmq/producer/options/rabbitmq_exchange_options.go index 22184cfc..d067ab2b 100644 --- a/internal/pkg/rabbitmq/producer/options/rabbitmq_exchange_options.go +++ b/internal/pkg/rabbitmq/producer/options/rabbitmq_exchange_options.go @@ -1,6 +1,6 @@ package options -import "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" type RabbitMQExchangeOptions struct { Name string diff --git a/internal/pkg/rabbitmq/producer/producer_factory.go b/internal/pkg/rabbitmq/producer/producer_factory.go new file mode 100644 index 00000000..d5b49009 --- /dev/null +++ b/internal/pkg/rabbitmq/producer/producer_factory.go @@ -0,0 +1,46 @@ +package producer + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/producer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + serializer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + producerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/producercontracts" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" +) + +type producerFactory struct { + connection types2.IConnection + logger logger.Logger + eventSerializer serializer.MessageSerializer + rabbitmqOptions *config.RabbitmqOptions +} + +func NewProducerFactory( + rabbitmqOptions *config.RabbitmqOptions, + connection types2.IConnection, + eventSerializer serializer.MessageSerializer, + l logger.Logger, +) producercontracts.ProducerFactory { + return &producerFactory{ + rabbitmqOptions: rabbitmqOptions, + logger: l, + connection: connection, + eventSerializer: eventSerializer, + } +} + +func (p *producerFactory) CreateProducer( + rabbitmqProducersConfiguration map[string]*producerConfigurations.RabbitMQProducerConfiguration, + isProducedNotifications ...func(message types.IMessage), +) (producer.Producer, error) { + return NewRabbitMQProducer( + p.rabbitmqOptions, + p.connection, + rabbitmqProducersConfiguration, + p.logger, + p.eventSerializer, + isProducedNotifications...) +} diff --git a/internal/pkg/rabbitmq/producer/producercontracts/producer_factory.go b/internal/pkg/rabbitmq/producer/producercontracts/producer_factory.go new file mode 100644 index 00000000..8985bdeb --- /dev/null +++ b/internal/pkg/rabbitmq/producer/producercontracts/producer_factory.go @@ -0,0 +1,14 @@ +package producercontracts + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/producer" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/configurations" +) + +type ProducerFactory interface { + CreateProducer( + rabbitmqProducersConfiguration map[string]*configurations.RabbitMQProducerConfiguration, + isProducedNotifications ...func(message types2.IMessage), + ) (producer.Producer, error) +} diff --git a/internal/pkg/rabbitmq/producer/rabbitmq_producer.go b/internal/pkg/rabbitmq/producer/rabbitmq_producer.go index b9c864f3..5a3ad10d 100644 --- a/internal/pkg/rabbitmq/producer/rabbitmq_producer.go +++ b/internal/pkg/rabbitmq/producer/rabbitmq_producer.go @@ -2,20 +2,19 @@ package producer import ( "context" - "fmt" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - messageHeader "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/message_header" - producer2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/otel/tracing/producer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/producer" - types2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/utils" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/producer/configurations" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + messageHeader "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/messageheader" + producer3 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/otel/tracing/producer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/producer" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" "emperror.dev/errors" "github.com/rabbitmq/amqp091-go" @@ -26,23 +25,26 @@ import ( type rabbitMQProducer struct { logger logger.Logger + rabbitmqOptions *config.RabbitmqOptions connection types.IConnection - eventSerializer serializer.EventSerializer + messageSerializer serializer.MessageSerializer producersConfigurations map[string]*configurations.RabbitMQProducerConfiguration isProducedNotifications []func(message types2.IMessage) } func NewRabbitMQProducer( + cfg *config.RabbitmqOptions, connection types.IConnection, rabbitmqProducersConfiguration map[string]*configurations.RabbitMQProducerConfiguration, logger logger.Logger, - eventSerializer serializer.EventSerializer, + eventSerializer serializer.MessageSerializer, isProducedNotifications ...func(message types2.IMessage), ) (producer.Producer, error) { p := &rabbitMQProducer{ logger: logger, + rabbitmqOptions: cfg, connection: connection, - eventSerializer: eventSerializer, + messageSerializer: eventSerializer, producersConfigurations: rabbitmqProducersConfiguration, } @@ -76,19 +78,12 @@ func (r *rabbitMQProducer) PublishMessageWithTopicName( meta metadata.Metadata, topicOrExchangeName string, ) error { - if message.IsMessage() == false { - return errors.New( - fmt.Sprintf( - "message %s is not a message type or message property is nil", - utils.GetMessageBaseReflectType(message), - ), - ) - } - producerConfiguration := r.getProducerConfigurationByMessage(message) if producerConfiguration == nil { - producerConfiguration = configurations.NewDefaultRabbitMQProducerConfiguration(message) + producerConfiguration = configurations.NewDefaultRabbitMQProducerConfiguration( + message, + ) } var exchange string @@ -110,7 +105,7 @@ func (r *rabbitMQProducer) PublishMessageWithTopicName( meta = r.getMetadata(message, meta) - producerOptions := &producer2.ProducerTracingOptions{ + producerOptions := &producer3.ProducerTracingOptions{ MessagingSystem: "rabbitmq", DestinationKind: "exchange", Destination: exchange, @@ -119,12 +114,12 @@ func (r *rabbitMQProducer) PublishMessageWithTopicName( }, } - serializedObj, err := r.eventSerializer.Serialize(message) + serializedObj, err := r.messageSerializer.Serialize(message) if err != nil { return err } - ctx, beforeProduceSpan := producer2.StartProducerSpan( + ctx, beforeProduceSpan := producer3.StartProducerSpan( ctx, message, &meta, @@ -134,11 +129,14 @@ func (r *rabbitMQProducer) PublishMessageWithTopicName( // https://github.com/rabbitmq/rabbitmq-tutorials/blob/master/go/publisher_confirms.go if r.connection == nil { - return producer2.FinishProducerSpan(beforeProduceSpan, errors.New("connection is nil")) + return producer3.FinishProducerSpan( + beforeProduceSpan, + errors.New("connection is nil"), + ) } if r.connection.IsClosed() { - return producer2.FinishProducerSpan( + return producer3.FinishProducerSpan( beforeProduceSpan, errors.New("connection is closed, wait for connection alive"), ) @@ -147,17 +145,17 @@ func (r *rabbitMQProducer) PublishMessageWithTopicName( // create a unique channel on the connection and in the end close the channel channel, err := r.connection.Channel() if err != nil { - return producer2.FinishProducerSpan(beforeProduceSpan, err) + return producer3.FinishProducerSpan(beforeProduceSpan, err) } defer channel.Close() err = r.ensureExchange(producerConfiguration, channel, exchange) if err != nil { - return producer2.FinishProducerSpan(beforeProduceSpan, err) + return producer3.FinishProducerSpan(beforeProduceSpan, err) } if err := channel.Confirm(false); err != nil { - return producer2.FinishProducerSpan(beforeProduceSpan, err) + return producer3.FinishProducerSpan(beforeProduceSpan, err) } confirms := make(chan amqp091.Confirmation) @@ -168,7 +166,7 @@ func (r *rabbitMQProducer) PublishMessageWithTopicName( MessageId: message.GeMessageId(), Timestamp: time.Now(), Headers: metadata.MetadataToMap(meta), - Type: message.GetEventTypeName(), // typeMapper.GetTypeName(message) - just message type name not full type name because in other side package name for type could be different + Type: message.GetMessageTypeName(), // typeMapper.GetTypeName(message) - just message type name not full type name because in other side package name for type could be different ContentType: serializedObj.ContentType, Body: serializedObj.Data, DeliveryMode: producerConfiguration.DeliveryMode, @@ -188,11 +186,14 @@ func (r *rabbitMQProducer) PublishMessageWithTopicName( props, ) if err != nil { - return producer2.FinishProducerSpan(beforeProduceSpan, err) + return producer3.FinishProducerSpan(beforeProduceSpan, err) } if confirmed := <-confirms; !confirmed.Ack { - return producer2.FinishProducerSpan(beforeProduceSpan, errors.New("ack not confirmed")) + return producer3.FinishProducerSpan( + beforeProduceSpan, + errors.New("ack not confirmed"), + ) } if len(r.isProducedNotifications) > 0 { @@ -203,7 +204,7 @@ func (r *rabbitMQProducer) PublishMessageWithTopicName( } } - return producer2.FinishProducerSpan(beforeProduceSpan, err) + return producer3.FinishProducerSpan(beforeProduceSpan, err) } func (r *rabbitMQProducer) getMetadata( @@ -212,13 +213,9 @@ func (r *rabbitMQProducer) getMetadata( ) metadata.Metadata { meta = metadata.FromMetadata(meta) - if message.GetEventTypeName() == "" { - message.SetEventTypeName( - typeMapper.GetTypeName(message), - ) // just message type name not full type name because in other side package name for type could be different) - } - messageHeader.SetMessageType(meta, message.GetEventTypeName()) - messageHeader.SetMessageContentType(meta, r.eventSerializer.ContentType()) + // just message type name not full type name because in other side package name for type could be different + messageHeader.SetMessageType(meta, message.GetMessageTypeName()) + messageHeader.SetMessageContentType(meta, r.messageSerializer.ContentType()) if messageHeader.GetMessageId(meta) == "" { messageHeader.SetMessageId(meta, message.GeMessageId()) diff --git a/internal/pkg/rabbitmq/producer/rabbitmq_producer_test.go b/internal/pkg/rabbitmq/producer/rabbitmq_producer_test.go index 22d89d6f..ffc6f27f 100644 --- a/internal/pkg/rabbitmq/producer/rabbitmq_producer_test.go +++ b/internal/pkg/rabbitmq/producer/rabbitmq_producer_test.go @@ -4,16 +4,15 @@ import ( "context" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer/json" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - types2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - config2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/otel/tracing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" - testUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + types2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer/json" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/rabbitmq" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/require" @@ -22,43 +21,56 @@ import ( func Test_Publish_Message(t *testing.T) { testUtils.SkipCI(t) - defaultLogger.SetupDefaultLogger() - eventSerializer := serializer.NewDefaultEventSerializer(json.NewDefaultSerializer()) + eventSerializer := json.NewDefaultMessageJsonSerializer( + json.NewDefaultJsonSerializer(), + ) ctx := context.Background() tp, err := tracing.NewOtelTracing( - &config2.OpenTelemetryOptions{ + &tracing.TracingOptions{ ServiceName: "test", Enabled: true, AlwaysOnSampler: true, - JaegerExporterOptions: &config2.JaegerExporterOptions{ - AgentHost: "localhost", - AgentPort: "6831", + ZipkinExporterOptions: &tracing.ZipkinExporterOptions{ + Url: "http://localhost:9411/api/v2/spans", }, }, - environemnt.Development, + environment.Development, ) if err != nil { return } - defer tp.TracerProvider.Shutdown(ctx) - - conn, err := types.NewRabbitMQConnection(&config.RabbitmqOptions{ - RabbitmqHostOptions: &config.RabbitmqHostOptions{ - UserName: "guest", - Password: "guest", - HostName: "localhost", - Port: 5672, - }, - }) + defer tp.Shutdown(ctx) + + //options := &config.RabbitmqOptions{ + // RabbitmqHostOptions: &config.RabbitmqHostOptions{ + // UserName: "guest", + // Password: "guest", + // HostName: "localhost", + // Port: 5672, + // }, + //} + + rabbitmqHostOption, err := rabbitmq.NewRabbitMQTestContainers(defaultLogger.GetLogger()). + PopulateContainerOptions(ctx, t) + require.NoError(t, err) + + options := &config.RabbitmqOptions{ + RabbitmqHostOptions: rabbitmqHostOption, + } + + conn, err := types.NewRabbitMQConnection(options) require.NoError(t, err) - rabbitmqProducer, err := NewRabbitMQProducer( + producerFactory := NewProducerFactory( + options, conn, - nil, - defaultLogger.Logger, eventSerializer, + defaultLogger.GetLogger(), ) + + rabbitmqProducer, err := producerFactory.CreateProducer(nil) + require.NoError(t, err) err = rabbitmqProducer.PublishMessage(ctx, NewProducerMessage("test"), nil) diff --git a/internal/pkg/rabbitmq/rabbitmq_fx.go b/internal/pkg/rabbitmq/rabbitmq_fx.go index d601b5eb..46a43f57 100644 --- a/internal/pkg/rabbitmq/rabbitmq_fx.go +++ b/internal/pkg/rabbitmq/rabbitmq_fx.go @@ -5,13 +5,15 @@ import ( "fmt" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/health" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - bus2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/bus" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/producer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/bus" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" + bus2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/bus" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/producer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/bus" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/factory" + producer2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/types" "go.uber.org/fx" ) @@ -31,20 +33,21 @@ var ( // - order is not important in provide // - provide can have parameter and will resolve if registered // - execute its func only if it requested - rabbitmqProviders = fx.Options(fx.Provide( //nolint:gochecknoglobals - config.ProvideConfig, - ), + rabbitmqProviders = fx.Options( + fx.Provide(config.ProvideConfig), fx.Provide(types.NewRabbitMQConnection), fx.Provide(fx.Annotate( bus.NewRabbitmqBus, - fx.ParamTags(``, ``, ``, ``, `optional:"true"`), + fx.ParamTags(``, ``, ``, `optional:"true"`), fx.As(new(producer.Producer)), fx.As(new(bus2.Bus)), fx.As(new(bus.RabbitmqBus)), )), + fx.Provide(factory.NewConsumerFactory), + fx.Provide(producer2.NewProducerFactory), fx.Provide(fx.Annotate( NewRabbitMQHealthChecker, - fx.As(new(health.Health)), + fx.As(new(contracts.Health)), fx.ResultTags(fmt.Sprintf(`group:"%s"`, "healths")), ))) @@ -52,7 +55,9 @@ var ( // - they execute by their orders // - invokes always execute its func compare to provides that only run when we request for them. // - return value will be discarded and can not be provided - rabbitmqInvokes = fx.Options(fx.Invoke(registerHooks)) //nolint:gochecknoglobals + rabbitmqInvokes = fx.Options( + fx.Invoke(registerHooks), + ) //nolint:gochecknoglobals ) // we don't want to register any dependencies here, its func body should execute always even we don't request for that, so we should use `invoke` @@ -77,7 +82,10 @@ func registerHooks( go func() { // if (ctx.Err() == nil), context not canceled or deadlined if err := bus.Start(lifeTimeCtx); err != nil { - logger.Errorf("(bus.Start) error in running rabbitmq server: {%v}", err) + logger.Errorf( + "(bus.Start) error in running rabbitmq server: {%v}", + err, + ) return } }() diff --git a/internal/pkg/rabbitmq/test/in-memory/rabbitmq_harnes.go b/internal/pkg/rabbitmq/test/in-memory/rabbitmq_harnes.go index 58699134..8827ac11 100644 --- a/internal/pkg/rabbitmq/test/in-memory/rabbitmq_harnes.go +++ b/internal/pkg/rabbitmq/test/in-memory/rabbitmq_harnes.go @@ -3,15 +3,15 @@ package in_memory import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/metadata" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" + consumer2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/metadata" ) type RabbitmqInMemoryHarnesses struct { publishedMessage []types.IMessage consumedMessage []types.IMessage - consumerHandlers map[types.IMessage][]consumer.ConsumerHandler + consumerHandlers map[types.IMessage][]consumer2.ConsumerHandler } func NewRabbitmqInMemoryHarnesses() *RabbitmqInMemoryHarnesses { @@ -53,7 +53,7 @@ func (r *RabbitmqInMemoryHarnesses) Stop(ctx context.Context) error { func (r *RabbitmqInMemoryHarnesses) ConnectConsumerHandler( messageType types.IMessage, - consumerHandler consumer.ConsumerHandler, + consumerHandler consumer2.ConsumerHandler, ) error { r.consumerHandlers[messageType] = append(r.consumerHandlers[messageType], consumerHandler) return nil @@ -61,7 +61,7 @@ func (r *RabbitmqInMemoryHarnesses) ConnectConsumerHandler( func (r *RabbitmqInMemoryHarnesses) ConnectConsumer( messageType types.IMessage, - consumer consumer.Consumer, + consumer consumer2.Consumer, ) error { return nil } diff --git a/internal/pkg/rabbitmq/test/testcontainer/rabbitmq_test_container.go b/internal/pkg/rabbitmq/test/testcontainer/rabbitmq_test_container.go new file mode 100644 index 00000000..c117a0d0 --- /dev/null +++ b/internal/pkg/rabbitmq/test/testcontainer/rabbitmq_test_container.go @@ -0,0 +1,95 @@ +package testcontainer + +import ( + "context" + "fmt" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + + "github.com/docker/docker/api/types/container" + "github.com/docker/go-connections/nat" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +func CreatingContainerOptions( + ctx context.Context, + t *testing.T, +) (*config.RabbitmqHostOptions, error) { + t.Helper() + + // https://github.com/testcontainers/testcontainers-go + // https://dev.to/remast/go-integration-tests-using-testcontainers-9o5 + containerReq := testcontainers.ContainerRequest{ + Image: fmt.Sprintf( + "%s:%s", + "rabbitmq", + "management", + ), + ExposedPorts: []string{"5672/tcp", "15672/tcp"}, + WaitingFor: wait.ForListeningPort( + nat.Port("5672/tcp"), + ), + HostConfigModifier: func(hostConfig *container.HostConfig) { + hostConfig.AutoRemove = true + }, + Hostname: "localhost", + Env: map[string]string{ + "RABBITMQ_DEFAULT_USER": "guest", + "RABBITMQ_DEFAULT_PASS": "guest", + }, + } + + dbContainer, err := testcontainers.GenericContainer( + context.Background(), + testcontainers.GenericContainerRequest{ + ContainerRequest: containerReq, + Started: true, + }) + if err != nil { + return nil, err + } + + //// Clean up the container after the test is complete + t.Cleanup(func() { + if terr := dbContainer.Terminate(ctx); terr != nil { + t.Fatalf("failed to terminate container: %s", err) + } + }) + + // get a free random host port for rabbitmq `Tcp Port` + hostPort, err := dbContainer.MappedPort( + ctx, + nat.Port("5672/tcp"), + ) + if err != nil { + return nil, err + } + + // https://github.com/michaelklishin/rabbit-hole/issues/74 + // get a free random host port for rabbitmq UI `Http Port` + uiHttpPort, err := dbContainer.MappedPort( + ctx, + nat.Port("15672/tcp"), + ) + if err != nil { + return nil, err + } + + host, err := dbContainer.Host(ctx) + if err != nil { + return nil, err + } + + option := &config.RabbitmqHostOptions{ + UserName: "guest", + Password: "guest", + HostName: host, + VirtualHost: "/", + Port: hostPort.Int(), + HttpPort: uiHttpPort.Int(), + } + + return option, nil +} diff --git a/internal/pkg/rabbitmq/types/connection.go b/internal/pkg/rabbitmq/types/connection.go index a1dc869d..2360602d 100644 --- a/internal/pkg/rabbitmq/types/connection.go +++ b/internal/pkg/rabbitmq/types/connection.go @@ -3,9 +3,9 @@ package types import ( "fmt" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" - errorUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/utils/error_utils" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + errorUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils/errorutils" "emperror.dev/errors" "github.com/rabbitmq/amqp091-go" @@ -131,13 +131,17 @@ func (c *internalConnection) handleReconnecting() { select { case err := <-c.errConnectionChan: if err != nil { - defaultLogger.Logger.Info("Rabbitmq Connection Reconnecting started") + defaultLogger.GetLogger(). + Info("Rabbitmq Connection Reconnecting started") err := c.connect() if err != nil { - defaultLogger.Logger.Error(fmt.Sprintf("Error in reconnecting, %s", err)) + defaultLogger.GetLogger(). + Error(fmt.Sprintf("Error in reconnecting, %s", err)) continue } - defaultLogger.Logger.Info("Rabbitmq Connection Reconnected") + + defaultLogger.GetLogger(). + Info("Rabbitmq Connection Reconnected") c.isConnected = true c.reconnectedChan <- struct{}{} continue diff --git a/internal/pkg/redis/health.go b/internal/pkg/redis/health.go index 8ab22b02..fadc4d85 100644 --- a/internal/pkg/redis/health.go +++ b/internal/pkg/redis/health.go @@ -3,7 +3,7 @@ package redis import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/health" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" "github.com/redis/go-redis/v9" ) @@ -12,7 +12,7 @@ type RedisHealthChecker struct { client *redis.Client } -func NewRedisHealthChecker(client *redis.Client) health.Health { +func NewRedisHealthChecker(client *redis.Client) contracts.Health { return &RedisHealthChecker{client} } diff --git a/internal/pkg/redis/redis.go b/internal/pkg/redis/redis.go index 742c104d..46d9569d 100644 --- a/internal/pkg/redis/redis.go +++ b/internal/pkg/redis/redis.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/redis/go-redis/extra/redisotel/v9" "github.com/redis/go-redis/v9" ) @@ -34,5 +35,9 @@ func NewRedisClient(cfg *RedisOptions) *redis.Client { PoolTimeout: poolTimeout, }) + if cfg.EnableTracing { + _ = redisotel.InstrumentTracing(universalClient) + } + return universalClient } diff --git a/internal/pkg/redis/redis_fx.go b/internal/pkg/redis/redis_fx.go index 266bf24f..c0da0674 100644 --- a/internal/pkg/redis/redis_fx.go +++ b/internal/pkg/redis/redis_fx.go @@ -4,10 +4,9 @@ import ( "context" "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/health" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" - "github.com/redis/go-redis/extra/redisotel/v9" "github.com/redis/go-redis/v9" "go.uber.org/fx" ) @@ -15,7 +14,11 @@ import ( var ( // Module provided to fxlog // https://uber-go.github.io/fx/modules.html - Module = fx.Module("redisfx", redisProviders, redisInvokes) //nolint:gochecknoglobals + Module = fx.Module( + "redisfx", + redisProviders, + redisInvokes, + ) //nolint:gochecknoglobals redisProviders = fx.Options(fx.Provide( //nolint:gochecknoglobals NewRedisClient, @@ -29,16 +32,21 @@ var ( //), fx.Annotate( NewRedisHealthChecker, - fx.As(new(health.Health)), + fx.As(new(contracts.Health)), fx.ResultTags(fmt.Sprintf(`group:"%s"`, "healths")), ), provideConfig)) - redisInvokes = fx.Options(fx.Invoke(registerHooks), //nolint:gochecknoglobals - fx.Invoke(EnableTracing)) + redisInvokes = fx.Options( + fx.Invoke(registerHooks), + ) //nolint:gochecknoglobals ) -func registerHooks(lc fx.Lifecycle, client redis.UniversalClient, logger logger.Logger) { +func registerHooks( + lc fx.Lifecycle, + client redis.UniversalClient, + logger logger.Logger, +) { lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { return client.Ping(ctx).Err() @@ -54,7 +62,3 @@ func registerHooks(lc fx.Lifecycle, client redis.UniversalClient, logger logger. }, }) } - -func EnableTracing(redis *redis.Client) error { - return redisotel.InstrumentTracing(redis) -} diff --git a/internal/pkg/redis/redis_options.go b/internal/pkg/redis/redis_options.go index 788d828c..7b15d09e 100644 --- a/internal/pkg/redis/redis_options.go +++ b/internal/pkg/redis/redis_options.go @@ -1,23 +1,24 @@ package redis import ( - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" "github.com/iancoleman/strcase" ) -var optionName = strcase.ToLowerCamel(typeMapper.GetTypeNameByT[RedisOptions]()) +var optionName = strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[RedisOptions]()) type RedisOptions struct { - Host string `mapstructure:"host"` - Port int `mapstructure:"port"` - Password string `mapstructure:"password"` - Database int `mapstructure:"database"` - PoolSize int `mapstructure:"poolSize"` + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + Password string `mapstructure:"password"` + Database int `mapstructure:"database"` + PoolSize int `mapstructure:"poolSize"` + EnableTracing bool `mapstructure:"enableTracing" default:"true"` } -func provideConfig(environment environemnt.Environment) (*RedisOptions, error) { +func provideConfig(environment environment.Environment) (*RedisOptions, error) { return config.BindConfigKey[*RedisOptions](optionName, environment) } diff --git a/internal/pkg/reflection/reflection_helper/reflection_helper.go b/internal/pkg/reflection/reflectionhelper/reflection_helper.go similarity index 100% rename from internal/pkg/reflection/reflection_helper/reflection_helper.go rename to internal/pkg/reflection/reflectionhelper/reflection_helper.go diff --git a/internal/pkg/reflection/reflection_helper/reflection_helper_test.go b/internal/pkg/reflection/reflectionhelper/reflection_helper_test.go similarity index 93% rename from internal/pkg/reflection/reflection_helper/reflection_helper_test.go rename to internal/pkg/reflection/reflectionhelper/reflection_helper_test.go index e2b13bac..48183139 100644 --- a/internal/pkg/reflection/reflection_helper/reflection_helper_test.go +++ b/internal/pkg/reflection/reflectionhelper/reflection_helper_test.go @@ -101,7 +101,7 @@ func Test_Get_Field_Value_For_Exported_Fields_And_Addressable_Struct(t *testing. // field by name only work on struct not pointer type so we should get Elem() v := reflect.ValueOf(p).Elem() - name := GetFieldValue(v.FieldByName("Name")).Interface() + name := GetFieldValue(v.FieldByName("ShortTypeName")).Interface() age := GetFieldValue(v.FieldByName("Age")).Interface() assert.Equal(t, "John", name) @@ -125,7 +125,7 @@ func Test_Get_Field_Value_For_Exported_Fields_And_UnAddressable_Struct(t *testin // field by name only work on struct not pointer type so we should get Elem() v := reflect.ValueOf(&p).Elem() - name := GetFieldValue(v.FieldByName("Name")).Interface() + name := GetFieldValue(v.FieldByName("ShortTypeName")).Interface() age := GetFieldValue(v.FieldByName("Age")).Interface() assert.Equal(t, "John", name) @@ -149,7 +149,7 @@ func Test_Set_Field_For_Exported_Fields_And_Addressable_Struct(t *testing.T) { // field by name only work on struct not pointer type so we should get Elem() v := reflect.ValueOf(p).Elem() - name := GetFieldValue(v.FieldByName("Name")) + name := GetFieldValue(v.FieldByName("ShortTypeName")) age := GetFieldValue(v.FieldByName("Age")) SetFieldValue(name, "John") @@ -179,7 +179,7 @@ func Test_Set_Field_For_Exported_Fields_And_UnAddressable_Struct(t *testing.T) { // field by name only work on struct not pointer type so we should get Elem() v := reflect.ValueOf(&p).Elem() - name := GetFieldValue(v.FieldByName("Name")) + name := GetFieldValue(v.FieldByName("ShortTypeName")) age := GetFieldValue(v.FieldByName("Age")) SetFieldValue(name, "John") @@ -206,14 +206,14 @@ func Test_Set_Field_For_UnExported_Fields_And_UnAddressable_Struct(t *testing.T) func Test_Get_Unexported_Field_From_Method_And_Addressable_Struct(t *testing.T) { p := &PersonPrivate{name: "John", age: 20} - name := GetFieldValueFromMethodAndObject(p, "Name") + name := GetFieldValueFromMethodAndObject(p, "ShortTypeName") assert.Equal(t, "John", name.Interface()) } func Test_Get_Unexported_Field_From_Method_And_UnAddressable_Struct(t *testing.T) { p := PersonPrivate{name: "John", age: 20} - name := GetFieldValueFromMethodAndObject(p, "Name") + name := GetFieldValueFromMethodAndObject(p, "ShortTypeName") assert.Equal(t, "John", name.Interface()) } @@ -224,7 +224,7 @@ func Test_Convert_NoPointer_Type_To_Pointer_Type_With_Addr(t *testing.T) { p := PersonPrivate{name: "John", age: 20} v := reflect.ValueOf(&p).Elem() pointerType := v.Addr() - name := pointerType.MethodByName("Name").Call(nil)[0].Interface() + name := pointerType.MethodByName("ShortTypeName").Call(nil)[0].Interface() age := pointerType.MethodByName("Age").Call(nil)[0].Interface() assert.Equal(t, "John", name) diff --git a/internal/pkg/reflection/type_mappper/type_mapper_test.go b/internal/pkg/reflection/type_mappper/type_mapper_test.go deleted file mode 100644 index 36141d6b..00000000 --- a/internal/pkg/reflection/type_mappper/type_mapper_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package typeMapper - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTypes(t *testing.T) { - s := Test{A: 10} - s2 := &Test{A: 10} - - q2 := TypeByName("*typeMapper.Test") - q22 := InstanceByTypeName("*typeMapper.Test").(*Test) - q222 := InstancePointerByTypeName("*typeMapper.Test").(*Test) - q3 := TypeByNameAndImplementedInterface[ITest]("*typeMapper.Test") - q4 := InstanceByTypeNameAndImplementedInterface[ITest]("*typeMapper.Test") - - c1 := GetTypeFromGeneric[Test]() - c2 := GetTypeFromGeneric[*Test]() - c3 := GetTypeFromGeneric[ITest]() - - d1 := GetReflectType(Test{}) - d2 := GetReflectType(&Test{}) - d3 := GetReflectType((*ITest)(nil)) - - q := TypeByName("typeMapper.Test") - q1 := InstanceByTypeName("typeMapper.Test").(Test) - q11 := InstancePointerByTypeName("typeMapper.Test").(*Test) - - y := TypeByName("*Test") - y1 := InstanceByTypeName("*Test").(*Test) - y2 := InstancePointerByTypeName("*Test").(*Test) - - z := TypeByName("Test") - z1 := InstanceByTypeName("Test").(Test) - z2 := InstancePointerByTypeName("Test").(*Test) - - r := GenericInstanceByT[*Test]() - r2 := GenericInstanceByT[Test]() - - typeName := GetFullTypeName(s) - typeName2 := GetFullTypeName(s2) - typeName3 := GetTypeName(s2) - typeName4 := GetTypeName(s) - - assert.Equal(t, "typeMapper.Test", typeName) - assert.Equal(t, "*typeMapper.Test", typeName2) - assert.Equal(t, "*Test", typeName3) - assert.Equal(t, "Test", typeName4) - - q1.A = 100 - q22.A = 100 - - assert.NotNil(t, d1) - assert.NotNil(t, d2) - assert.NotNil(t, d3) - assert.NotNil(t, c1) - assert.NotNil(t, c2) - assert.NotNil(t, c3) - assert.NotNil(t, q) - assert.NotNil(t, q1) - assert.NotNil(t, q11) - assert.NotNil(t, q2) - assert.NotNil(t, q22) - assert.NotNil(t, q222) - assert.NotNil(t, q3) - assert.NotNil(t, q4) - assert.NotNil(t, r) - assert.NotNil(t, r2) - assert.NotNil(t, y) - assert.NotNil(t, y1) - assert.NotNil(t, y2) - assert.NotNil(t, z) - assert.NotNil(t, z1) - assert.NotNil(t, z2) - assert.NotZero(t, q1.A) - assert.NotZero(t, q22.A) -} - -type Test struct { - A int -} - -type ITest interface { - Method1() -} - -func (t *Test) Method1() { -} diff --git a/internal/pkg/reflection/type_mappper/type_mapper.go b/internal/pkg/reflection/typemapper/type_mapper.go similarity index 75% rename from internal/pkg/reflection/type_mappper/type_mapper.go rename to internal/pkg/reflection/typemapper/type_mapper.go index 759b56b8..89fae6b4 100644 --- a/internal/pkg/reflection/type_mappper/type_mapper.go +++ b/internal/pkg/reflection/typemapper/type_mapper.go @@ -1,4 +1,4 @@ -package typeMapper +package typemapper // https://stackoverflow.com/a/34722791/581476 // https://stackoverflow.com/questions/7850140/how-do-you-create-a-new-instance-of-a-struct-from-its-type-at-run-time-in-go @@ -9,6 +9,8 @@ import ( "reflect" "strings" "unsafe" + + "github.com/iancoleman/strcase" ) var ( @@ -32,7 +34,8 @@ func discoverTypes() { for _, off := range offs { emptyInterface := (*emptyInterface)(unsafe.Pointer(&typ)) emptyInterface.data = resolveTypeOff(rodata, off) - if typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct { + if typ.Kind() == reflect.Ptr && + typ.Elem().Kind() == reflect.Struct { // just discover pointer types, but we also register this pointer type actual struct type to the registry loadedTypePtr := typ loadedType := typ.Elem() @@ -105,9 +108,11 @@ func TypesByName(typeName string) []reflect.Type { return nil } -func TypeByNameAndImplementedInterface[TInterface interface{}](typeName string) reflect.Type { +func TypeByNameAndImplementedInterface[TInterface interface{}]( + typeName string, +) reflect.Type { // https://stackoverflow.com/questions/7132848/how-to-get-the-reflect-type-of-an-interface - implementedInterface := GetTypeFromGeneric[TInterface]() + implementedInterface := GetGenericTypeByT[TInterface]() if types, ok := types[typeName]; ok { for _, t := range types { if t.Implements(implementedInterface) { @@ -115,6 +120,7 @@ func TypeByNameAndImplementedInterface[TInterface interface{}](typeName string) } } } + return nil } @@ -122,7 +128,7 @@ func TypesImplementedInterfaceWithFilterTypes[TInterface interface{}]( types []reflect.Type, ) []reflect.Type { // https://stackoverflow.com/questions/7132848/how-to-get-the-reflect-type-of-an-interface - implementedInterface := GetTypeFromGeneric[TInterface]() + implementedInterface := GetGenericTypeByT[TInterface]() var res []reflect.Type for _, t := range types { @@ -136,7 +142,7 @@ func TypesImplementedInterfaceWithFilterTypes[TInterface interface{}]( func TypesImplementedInterface[TInterface interface{}]() []reflect.Type { // https://stackoverflow.com/questions/7132848/how-to-get-the-reflect-type-of-an-interface - implementedInterface := GetTypeFromGeneric[TInterface]() + implementedInterface := GetGenericTypeByT[TInterface]() var res []reflect.Type for _, t := range types { @@ -152,16 +158,30 @@ func TypesImplementedInterface[TInterface interface{}]() []reflect.Type { // GetFullTypeName returns the full name of the type by its package name func GetFullTypeName(input interface{}) string { + if input == nil { + return "" + } + t := reflect.TypeOf(input) return t.String() } +func GetGenericFullTypeNameByT[T any]() string { + t := reflect.TypeOf((*T)(nil)).Elem() + + return t.String() +} + func GetFullTypeNameByType(typ reflect.Type) string { return typ.String() } // GetTypeName returns the name of the type without its package name func GetTypeName(input interface{}) string { + if input == nil { + return "" + } + t := reflect.TypeOf(input) if t.Kind() != reflect.Ptr { return t.Name() @@ -170,7 +190,33 @@ func GetTypeName(input interface{}) string { return fmt.Sprintf("*%s", t.Elem().Name()) } -func GetTypeNameByT[T any]() string { +func GetSnakeTypeName(input interface{}) string { + if input == nil { + return "" + } + + t := reflect.TypeOf(input) + if t.Kind() != reflect.Ptr { + return t.Name() + } + + return strcase.ToSnake(t.Elem().Name()) +} + +func GetKebabTypeName(input interface{}) string { + if input == nil { + return "" + } + + t := reflect.TypeOf(input) + if t.Kind() != reflect.Ptr { + return t.Name() + } + + return strcase.ToKebab(t.Elem().Name()) +} + +func GetGenericTypeNameByT[T any]() string { t := reflect.TypeOf((*T)(nil)).Elem() if t.Kind() != reflect.Ptr { return t.Name() @@ -179,7 +225,21 @@ func GetTypeNameByT[T any]() string { return fmt.Sprintf("*%s", t.Elem().Name()) } -func GetNonPointerTypeName(input interface{}) string { +func GetGenericNonePointerTypeNameByT[T any]() string { + t := reflect.TypeOf((*T)(nil)).Elem() + if t.Kind() != reflect.Ptr { + return t.Name() + } + + return t.Elem().Name() +} + +// GetNonePointerTypeName returns the name of the type without its package name and its pointer +func GetNonePointerTypeName(input interface{}) string { + if input == nil { + return "" + } + t := reflect.TypeOf(input) if t.Kind() != reflect.Ptr { return t.Name() @@ -189,6 +249,10 @@ func GetNonPointerTypeName(input interface{}) string { } func GetTypeNameByType(typ reflect.Type) string { + if typ == nil { + return "" + } + if typ.Kind() != reflect.Ptr { return typ.Name() } @@ -204,6 +268,19 @@ func TypeByPackageName(pkgPath string, name string) reflect.Type { return nil } +func GetPackageName(value interface{}) string { + inputType := reflect.TypeOf(value) + if inputType.Kind() == reflect.Ptr { + inputType = inputType.Elem() + } + + packagePath := inputType.PkgPath() + + parts := strings.Split(packagePath, "/") + + return parts[len(parts)-1] +} + func TypesByPackageName(pkgPath string, name string) []reflect.Type { if pkgTypes, ok := packages[pkgPath]; ok { return pkgTypes[name] @@ -211,7 +288,7 @@ func TypesByPackageName(pkgPath string, name string) []reflect.Type { return nil } -func GetTypeFromGeneric[T interface{}]() reflect.Type { +func GetGenericTypeByT[T interface{}]() reflect.Type { res := reflect.TypeOf((*T)(nil)).Elem() return res } @@ -244,7 +321,7 @@ func GetBaseReflectType(value interface{}) reflect.Type { func GenericInstanceByT[T any]() T { // https://stackoverflow.com/questions/7132848/how-to-get-the-reflect-type-of-an-interface - typ := GetTypeFromGeneric[T]() + typ := GetGenericTypeByT[T]() return getInstanceFromType(typ).(T) } @@ -261,12 +338,23 @@ func InstanceByTypeName(name string) interface{} { return getInstanceFromType(typ) } -func InstanceByTypeNameAndImplementedInterface[TInterface interface{}](name string) interface{} { +func EmptyInstanceByTypeNameAndImplementedInterface[TInterface interface{}]( + name string, +) interface{} { typ := TypeByNameAndImplementedInterface[TInterface](name) return getInstanceFromType(typ) } +func EmptyInstanceByTypeAndImplementedInterface[TInterface interface{}]( + typ reflect.Type, +) interface{} { + // we use short type name instead of full type name because this typ in other receiver packages could have different package name + typeName := GetTypeName(typ) + + return EmptyInstanceByTypeNameAndImplementedInterface[TInterface](typeName) +} + // InstancePointerByTypeName return an empty pointer instance of the type by its name // If the type is a pointer type, it will return a pointer instance of the type and // if the type is a struct type, it will return a pointer to the struct @@ -299,7 +387,7 @@ func getInstanceFromType(typ reflect.Type) interface{} { // return reflect.New(typ).Elem().Interface() } -func GetImplementInterfaceTypes[T any]() map[string][]reflect.Type { +func GetGenericImplementInterfaceTypesT[T any]() map[string][]reflect.Type { result := make(map[string][]reflect.Type) // Get the interface type @@ -324,3 +412,14 @@ func GetImplementInterfaceTypes[T any]() map[string][]reflect.Type { return result } + +func ImplementedInterfaceT[T any](obj interface{}) bool { + // Get the interface type + interfaceType := reflect.TypeOf((*T)(nil)).Elem() + + typ := GetReflectType(obj) + + implemented := typ.Implements(interfaceType) + + return implemented +} diff --git a/internal/pkg/reflection/typemapper/type_mapper_test.go b/internal/pkg/reflection/typemapper/type_mapper_test.go new file mode 100644 index 00000000..b2066fdb --- /dev/null +++ b/internal/pkg/reflection/typemapper/type_mapper_test.go @@ -0,0 +1,154 @@ +//go:build unit +// +build unit + +package typemapper + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_GetTypeNameByT(t *testing.T) { + pointerTypeName := GetGenericTypeNameByT[*Test]() + nonePointerTypeName := GetGenericTypeNameByT[Test]() + + require.Equal(t, pointerTypeName, "*Test") + require.Equal(t, nonePointerTypeName, "Test") +} + +func Test_GetNonePointerTypeNameByT(t *testing.T) { + pointerTypeName := GetGenericNonePointerTypeNameByT[*Test]() + nonePointerTypeName := GetGenericNonePointerTypeNameByT[Test]() + + require.Equal(t, pointerTypeName, "Test") + require.Equal(t, nonePointerTypeName, "Test") +} + +func Test_TypeByName(t *testing.T) { + s1 := TypeByName("*typemapper.Test") + s2 := TypeByName("typemapper.Test") + s3 := TypeByName("*Test") + s4 := TypeByName("Test") + + assert.NotNil(t, s1) + assert.NotNil(t, s2) + assert.NotNil(t, s3) + assert.NotNil(t, s4) +} + +func Test_GetTypeName(t *testing.T) { + t1 := Test{A: 10} + t2 := &Test{A: 10} + + typeName1 := GetTypeName(t1) + typeName2 := GetTypeName(t2) + + assert.Equal(t, "Test", typeName1) + assert.Equal(t, "*Test", typeName2) +} + +func Test_GetFullTypeName(t *testing.T) { + t1 := Test{A: 10} + t2 := &Test{A: 10} + + typeName1 := GetFullTypeName(t1) + typeName2 := GetFullTypeName(t2) + typeName3 := GetGenericFullTypeNameByT[*Test]() + typeName4 := GetGenericFullTypeNameByT[Test]() + + assert.Equal(t, "typemapper.Test", typeName1) + assert.Equal(t, "*typemapper.Test", typeName2) + assert.Equal(t, "*typemapper.Test", typeName3) + assert.Equal(t, "typemapper.Test", typeName4) +} + +func Test_InstanceByTypeName(t *testing.T) { + s1 := InstanceByTypeName("typemapper.Test").(Test) + s1.A = 100 + assert.NotNil(t, s1) + assert.NotZero(t, s1.A) + + s2 := InstanceByTypeName("*typemapper.Test").(*Test) + s2.A = 100 + assert.NotNil(t, s2) + assert.NotZero(t, s2.A) + + s3 := InstanceByTypeName("*Test").(*Test) + assert.NotNil(t, s3) + + s4 := InstanceByTypeName("Test").(Test) + assert.NotNil(t, s4) +} + +func Test_InstancePointerByTypeName(t *testing.T) { + s1 := InstancePointerByTypeName("*typemapper.Test").(*Test) + s2 := InstancePointerByTypeName("typemapper.Test").(*Test) + s3 := InstancePointerByTypeName("*Test").(*Test) + s4 := InstancePointerByTypeName("Test").(*Test) + + assert.NotNil(t, s1) + assert.NotNil(t, s2) + assert.NotNil(t, s3) + assert.NotNil(t, s4) +} + +func Test_GetTypeFromGeneric(t *testing.T) { + s1 := GetGenericTypeByT[Test]() + s2 := GetGenericTypeByT[*Test]() + s3 := GetGenericTypeByT[ITest]() + + assert.NotNil(t, s1) + assert.NotNil(t, s2) + assert.NotNil(t, s3) +} + +func Test_GenericInstanceByT(t *testing.T) { + s1 := GenericInstanceByT[*Test]() + s2 := GenericInstanceByT[Test]() + + assert.NotNil(t, s1) + assert.NotNil(t, s2) +} + +func Test_TypeByNameAndImplementedInterface(t *testing.T) { + s1 := TypeByNameAndImplementedInterface[ITest]("*typemapper.Test") + + assert.NotNil(t, s1) +} + +func Test_EmptyInstanceByTypeNameAndImplementedInterface(t *testing.T) { + s1 := EmptyInstanceByTypeNameAndImplementedInterface[ITest]("*typemapper.Test") + + assert.NotNil(t, s1) +} + +func Test_GetReflectType(t *testing.T) { + s1 := GetReflectType(Test{}) + s2 := GetReflectType(&Test{}) + s3 := GetReflectType((*ITest)(nil)) + + assert.NotNil(t, s1) + assert.NotNil(t, s2) + assert.NotNil(t, s3) +} + +func Test_GetPackageName(t *testing.T) { + pkName := GetPackageName(&Test{}) + pkName2 := GetPackageName(Test{}) + + assert.Equal(t, "typemapper", pkName) + assert.Equal(t, "typemapper", pkName2) +} + +type Test struct { + A int +} + +type ITest interface { + Method1() +} + +func (t *Test) Method1() { +} diff --git a/internal/pkg/reflection/type_mappper/unsafe_types.go b/internal/pkg/reflection/typemapper/unsafe_types.go similarity index 94% rename from internal/pkg/reflection/type_mappper/unsafe_types.go rename to internal/pkg/reflection/typemapper/unsafe_types.go index b90fbdcf..e4034894 100644 --- a/internal/pkg/reflection/type_mappper/unsafe_types.go +++ b/internal/pkg/reflection/typemapper/unsafe_types.go @@ -1,4 +1,4 @@ -package typeMapper +package typemapper import "unsafe" diff --git a/internal/pkg/reflection/type_registry/type_registry.go b/internal/pkg/reflection/typeregistry/type_registry.go similarity index 95% rename from internal/pkg/reflection/type_registry/type_registry.go rename to internal/pkg/reflection/typeregistry/type_registry.go index d981fa33..1cef8174 100644 --- a/internal/pkg/reflection/type_registry/type_registry.go +++ b/internal/pkg/reflection/typeregistry/type_registry.go @@ -1,4 +1,4 @@ -package type_registry +package typeregistry // Ref:https://stackoverflow.com/questions/23030884/is-there-a-way-to-create-an-instance-of-a-struct-from-a-string import "reflect" diff --git a/internal/pkg/test/containers/contracts/eventstoredb_container.go b/internal/pkg/test/containers/contracts/eventstoredb_container.go index aa6aa36c..198de868 100644 --- a/internal/pkg/test/containers/contracts/eventstoredb_container.go +++ b/internal/pkg/test/containers/contracts/eventstoredb_container.go @@ -4,9 +4,7 @@ import ( "context" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb/config" - - "github.com/EventStore/EventStore-Client-Go/esdb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/config" ) type EventstoreDBContainerOptions struct { @@ -21,15 +19,11 @@ type EventstoreDBContainerOptions struct { } type EventstoreDBContainer interface { - CreatingContainerOptions( + PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*EventstoreDBContainerOptions, ) (*config.EventStoreDbOptions, error) - Start(ctx context.Context, - t *testing.T, - options ...*EventstoreDBContainerOptions) (*esdb.Client, error) - Cleanup(ctx context.Context) error } diff --git a/internal/pkg/test/containers/contracts/gorm_container.go b/internal/pkg/test/containers/contracts/gorm_container.go index a96bdfc2..dbf1a232 100644 --- a/internal/pkg/test/containers/contracts/gorm_container.go +++ b/internal/pkg/test/containers/contracts/gorm_container.go @@ -4,9 +4,7 @@ import ( "context" "testing" - gormPostgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/gorm_postgres" - - "gorm.io/gorm" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" ) type PostgresContainerOptions struct { @@ -22,8 +20,7 @@ type PostgresContainerOptions struct { } type GormContainer interface { - Start(ctx context.Context, t *testing.T, options ...*PostgresContainerOptions) (*gorm.DB, error) - CreatingContainerOptions( + PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*PostgresContainerOptions, diff --git a/internal/pkg/test/containers/contracts/mongo_container.go b/internal/pkg/test/containers/contracts/mongo_container.go index b7b9622e..f1bff7b7 100644 --- a/internal/pkg/test/containers/contracts/mongo_container.go +++ b/internal/pkg/test/containers/contracts/mongo_container.go @@ -4,9 +4,7 @@ import ( "context" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mongodb" - - "go.mongodb.org/mongo-driver/mongo" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" ) type MongoContainerOptions struct { @@ -22,12 +20,7 @@ type MongoContainerOptions struct { } type MongoContainer interface { - Start( - ctx context.Context, - t *testing.T, - options ...*MongoContainerOptions, - ) (*mongo.Client, error) - CreatingContainerOptions( + PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*MongoContainerOptions, diff --git a/internal/pkg/test/containers/contracts/postgrespgx_container.go b/internal/pkg/test/containers/contracts/postgrespgx_container.go index 7d1bd432..86b8b0a5 100644 --- a/internal/pkg/test/containers/contracts/postgrespgx_container.go +++ b/internal/pkg/test/containers/contracts/postgrespgx_container.go @@ -4,12 +4,11 @@ import ( "context" "testing" - postgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/postgres_pgx" + postgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgrespgx" ) type PostgresPgxContainer interface { - Start(ctx context.Context, t *testing.T, options ...*PostgresContainerOptions) (*postgres.Pgx, error) - CreatingContainerOptions( + PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*PostgresContainerOptions, diff --git a/internal/pkg/test/containers/contracts/rabbitmq_container.go b/internal/pkg/test/containers/contracts/rabbitmq_container.go index 5f012fa5..3b752ec4 100644 --- a/internal/pkg/test/containers/contracts/rabbitmq_container.go +++ b/internal/pkg/test/containers/contracts/rabbitmq_container.go @@ -5,10 +5,7 @@ import ( "fmt" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/bus" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" ) type RabbitMQContainerOptions struct { @@ -25,7 +22,13 @@ type RabbitMQContainerOptions struct { } func (h *RabbitMQContainerOptions) AmqpEndPoint() string { - return fmt.Sprintf("amqp://%s:%s@%s:%d", h.UserName, h.Password, h.Host, h.HostPort) + return fmt.Sprintf( + "amqp://%s:%s@%s:%d", + h.UserName, + h.Password, + h.Host, + h.HostPort, + ) } func (h *RabbitMQContainerOptions) HttpEndPoint() string { @@ -33,13 +36,7 @@ func (h *RabbitMQContainerOptions) HttpEndPoint() string { } type RabbitMQContainer interface { - Start(ctx context.Context, - t *testing.T, - serializer serializer.EventSerializer, - rabbitmqBuilderFunc configurations.RabbitMQConfigurationBuilderFuc, - options ...*RabbitMQContainerOptions) (bus.Bus, error) - - CreatingContainerOptions( + PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*RabbitMQContainerOptions, diff --git a/internal/pkg/test/containers/contracts/redis_container.go b/internal/pkg/test/containers/contracts/redis_container.go index f44cbfe6..4042d1cf 100644 --- a/internal/pkg/test/containers/contracts/redis_container.go +++ b/internal/pkg/test/containers/contracts/redis_container.go @@ -4,9 +4,7 @@ import ( "context" "testing" - redis2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/redis" - - "github.com/redis/go-redis/v9" + redis2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/redis" ) type RedisContainerOptions struct { @@ -21,12 +19,7 @@ type RedisContainerOptions struct { } type RedisContainer interface { - Start( - ctx context.Context, - t *testing.T, - options ...*RedisContainerOptions, - ) (redis.UniversalClient, error) - CreatingContainerOptions( + PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*RedisContainerOptions, diff --git a/internal/pkg/test/containers/dockertest/gorm/gorm_container.go b/internal/pkg/test/containers/dockertest/gorm/gorm_container.go index d1d8912e..f75c1095 100644 --- a/internal/pkg/test/containers/dockertest/gorm/gorm_container.go +++ b/internal/pkg/test/containers/dockertest/gorm/gorm_container.go @@ -6,13 +6,12 @@ import ( "strconv" "testing" - gormPostgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/gorm_postgres" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/phayes/freeport" - "gorm.io/gorm" ) type gormDockerTest struct { @@ -35,11 +34,13 @@ func NewGormDockerTest() contracts.GormContainer { } } -func (g *gormDockerTest) CreatingContainerOptions( +func (g *gormDockerTest) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.PostgresContainerOptions, ) (*gormPostgres.GormOptions, error) { + t.Helper() + // https://github.com/ory/dockertest/blob/v3/examples/PostgreSQL.md // https://github.com/bozd4g/fb.testcontainers pool, err := dockertest.NewPool("") @@ -57,7 +58,10 @@ func (g *gormDockerTest) CreatingContainerOptions( config.RestartPolicy = docker.RestartPolicy{Name: "no"} }) if err != nil { - log.Fatalf("Could not start resource (Postgresql Test Container): %s", err) + log.Fatalf( + "Could not start resource (Postgresql Test Container): %s", + err, + ) } resource.Expire( @@ -91,21 +95,6 @@ func (g *gormDockerTest) CreatingContainerOptions( return postgresoptions, nil } -func (g *gormDockerTest) Start( - ctx context.Context, - t *testing.T, - options ...*contracts.PostgresContainerOptions, -) (*gorm.DB, error) { - gormOptions, err := g.CreatingContainerOptions(ctx, t, options...) - if err != nil { - return nil, err - } - - db, err := gormPostgres.NewGorm(gormOptions) - - return db, nil -} - func (g *gormDockerTest) Cleanup(ctx context.Context) error { return g.resource.Close() } @@ -154,7 +143,10 @@ func (g *gormDockerTest) getRunOptions( ExposedPorts: []string{g.defaultOptions.Port}, PortBindings: map[docker.Port][]docker.PortBinding{ docker.Port(g.defaultOptions.Port): { - {HostIP: "0.0.0.0", HostPort: strconv.Itoa(g.defaultOptions.HostPort)}, + { + HostIP: "0.0.0.0", + HostPort: strconv.Itoa(g.defaultOptions.HostPort), + }, }, }, } diff --git a/internal/pkg/test/containers/dockertest/gorm/gorm_container_test.go b/internal/pkg/test/containers/dockertest/gorm/gorm_container_test.go index ebee5aba..ad285dfa 100644 --- a/internal/pkg/test/containers/dockertest/gorm/gorm_container_test.go +++ b/internal/pkg/test/containers/dockertest/gorm/gorm_container_test.go @@ -4,12 +4,32 @@ import ( "context" "testing" - "github.com/stretchr/testify/require" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + + "github.com/stretchr/testify/assert" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + "gorm.io/gorm" ) func Test_Gorm_Container(t *testing.T) { - gorm, err := NewGormDockerTest().Start(context.Background(), t) - require.NoError(t, err) + ctx := context.Background() + var gorm *gorm.DB + + fxtest.New(t, + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + core.Module, + gormPostgres.Module, + fx.Decorate(GormDockerTestConatnerOptionsDecorator(t, ctx)), + fx.Populate(&gorm), + ).RequireStart() - require.NotNil(t, gorm) + assert.NotNil(t, gorm) } diff --git a/internal/pkg/test/containers/dockertest/gorm/gorm_fx.go b/internal/pkg/test/containers/dockertest/gorm/gorm_fx.go new file mode 100644 index 00000000..d0a6580d --- /dev/null +++ b/internal/pkg/test/containers/dockertest/gorm/gorm_fx.go @@ -0,0 +1,14 @@ +package gorm + +import ( + "context" + "testing" + + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" +) + +var GormDockerTestConatnerOptionsDecorator = func(t *testing.T, ctx context.Context) interface{} { + return func(c *gormPostgres.GormOptions) (*gormPostgres.GormOptions, error) { + return NewGormDockerTest().PopulateContainerOptions(ctx, t) + } +} diff --git a/internal/pkg/test/containers/dockertest/mongo/mongo_container.go b/internal/pkg/test/containers/dockertest/mongo/mongo_container.go index 0fa6683c..09beb0dd 100644 --- a/internal/pkg/test/containers/dockertest/mongo/mongo_container.go +++ b/internal/pkg/test/containers/dockertest/mongo/mongo_container.go @@ -7,12 +7,11 @@ import ( "strconv" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mongodb" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" - "go.mongodb.org/mongo-driver/mongo" ) type mongoDockerTest struct { @@ -35,7 +34,7 @@ func NewMongoDockerTest() contracts.MongoContainer { } } -func (g *mongoDockerTest) CreatingContainerOptions( +func (g *mongoDockerTest) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.MongoContainerOptions, @@ -48,13 +47,16 @@ func (g *mongoDockerTest) CreatingContainerOptions( runOptions := g.getRunOptions(options...) // pull mongodb docker image for version 5.0 - resource, err := pool.RunWithOptions(runOptions, func(config *docker.HostConfig) { - // set AutoRemove to true so that stopped container goes away by itself - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{ - Name: "no", - } - }) + resource, err := pool.RunWithOptions( + runOptions, + func(config *docker.HostConfig) { + // set AutoRemove to true so that stopped container goes away by itself + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{ + Name: "no", + } + }, + ) if err != nil { log.Fatalf("Could not start resource (Mongo Container): %s", err) } @@ -64,7 +66,9 @@ func (g *mongoDockerTest) CreatingContainerOptions( ) // Tell docker to hard kill the container in 120 seconds exponential backoff-retry, because the application_exceptions in the container might not be ready to accept connections yet g.resource = resource - port, _ := strconv.Atoi(resource.GetPort(fmt.Sprintf("%s/tcp", g.defaultOptions.Port))) + port, _ := strconv.Atoi( + resource.GetPort(fmt.Sprintf("%s/tcp", g.defaultOptions.Port)), + ) g.defaultOptions.HostPort = port t.Cleanup(func() { _ = resource.Close() }) @@ -91,24 +95,6 @@ func (g *mongoDockerTest) CreatingContainerOptions( return mongoOptions, nil } -func (g *mongoDockerTest) Start( - ctx context.Context, - t *testing.T, - options ...*contracts.MongoContainerOptions, -) (*mongo.Client, error) { - mongoOptions, err := g.CreatingContainerOptions(ctx, t, options...) - if err != nil { - return nil, err - } - - db, err := mongodb.NewMongoDB(mongoOptions) - if err != nil { - return nil, err - } - - return db, nil -} - func (g *mongoDockerTest) Cleanup(ctx context.Context) error { return g.resource.Close() } diff --git a/internal/pkg/test/containers/dockertest/mongo/mongo_container_test.go b/internal/pkg/test/containers/dockertest/mongo/mongo_container_test.go index 519305ef..9d20da8d 100644 --- a/internal/pkg/test/containers/dockertest/mongo/mongo_container_test.go +++ b/internal/pkg/test/containers/dockertest/mongo/mongo_container_test.go @@ -4,13 +4,32 @@ import ( "context" "testing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/mongo" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" ) func Test_Mongo_Container(t *testing.T) { - mongo, err := NewMongoDockerTest().Start(context.Background(), t) - require.NoError(t, err) + ctx := context.Background() + var mongoClient *mongo.Client + + fxtest.New(t, + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + core.Module, + mongodb.Module, + fx.Decorate(MongoDockerTestContainerOptionsDecorator(t, ctx)), + fx.Populate(&mongoClient), + ).RequireStart() - assert.NotNil(t, mongo) + assert.NotNil(t, mongoClient) } diff --git a/internal/pkg/test/containers/dockertest/mongo/mongo_fx.go b/internal/pkg/test/containers/dockertest/mongo/mongo_fx.go new file mode 100644 index 00000000..6d875c53 --- /dev/null +++ b/internal/pkg/test/containers/dockertest/mongo/mongo_fx.go @@ -0,0 +1,15 @@ +package mongo + +import ( + "context" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" +) + +var MongoDockerTestContainerOptionsDecorator = func(t *testing.T, ctx context.Context) interface{} { + return func(c *mongodb.MongoDbOptions, logger logger.Logger) (*mongodb.MongoDbOptions, error) { + return NewMongoDockerTest().PopulateContainerOptions(ctx, t) + } +} diff --git a/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_container.go b/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_container.go index 423412df..0ebdc3e0 100644 --- a/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_container.go +++ b/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_container.go @@ -7,14 +7,9 @@ import ( "strconv" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/bus" - bus2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/bus" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/configurations" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" @@ -29,11 +24,6 @@ type rabbitmqDockerTest struct { } func NewRabbitMQDockerTest(logger logger.Logger) contracts.RabbitMQContainer { - pool, err := dockertest.NewPool("") - if err != nil { - log.Fatalf("Could not connect to docker: %s", err) - } - return &rabbitmqDockerTest{ defaultOptions: &contracts.RabbitMQContainerOptions{ Ports: []string{"5672", "15672"}, @@ -46,25 +36,32 @@ func NewRabbitMQDockerTest(logger logger.Logger) contracts.RabbitMQContainer { Name: "rabbitmq-dockertest", }, logger: logger, - pool: pool, } } -func (g *rabbitmqDockerTest) CreatingContainerOptions( +func (g *rabbitmqDockerTest) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.RabbitMQContainerOptions, ) (*config.RabbitmqHostOptions, error) { + pool, err := dockertest.NewPool("") + if err != nil { + log.Fatalf("Could not connect to docker: %s", err) + } + runOptions := g.getRunOptions(options...) // pull mongodb docker image for version 5.0 - resource, err := g.pool.RunWithOptions(runOptions, func(config *docker.HostConfig) { - // set AutoRemove to true so that stopped container goes away by itself - config.AutoRemove = true - config.RestartPolicy = docker.RestartPolicy{ - Name: "no", - } - }) + resource, err := pool.RunWithOptions( + runOptions, + func(config *docker.HostConfig) { + // set AutoRemove to true so that stopped container goes away by itself + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{ + Name: "no", + } + }, + ) if err != nil { log.Fatalf("Could not start resource (RabbitMQ Container): %s", err) } @@ -86,62 +83,29 @@ func (g *rabbitmqDockerTest) CreatingContainerOptions( t.Cleanup(func() { _ = resource.Close() }) - isConnectable := isConnectable(g.logger, g.defaultOptions) - if !isConnectable { - return g.CreatingContainerOptions(context.Background(), t, options...) - } - - opt := &config.RabbitmqHostOptions{ - UserName: g.defaultOptions.UserName, - Password: g.defaultOptions.Password, - HostName: g.defaultOptions.Host, - VirtualHost: g.defaultOptions.VirtualHost, - Port: g.defaultOptions.HostPort, - HttpPort: g.defaultOptions.HttpPort, - } - - return opt, nil -} - -func (g *rabbitmqDockerTest) Start( - ctx context.Context, - t *testing.T, - serializer serializer.EventSerializer, - rabbitmqBuilderFunc configurations.RabbitMQConfigurationBuilderFuc, - options ...*contracts.RabbitMQContainerOptions, -) (bus.Bus, error) { - rabbitmqHostOptions, err := g.CreatingContainerOptions(ctx, t, options...) - if err != nil { - return nil, err - } - - var mqBus bus.Bus - if err := g.pool.Retry(func() error { - config := &config.RabbitmqOptions{ - RabbitmqHostOptions: rabbitmqHostOptions, - } - conn, err := types.NewRabbitMQConnection(config) - if err != nil { - return err - } - - mqBus, err = bus2.NewRabbitmqBus( - config, - serializer, - g.logger, - conn, - rabbitmqBuilderFunc) - if err != nil { - return err + //isConnectable := isConnectable(g.logger, g.defaultOptions) + //if !isConnectable { + // return g.PopulateContainerOptions(context.Background(), t, options...) + //} + + var rabbitmqoptions *config.RabbitmqHostOptions + if err = pool.Retry(func() error { + rabbitmqoptions = &config.RabbitmqHostOptions{ + UserName: g.defaultOptions.UserName, + Password: g.defaultOptions.Password, + HostName: g.defaultOptions.Host, + VirtualHost: g.defaultOptions.VirtualHost, + Port: g.defaultOptions.HostPort, + HttpPort: g.defaultOptions.HttpPort, } return nil }); err != nil { - g.logger.Errorf("Could not connect to docker: %s", err) + log.Fatalf("Could not connect to docker: %s", err) return nil, err } - return mqBus, nil + return rabbitmqoptions, nil } func (g *rabbitmqDockerTest) Cleanup(ctx context.Context) error { @@ -187,12 +151,27 @@ func (g *rabbitmqDockerTest) getRunOptions( return runOptions } -func isConnectable(logger logger.Logger, options *contracts.RabbitMQContainerOptions) bool { +func isConnectable( + logger logger.Logger, + options *contracts.RabbitMQContainerOptions, +) bool { conn, err := amqp091.Dial( - fmt.Sprintf("amqp://%s:%s@%s:%d", options.UserName, options.Password, options.Host, options.HostPort), + fmt.Sprintf( + "amqp://%s:%s@%s:%d", + options.UserName, + options.Password, + options.Host, + options.HostPort, + ), ) if err != nil { - logError(logger, options.UserName, options.Password, options.Host, options.HostPort) + logError( + logger, + options.UserName, + options.Password, + options.Host, + options.HostPort, + ) return false } @@ -200,24 +179,48 @@ func isConnectable(logger logger.Logger, options *contracts.RabbitMQContainerOpt defer conn.Close() if err != nil || (conn != nil && conn.IsClosed()) { - logError(logger, options.UserName, options.Password, options.Host, options.HostPort) + logError( + logger, + options.UserName, + options.Password, + options.Host, + options.HostPort, + ) return false } logger.Infof( "Opened rabbitmq connection on host: %s", - fmt.Sprintf("amqp://%s:%s@%s:%d", options.UserName, options.Password, options.Host, options.HostPort), + fmt.Sprintf( + "amqp://%s:%s@%s:%d", + options.UserName, + options.Password, + options.Host, + options.HostPort, + ), ) return true } -func logError(logger logger.Logger, userName string, password string, host string, hostPort int) { +func logError( + logger logger.Logger, + userName string, + password string, + host string, + hostPort int, +) { // we should not use `t.Error` or `t.Errorf` for logging errors because it will `fail` our test at the end and, we just should use logs without error like log.Error (not log.Fatal) logger.Errorf( fmt.Sprintf( "Error in creating rabbitmq connection with %s", - fmt.Sprintf("amqp://%s:%s@%s:%d", userName, password, host, hostPort), + fmt.Sprintf( + "amqp://%s:%s@%s:%d", + userName, + password, + host, + hostPort, + ), ), ) } diff --git a/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_container_test.go b/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_container_test.go index 954ccf0d..0e061453 100644 --- a/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_container_test.go +++ b/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_container_test.go @@ -5,49 +5,68 @@ import ( "testing" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer/json" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - messageConsumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - rabbitmqConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/configurations" - consumerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer/configurations" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/messaging/consumer" - testUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/bus" + messageConsumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + rabbitmq2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + rabbitmqConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + consumerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging/consumer" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/require" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" ) func Test_RabbitMQ_Container(t *testing.T) { ctx := context.Background() fakeConsumer := consumer.NewRabbitMQFakeTestConsumerHandler[*ProducerConsumerMessage]() - defaultLogger.SetupDefaultLogger() - eventSerializer := serializer.NewDefaultEventSerializer(json.NewDefaultSerializer()) - rabbitmq, err := NewRabbitMQDockerTest( - defaultLogger.Logger, - ).Start(ctx, t, eventSerializer, func(builder rabbitmqConfigurations.RabbitMQConfigurationBuilder) { - builder.AddConsumer(ProducerConsumerMessage{}, - func(consumerBuilder consumerConfigurations.RabbitMQConsumerConfigurationBuilder) { - consumerBuilder.WithHandlers( - func(handlerBuilder messageConsumer.ConsumerHandlerConfigurationBuilder) { - handlerBuilder.AddHandler(fakeConsumer) - }, - ) - }) - }) + var rabbitmqbus bus.Bus - require.NoError(t, err) - require.NotNil(t, rabbitmq) + fxtest.New(t, + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + core.Module, + rabbitmq2.ModuleFunc( + func(l logger.Logger) rabbitmqConfigurations.RabbitMQConfigurationBuilderFuc { + return func(builder configurations.RabbitMQConfigurationBuilder) { + builder.AddConsumer( + ProducerConsumerMessage{}, + func(consumerBuilder consumerConfigurations.RabbitMQConsumerConfigurationBuilder) { + consumerBuilder.WithHandlers( + func(handlerBuilder messageConsumer.ConsumerHandlerConfigurationBuilder) { + handlerBuilder.AddHandler(fakeConsumer) + }, + ) + }, + ) + } + }, + ), + fx.Decorate(RabbitmqDockerTestContainerOptionsDecorator(t, ctx)), + fx.Populate(&rabbitmqbus), + ).RequireStart() + + require.NotNil(t, rabbitmqbus) - err = rabbitmq.Start(ctx) + err := rabbitmqbus.Start(ctx) require.NoError(t, err) - // wait for consumers ready to consume before publishing messages (for preventing messages lost) - time.Sleep(time.Second * 1) + //// wait for consumers ready to consume before publishing messages (for preventing messages lost) + time.Sleep(time.Second * 2) - err = rabbitmq.PublishMessage( + err = rabbitmqbus.PublishMessage( context.Background(), &ProducerConsumerMessage{ Data: "ssssssssss", diff --git a/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_fx.go b/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_fx.go index 7cf7af6b..6235760c 100644 --- a/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_fx.go +++ b/internal/pkg/test/containers/dockertest/rabbitmq/rabbitmq_fx.go @@ -4,13 +4,13 @@ import ( "context" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" ) var RabbitmqDockerTestContainerOptionsDecorator = func(t *testing.T, ctx context.Context) interface{} { return func(c *config.RabbitmqOptions, logger logger.Logger) (*config.RabbitmqOptions, error) { - rabbitmqHostOptions, err := NewRabbitMQDockerTest(logger).CreatingContainerOptions(ctx, t) + rabbitmqHostOptions, err := NewRabbitMQDockerTest(logger).PopulateContainerOptions(ctx, t) c.RabbitmqHostOptions = rabbitmqHostOptions return c, err diff --git a/internal/pkg/test/containers/gnomock/gorm/gorm_container.go b/internal/pkg/test/containers/gnomock/gorm/gorm_container.go index 91292a0a..1be19a14 100644 --- a/internal/pkg/test/containers/gnomock/gorm/gorm_container.go +++ b/internal/pkg/test/containers/gnomock/gorm/gorm_container.go @@ -5,8 +5,8 @@ import ( "log" "testing" - gormPostgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/gorm_postgres" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "emperror.dev/errors" "github.com/orlangure/gnomock" @@ -34,7 +34,7 @@ func NewGnoMockGormContainer() contracts.GormContainer { } } -func (g *gnoMockGormContainer) CreatingContainerOptions( +func (g *gnoMockGormContainer) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.PostgresContainerOptions, @@ -70,7 +70,7 @@ func (g *gnoMockGormContainer) Start( t *testing.T, options ...*contracts.PostgresContainerOptions, ) (*gorm.DB, error) { - gormOptions, err := g.CreatingContainerOptions(ctx, t, options...) + gormOptions, err := g.PopulateContainerOptions(ctx, t, options...) if err != nil { return nil, err } diff --git a/internal/pkg/test/containers/testcontainer/eventstoredb/eveentstoredb_container_test.go b/internal/pkg/test/containers/testcontainer/eventstoredb/eveentstoredb_container_test.go index e85211d0..b0717c89 100644 --- a/internal/pkg/test/containers/testcontainer/eventstoredb/eveentstoredb_container_test.go +++ b/internal/pkg/test/containers/testcontainer/eventstoredb/eveentstoredb_container_test.go @@ -4,17 +4,30 @@ import ( "context" "testing" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/EventStore/EventStore-Client-Go/esdb" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" ) func Test_Custom_EventStoreDB_Container(t *testing.T) { - defaultLogger.SetupDefaultLogger() + var esdbClient *esdb.Client + ctx := context.Background() - esdbInstance, err := NewEventstoreDBTestContainers(defaultLogger.Logger).Start(context.Background(), t) - require.NoError(t, err) - - assert.NotNil(t, esdbInstance) + fxtest.New(t, + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + core.Module, + eventstroredb.ModuleFunc(func() { + }), + fx.Decorate(EventstoreDBContainerOptionsDecorator(t, ctx)), + fx.Populate(&esdbClient), + ).RequireStart() } diff --git a/internal/pkg/test/containers/testcontainer/eventstoredb/eventstoredb_container.go b/internal/pkg/test/containers/testcontainer/eventstoredb/eventstoredb_container.go index 6c71df30..54ea0ac5 100644 --- a/internal/pkg/test/containers/testcontainer/eventstoredb/eventstoredb_container.go +++ b/internal/pkg/test/containers/testcontainer/eventstoredb/eventstoredb_container.go @@ -6,10 +6,10 @@ import ( "testing" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "emperror.dev/errors" "github.com/EventStore/EventStore-Client-Go/esdb" @@ -40,7 +40,7 @@ func NewEventstoreDBTestContainers(l logger.Logger) contracts.EventstoreDBContai } } -func (g *eventstoredbTestContainers) CreatingContainerOptions( +func (g *eventstoredbTestContainers) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.EventstoreDBContainerOptions, @@ -101,7 +101,7 @@ func (g *eventstoredbTestContainers) Start( t *testing.T, options ...*contracts.EventstoreDBContainerOptions, ) (*esdb.Client, error) { - eventstoredbOptions, err := g.CreatingContainerOptions(ctx, t, options...) + eventstoredbOptions, err := g.PopulateContainerOptions(ctx, t, options...) if err != nil { return nil, err } diff --git a/internal/pkg/test/containers/testcontainer/eventstoredb/eventstoredb_fx.go b/internal/pkg/test/containers/testcontainer/eventstoredb/eventstoredb_fx.go index 42a4ae09..3f76cf1d 100644 --- a/internal/pkg/test/containers/testcontainer/eventstoredb/eventstoredb_fx.go +++ b/internal/pkg/test/containers/testcontainer/eventstoredb/eventstoredb_fx.go @@ -4,13 +4,13 @@ import ( "context" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/eventstroredb/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" ) var EventstoreDBContainerOptionsDecorator = func(t *testing.T, ctx context.Context) interface{} { return func(c *config.EventStoreDbOptions, logger logger.Logger) (*config.EventStoreDbOptions, error) { - newOption, err := NewEventstoreDBTestContainers(logger).CreatingContainerOptions(ctx, t) + newOption, err := NewEventstoreDBTestContainers(logger).PopulateContainerOptions(ctx, t) if err != nil { return nil, err } @@ -21,7 +21,7 @@ var EventstoreDBContainerOptionsDecorator = func(t *testing.T, ctx context.Conte } var ReplaceEventStoreContainerOptions = func(t *testing.T, options *config.EventStoreDbOptions, ctx context.Context, logger logger.Logger) error { - newOption, err := NewEventstoreDBTestContainers(logger).CreatingContainerOptions(ctx, t) + newOption, err := NewEventstoreDBTestContainers(logger).PopulateContainerOptions(ctx, t) if err != nil { return err } diff --git a/internal/pkg/test/containers/testcontainer/gorm/gorm_container.go b/internal/pkg/test/containers/testcontainer/gorm/gorm_container.go index 7b601369..b0cd4f20 100644 --- a/internal/pkg/test/containers/testcontainer/gorm/gorm_container.go +++ b/internal/pkg/test/containers/testcontainer/gorm/gorm_container.go @@ -6,9 +6,9 @@ import ( "testing" "time" - gormPostgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/gorm_postgres" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "emperror.dev/errors" "github.com/docker/docker/api/types/container" @@ -43,7 +43,7 @@ func NewGormTestContainers(l logger.Logger) contracts.GormContainer { } } -func (g *gormTestContainers) CreatingContainerOptions( +func (g *gormTestContainers) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.PostgresContainerOptions, @@ -85,7 +85,7 @@ func (g *gormTestContainers) CreatingContainerOptions( isConnectable := isConnectable(ctx, g.logger, g.defaultOptions) if !isConnectable { - return g.CreatingContainerOptions(context.Background(), t, options...) + return g.PopulateContainerOptions(context.Background(), t, options...) } g.container = dbContainer @@ -106,7 +106,7 @@ func (g *gormTestContainers) Start( t *testing.T, options ...*contracts.PostgresContainerOptions, ) (*gorm.DB, error) { - gormOptions, err := g.CreatingContainerOptions(ctx, t, options...) + gormOptions, err := g.PopulateContainerOptions(ctx, t, options...) if err != nil { return nil, err } diff --git a/internal/pkg/test/containers/testcontainer/gorm/gorm_container_test.go b/internal/pkg/test/containers/testcontainer/gorm/gorm_container_test.go index e394f8ff..f22f0b32 100644 --- a/internal/pkg/test/containers/testcontainer/gorm/gorm_container_test.go +++ b/internal/pkg/test/containers/testcontainer/gorm/gorm_container_test.go @@ -5,23 +5,37 @@ import ( "testing" "time" - gormPostgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/gorm_postgres" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" "github.com/docker/go-connections/nat" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/testcontainers/testcontainers-go/wait" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + "gorm.io/gorm" ) func Test_Custom_Gorm_Container(t *testing.T) { ctx := context.Background() - defaultLogger.SetupDefaultLogger() - gorm, err := NewGormTestContainers(defaultLogger.Logger).Start(ctx, t) - require.NoError(t, err) + var gorm *gorm.DB + + fxtest.New(t, + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + core.Module, + gormPostgres.Module, + fx.Decorate(GormContainerOptionsDecorator(t, ctx)), + fx.Populate(&gorm), + ).RequireStart() assert.NotNil(t, gorm) } diff --git a/internal/pkg/test/containers/testcontainer/gorm/gorm_fx.go b/internal/pkg/test/containers/testcontainer/gorm/gorm_fx.go index 237f61fe..60956a25 100644 --- a/internal/pkg/test/containers/testcontainer/gorm/gorm_fx.go +++ b/internal/pkg/test/containers/testcontainer/gorm/gorm_fx.go @@ -4,12 +4,12 @@ import ( "context" "testing" - gormPostgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/gorm_postgres" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" ) var GormContainerOptionsDecorator = func(t *testing.T, ctx context.Context) interface{} { return func(c *gormPostgres.GormOptions, logger logger.Logger) (*gormPostgres.GormOptions, error) { - return NewGormTestContainers(logger).CreatingContainerOptions(ctx, t) + return NewGormTestContainers(logger).PopulateContainerOptions(ctx, t) } } diff --git a/internal/pkg/test/containers/testcontainer/mongo/mongo_container.go b/internal/pkg/test/containers/testcontainer/mongo/mongo_container.go index 7ed4d962..67b5d266 100644 --- a/internal/pkg/test/containers/testcontainer/mongo/mongo_container.go +++ b/internal/pkg/test/containers/testcontainer/mongo/mongo_container.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mongodb" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "emperror.dev/errors" "github.com/docker/docker/api/types/container" @@ -48,7 +48,7 @@ func NewMongoTestContainers(l logger.Logger) contracts.MongoContainer { } } -func (g *mongoTestContainers) CreatingContainerOptions( +func (g *mongoTestContainers) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.MongoContainerOptions, @@ -76,7 +76,10 @@ func (g *mongoTestContainers) CreatingContainerOptions( }) // get a free random host hostPort - hostPort, err := dbContainer.MappedPort(ctx, nat.Port(g.defaultOptions.Port)) + hostPort, err := dbContainer.MappedPort( + ctx, + nat.Port(g.defaultOptions.Port), + ) if err != nil { return nil, err } @@ -89,7 +92,7 @@ func (g *mongoTestContainers) CreatingContainerOptions( isConnectable := isConnectable(ctx, g.logger, g.defaultOptions) if !isConnectable { - return g.CreatingContainerOptions(context.Background(), t, options...) + return g.PopulateContainerOptions(context.Background(), t, options...) } g.container = dbContainer @@ -106,24 +109,6 @@ func (g *mongoTestContainers) CreatingContainerOptions( return option, nil } -func (g *mongoTestContainers) Start( - ctx context.Context, - t *testing.T, - options ...*contracts.MongoContainerOptions, -) (*mongo.Client, error) { - mongoOptions, err := g.CreatingContainerOptions(ctx, t, options...) - if err != nil { - return nil, err - } - - db, err := mongodb.NewMongoDB(mongoOptions) - if err != nil { - return nil, err - } - - return db, nil -} - func (g *mongoTestContainers) Cleanup(ctx context.Context) error { if err := g.container.Terminate(ctx); err != nil { return errors.WrapIf(err, "failed to terminate container: %s") @@ -158,10 +143,15 @@ func (g *mongoTestContainers) getRunOptions( } containerReq := testcontainers.ContainerRequest{ - Image: fmt.Sprintf("%s:%s", g.defaultOptions.ImageName, g.defaultOptions.Tag), + Image: fmt.Sprintf( + "%s:%s", + g.defaultOptions.ImageName, + g.defaultOptions.Tag, + ), ExposedPorts: []string{g.defaultOptions.Port}, - WaitingFor: wait.ForListeningPort(nat.Port(g.defaultOptions.Port)).WithPollInterval(2 * time.Second), - Hostname: g.defaultOptions.Host, + WaitingFor: wait.ForListeningPort(nat.Port(g.defaultOptions.Port)). + WithPollInterval(2 * time.Second), + Hostname: g.defaultOptions.Host, HostConfigModifier: func(hostConfig *container.HostConfig) { hostConfig.AutoRemove = true }, @@ -174,7 +164,11 @@ func (g *mongoTestContainers) getRunOptions( return containerReq } -func isConnectable(ctx context.Context, logger logger.Logger, mongoOptions *contracts.MongoContainerOptions) bool { +func isConnectable( + ctx context.Context, + logger logger.Logger, + mongoOptions *contracts.MongoContainerOptions, +) bool { uriAddress := fmt.Sprintf( "mongodb://%s:%s@%s:%d", mongoOptions.UserName, @@ -187,7 +181,12 @@ func isConnectable(ctx context.Context, logger logger.Logger, mongoOptions *cont SetMaxConnIdleTime(maxConnIdleTime). SetMinPoolSize(minPoolSize). SetMaxPoolSize(maxPoolSize) - opt = opt.SetAuth(options.Credential{Username: mongoOptions.UserName, Password: mongoOptions.Password}) + opt = opt.SetAuth( + options.Credential{ + Username: mongoOptions.UserName, + Password: mongoOptions.Password, + }, + ) mongoClient, err := mongo.Connect(ctx, opt) @@ -206,7 +205,10 @@ func isConnectable(ctx context.Context, logger logger.Logger, mongoOptions *cont return false } logger.Infof( - "Opened mongodb connection on host: %s:%d", mongoOptions.Host, mongoOptions.HostPort) + "Opened mongodb connection on host: %s:%d", + mongoOptions.Host, + mongoOptions.HostPort, + ) return true } diff --git a/internal/pkg/test/containers/testcontainer/mongo/mongo_container_test.go b/internal/pkg/test/containers/testcontainer/mongo/mongo_container_test.go index 958c88a4..ed1bd8cf 100644 --- a/internal/pkg/test/containers/testcontainer/mongo/mongo_container_test.go +++ b/internal/pkg/test/containers/testcontainer/mongo/mongo_container_test.go @@ -4,17 +4,33 @@ import ( "context" "testing" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/mongo" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" ) func Test_Custom_Mongo_Container(t *testing.T) { - defaultLogger.SetupDefaultLogger() + ctx := context.Background() - mongo, err := NewMongoTestContainers(defaultLogger.Logger).Start(context.Background(), t) - require.NoError(t, err) + var mongoClient *mongo.Client - assert.NotNil(t, mongo) + fxtest.New(t, + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + core.Module, + mongodb.Module, + fx.Decorate(MongoContainerOptionsDecorator(t, ctx)), + fx.Populate(&mongoClient), + ).RequireStart() + + assert.NotNil(t, mongoClient) } diff --git a/internal/pkg/test/containers/testcontainer/mongo/mongo_fx.go b/internal/pkg/test/containers/testcontainer/mongo/mongo_fx.go index f9ed16a6..35dc81ea 100644 --- a/internal/pkg/test/containers/testcontainer/mongo/mongo_fx.go +++ b/internal/pkg/test/containers/testcontainer/mongo/mongo_fx.go @@ -4,12 +4,12 @@ import ( "context" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" ) var MongoContainerOptionsDecorator = func(t *testing.T, ctx context.Context) interface{} { return func(c *mongodb.MongoDbOptions, logger logger.Logger) (*mongodb.MongoDbOptions, error) { - return NewMongoTestContainers(logger).CreatingContainerOptions(ctx, t) + return NewMongoTestContainers(logger).PopulateContainerOptions(ctx, t) } } diff --git a/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_container.go b/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_container.go index 2fa22079..a305f075 100644 --- a/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_container.go +++ b/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_container.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - postgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/postgres_pgx" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + postgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgrespgx" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "emperror.dev/errors" "github.com/docker/go-connections/nat" @@ -40,7 +40,7 @@ func NewPostgresPgxContainers(l logger.Logger) contracts.PostgresPgxContainer { } } -func (g *postgresPgxTestContainers) CreatingContainerOptions( +func (g *postgresPgxTestContainers) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.PostgresContainerOptions, @@ -97,7 +97,7 @@ func (g *postgresPgxTestContainers) Start( t *testing.T, options ...*contracts.PostgresContainerOptions, ) (*postgres.Pgx, error) { - postgresPgxOptions, err := g.CreatingContainerOptions(ctx, t, options...) + postgresPgxOptions, err := g.PopulateContainerOptions(ctx, t, options...) if err != nil { return nil, err } diff --git a/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_container_test.go b/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_container_test.go index 7f6d7436..2c8b2d42 100644 --- a/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_container_test.go +++ b/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_container_test.go @@ -4,16 +4,16 @@ import ( "context" "testing" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func Test_Custom_PostgresPgx_Container(t *testing.T) { - defaultLogger.SetupDefaultLogger() - - gorm, err := NewPostgresPgxContainers(defaultLogger.Logger).Start(context.Background(), t) + gorm, err := NewPostgresPgxContainers( + defaultLogger.GetLogger(), + ).Start(context.Background(), t) require.NoError(t, err) assert.NotNil(t, gorm) diff --git a/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_fx.go b/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_fx.go index 9ad471e8..ea402fb1 100644 --- a/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_fx.go +++ b/internal/pkg/test/containers/testcontainer/postgrespxg/postgrespgx_fx.go @@ -4,12 +4,12 @@ import ( "context" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - postgres "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/postgres_pgx" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + postgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgrespgx" ) var PostgresPgxContainerOptionsDecorator = func(t *testing.T, ctx context.Context) interface{} { return func(c *postgres.PostgresPgxOptions, logger logger.Logger) (*postgres.PostgresPgxOptions, error) { - return NewPostgresPgxContainers(logger).CreatingContainerOptions(ctx, t) + return NewPostgresPgxContainers(logger).PopulateContainerOptions(ctx, t) } } diff --git a/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_container.go b/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_container.go index 9c3af822..948acf02 100644 --- a/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_container.go +++ b/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_container.go @@ -6,14 +6,9 @@ import ( "testing" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/bus" - bus2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/bus" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/configurations" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "emperror.dev/errors" "github.com/docker/docker/api/types/container" @@ -51,7 +46,7 @@ func NewRabbitMQTestContainers(l logger.Logger) contracts.RabbitMQContainer { } } -func (g *rabbitmqTestContainers) CreatingContainerOptions( +func (g *rabbitmqTestContainers) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.RabbitMQContainerOptions, @@ -60,7 +55,6 @@ func (g *rabbitmqTestContainers) CreatingContainerOptions( // https://dev.to/remast/go-integration-tests-using-testcontainers-9o5 containerReq := g.getRunOptions(options...) - // TODO: Using Parallel Container dbContainer, err := testcontainers.GenericContainer( ctx, testcontainers.GenericContainerRequest{ @@ -109,7 +103,7 @@ func (g *rabbitmqTestContainers) CreatingContainerOptions( isConnectable := IsConnectable(g.logger, g.defaultOptions) if !isConnectable { - return g.CreatingContainerOptions(context.Background(), t, options...) + return g.PopulateContainerOptions(context.Background(), t, options...) } g.container = dbContainer @@ -126,45 +120,6 @@ func (g *rabbitmqTestContainers) CreatingContainerOptions( return option, nil } -func (g *rabbitmqTestContainers) Start( - ctx context.Context, - t *testing.T, - serializer serializer.EventSerializer, - rabbitmqBuilderFunc configurations.RabbitMQConfigurationBuilderFuc, - options ...*contracts.RabbitMQContainerOptions, -) (bus.Bus, error) { - rabbitHostOptions, err := g.CreatingContainerOptions(ctx, t, options...) - if err != nil { - return nil, err - } - - g.logger.Infof( - "rabbitmq connection is on host: %s", - rabbitHostOptions.AmqpEndPoint(), - ) - - rabbitmqConfig := &config.RabbitmqOptions{ - RabbitmqHostOptions: rabbitHostOptions, - } - conn, err := types.NewRabbitMQConnection(rabbitmqConfig) - if err != nil { - return nil, err - } - - mqBus, err := bus2.NewRabbitmqBus( - rabbitmqConfig, - serializer, - g.logger, - conn, - rabbitmqBuilderFunc, - ) - if err != nil { - return nil, err - } - - return mqBus, nil -} - func (g *rabbitmqTestContainers) Cleanup(ctx context.Context) error { if err := g.container.Terminate(ctx); err != nil { return errors.WrapIf(err, "failed to terminate container: %s") @@ -258,6 +213,7 @@ func IsConnectable( options.Password, ) _, err = rmqc.ListExchanges() + if err != nil { logger.Errorf( "Error in creating rabbitmq connection with http host: %s", diff --git a/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_container_test.go b/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_container_test.go index 81840d62..96d7ee51 100644 --- a/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_container_test.go +++ b/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_container_test.go @@ -5,49 +5,68 @@ import ( "testing" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/core/serializer/json" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" - messageConsumer "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/consumer" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - rabbitmqConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/configurations" - consumerConfigurations "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/consumer/configurations" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/messaging/consumer" - testUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/bus" + messageConsumer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + rabbitmq2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + rabbitmqConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + consumerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging/consumer" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/require" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" ) func Test_Custom_RabbitMQ_Container(t *testing.T) { ctx := context.Background() fakeConsumer := consumer.NewRabbitMQFakeTestConsumerHandler[*ProducerConsumerMessage]() - defaultLogger.SetupDefaultLogger() - eventSerializer := serializer.NewDefaultEventSerializer(json.NewDefaultSerializer()) - rabbitmq, err := NewRabbitMQTestContainers( - defaultLogger.Logger, - ).Start(ctx, t, eventSerializer, func(builder rabbitmqConfigurations.RabbitMQConfigurationBuilder) { - builder.AddConsumer(ProducerConsumerMessage{}, - func(consumerBuilder consumerConfigurations.RabbitMQConsumerConfigurationBuilder) { - consumerBuilder.WithHandlers( - func(handlerBuilder messageConsumer.ConsumerHandlerConfigurationBuilder) { - handlerBuilder.AddHandler(fakeConsumer) - }, - ) - }) - }) + var rabbitmqbus bus.Bus - require.NoError(t, err) - require.NotNil(t, rabbitmq) + fxtest.New(t, + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + core.Module, + rabbitmq2.ModuleFunc( + func(l logger.Logger) rabbitmqConfigurations.RabbitMQConfigurationBuilderFuc { + return func(builder configurations.RabbitMQConfigurationBuilder) { + builder.AddConsumer( + ProducerConsumerMessage{}, + func(consumerBuilder consumerConfigurations.RabbitMQConsumerConfigurationBuilder) { + consumerBuilder.WithHandlers( + func(handlerBuilder messageConsumer.ConsumerHandlerConfigurationBuilder) { + handlerBuilder.AddHandler(fakeConsumer) + }, + ) + }, + ) + } + }, + ), + fx.Decorate(RabbitmqContainerOptionsDecorator(t, ctx)), + fx.Populate(&rabbitmqbus), + ).RequireStart() + + require.NotNil(t, rabbitmqbus) - err = rabbitmq.Start(ctx) + err := rabbitmqbus.Start(ctx) require.NoError(t, err) - // wait for consumers ready to consume before publishing messages (for preventing messages lost) + //// wait for consumers ready to consume before publishing messages (for preventing messages lost) time.Sleep(time.Second * 1) - err = rabbitmq.PublishMessage( + err = rabbitmqbus.PublishMessage( context.Background(), &ProducerConsumerMessage{ Data: "ssssssssss", diff --git a/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_fx.go b/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_fx.go index 3dcc0e5d..1b4dc270 100644 --- a/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_fx.go +++ b/internal/pkg/test/containers/testcontainer/rabbitmq/rabbitmq_fx.go @@ -4,13 +4,13 @@ import ( "context" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" ) var RabbitmqContainerOptionsDecorator = func(t *testing.T, ctx context.Context) interface{} { return func(c *config.RabbitmqOptions, logger logger.Logger) (*config.RabbitmqOptions, error) { - rabbitmqHostOptions, err := NewRabbitMQTestContainers(logger).CreatingContainerOptions(ctx, t) + rabbitmqHostOptions, err := NewRabbitMQTestContainers(logger).PopulateContainerOptions(ctx, t) c.RabbitmqHostOptions = rabbitmqHostOptions return c, err diff --git a/internal/pkg/test/containers/testcontainer/redis/redis_container.go b/internal/pkg/test/containers/testcontainer/redis/redis_container.go index c2dcb052..92a0bcb2 100644 --- a/internal/pkg/test/containers/testcontainer/redis/redis_container.go +++ b/internal/pkg/test/containers/testcontainer/redis/redis_container.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - redis2 "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/redis" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/containers/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + redis2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/redis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/contracts" "emperror.dev/errors" "github.com/docker/go-connections/nat" @@ -38,7 +38,7 @@ func NewRedisTestContainers(l logger.Logger) contracts.RedisContainer { } } -func (g *redisTestContainers) CreatingContainerOptions( +func (g *redisTestContainers) PopulateContainerOptions( ctx context.Context, t *testing.T, options ...*contracts.RedisContainerOptions, @@ -66,7 +66,10 @@ func (g *redisTestContainers) CreatingContainerOptions( }) // get a free random host hostPort - hostPort, err := dbContainer.MappedPort(ctx, nat.Port(g.defaultOptions.Port)) + hostPort, err := dbContainer.MappedPort( + ctx, + nat.Port(g.defaultOptions.Port), + ) if err != nil { return nil, err } @@ -79,7 +82,7 @@ func (g *redisTestContainers) CreatingContainerOptions( isConnectable := isConnectable(ctx, g.logger, g.defaultOptions) if !isConnectable { - return g.CreatingContainerOptions(context.Background(), t, options...) + return g.PopulateContainerOptions(context.Background(), t, options...) } g.container = dbContainer @@ -93,21 +96,6 @@ func (g *redisTestContainers) CreatingContainerOptions( return reidsOptions, nil } -func (g *redisTestContainers) Start( - ctx context.Context, - t *testing.T, - options ...*contracts.RedisContainerOptions, -) (redis.UniversalClient, error) { - redisOptions, err := g.CreatingContainerOptions(ctx, t, options...) - - db := redis2.NewRedisClient(redisOptions) - if err != nil { - return nil, err - } - - return db, nil -} - func (g *redisTestContainers) Cleanup(ctx context.Context) error { if err := g.container.Terminate(ctx); err != nil { return errors.WrapIf(err, "failed to terminate container: %s") @@ -136,7 +124,11 @@ func (g *redisTestContainers) getRunOptions( } containerReq := testcontainers.ContainerRequest{ - Image: fmt.Sprintf("%s:%s", g.defaultOptions.ImageName, g.defaultOptions.Tag), + Image: fmt.Sprintf( + "%s:%s", + g.defaultOptions.ImageName, + g.defaultOptions.Tag, + ), ExposedPorts: []string{g.defaultOptions.Port}, WaitingFor: wait.ForListeningPort(nat.Port(g.defaultOptions.Port)). WithPollInterval(2 * time.Second), @@ -147,7 +139,11 @@ func (g *redisTestContainers) getRunOptions( return containerReq } -func isConnectable(ctx context.Context, logger logger.Logger, options *contracts.RedisContainerOptions) bool { +func isConnectable( + ctx context.Context, + logger logger.Logger, + options *contracts.RedisContainerOptions, +) bool { redisClient := redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", options.Host, options.HostPort), }) @@ -158,13 +154,18 @@ func isConnectable(ctx context.Context, logger logger.Logger, options *contracts if err != nil { // we should not use `t.Error` or `t.Errorf` for logging errors because it will `fail` our test at the end and, we just should use logs without error like log.Error (not log.Fatal) logger.Errorf( - "Error in creating redis connection with %s:%d", options.Host, options.HostPort) + "Error in creating redis connection with %s:%d", + options.Host, + options.HostPort, + ) return false } logger.Infof( - "Opened redis connection on host: %s:%d", options.Host, options.HostPort, + "Opened redis connection on host: %s:%d", + options.Host, + options.HostPort, ) return true diff --git a/internal/pkg/test/containers/testcontainer/redis/redis_container_test.go b/internal/pkg/test/containers/testcontainer/redis/redis_container_test.go index 07d0ea78..d3ba4740 100644 --- a/internal/pkg/test/containers/testcontainer/redis/redis_container_test.go +++ b/internal/pkg/test/containers/testcontainer/redis/redis_container_test.go @@ -4,17 +4,32 @@ import ( "context" "testing" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + redis2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/redis" + "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" ) func Test_Custom_Redis_Container(t *testing.T) { - defaultLogger.SetupDefaultLogger() + ctx := context.Background() + var redisClient redis.UniversalClient - redis, err := NewRedisTestContainers(defaultLogger.Logger).Start(context.Background(), t) - require.NoError(t, err) + fxtest.New(t, + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + core.Module, + redis2.Module, + fx.Decorate(RedisContainerOptionsDecorator(t, ctx)), + fx.Populate(&redisClient), + ).RequireStart() - assert.NotNil(t, redis) + assert.NotNil(t, redisClient) } diff --git a/internal/pkg/test/containers/testcontainer/redis/redis_fx.go b/internal/pkg/test/containers/testcontainer/redis/redis_fx.go index 418274a1..063c1e58 100644 --- a/internal/pkg/test/containers/testcontainer/redis/redis_fx.go +++ b/internal/pkg/test/containers/testcontainer/redis/redis_fx.go @@ -4,12 +4,12 @@ import ( "context" "testing" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/redis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/redis" ) var RedisContainerOptionsDecorator = func(t *testing.T, ctx context.Context) interface{} { return func(c *redis.RedisOptions, logger logger.Logger) (*redis.RedisOptions, error) { - return NewRedisTestContainers(logger).CreatingContainerOptions(ctx, t) + return NewRedisTestContainers(logger).PopulateContainerOptions(ctx, t) } } diff --git a/internal/pkg/test/hypothesis/hypothesis.go b/internal/pkg/test/hypothesis/hypothesis.go index e77a8814..de37eb1e 100644 --- a/internal/pkg/test/hypothesis/hypothesis.go +++ b/internal/pkg/test/hypothesis/hypothesis.go @@ -4,7 +4,7 @@ import ( "context" "time" - testUtils "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/utils" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" "github.com/goccy/go-reflect" "github.com/stretchr/testify/assert" diff --git a/internal/pkg/test/messaging/consumer/rabbitmq_fake_consumer.go b/internal/pkg/test/messaging/consumer/rabbitmq_fake_consumer.go index a32ee866..aeeb82e4 100644 --- a/internal/pkg/test/messaging/consumer/rabbitmq_fake_consumer.go +++ b/internal/pkg/test/messaging/consumer/rabbitmq_fake_consumer.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/hypothesis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/hypothesis" ) type RabbitMQFakeTestConsumerHandler[T any] struct { diff --git a/internal/pkg/test/messaging/utils.go b/internal/pkg/test/messaging/utils.go index 270956cd..89dff136 100644 --- a/internal/pkg/test/messaging/utils.go +++ b/internal/pkg/test/messaging/utils.go @@ -3,12 +3,12 @@ package messaging import ( "context" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/bus" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/types" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/messaging/utils" - typeMapper "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/reflection/type_mappper" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/hypothesis" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/test/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/bus" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/utils" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/hypothesis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging/consumer" ) func ShouldProduced[T types.IMessage]( diff --git a/internal/pkg/utils/error_utils/errors.go b/internal/pkg/utils/errorutils/errors.go similarity index 81% rename from internal/pkg/utils/error_utils/errors.go rename to internal/pkg/utils/errorutils/errors.go index a704ed6e..14ce17c5 100644 --- a/internal/pkg/utils/error_utils/errors.go +++ b/internal/pkg/utils/errorutils/errors.go @@ -5,8 +5,8 @@ import ( "runtime/debug" "strings" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/http/http_errors/contracts" - defaultLogger "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/default_logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/contracts" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" "emperror.dev/errors" ) @@ -24,7 +24,7 @@ func CheckErrMessages(err error, messages ...string) bool { return false } -// ErrorsWithStack returns a string contains grpc_errors messages in the stack with its stack trace levels for given error +// ErrorsWithStack returns a string contains errors messages in the stack with its stack trace levels for given error func ErrorsWithStack(err error) string { res := fmt.Sprintf("%+v\n", err) return res @@ -94,6 +94,7 @@ func RootStackTrace(err error) string { func HandlePanic() { if r := recover(); r != nil { - defaultLogger.Logger.Error("stacktrace from panic: \n" + string(debug.Stack())) + defaultLogger.GetLogger(). + Error("stacktrace from panic: \n" + string(debug.Stack())) } } diff --git a/internal/pkg/utils/error_utils/errors_test.go b/internal/pkg/utils/errorutils/errors_test.go similarity index 100% rename from internal/pkg/utils/error_utils/errors_test.go rename to internal/pkg/utils/errorutils/errors_test.go diff --git a/internal/pkg/utils/pagination.go b/internal/pkg/utils/pagination.go index 1dfe5320..3021d707 100644 --- a/internal/pkg/utils/pagination.go +++ b/internal/pkg/utils/pagination.go @@ -5,7 +5,7 @@ import ( "math" "strconv" - "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" "emperror.dev/errors" "github.com/goccy/go-json" diff --git a/internal/pkg/validation/pipeline/validation_pipeline.go b/internal/pkg/validation/pipeline/validation_pipeline.go new file mode 100644 index 00000000..86d33f67 --- /dev/null +++ b/internal/pkg/validation/pipeline/validation_pipeline.go @@ -0,0 +1,34 @@ +package pipeline + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/validation" + + "github.com/mehdihadeli/go-mediatr" +) + +type mediatorValidationPipeline struct { + logger logger.Logger +} + +func NewMediatorValidationPipeline(l logger.Logger) mediatr.PipelineBehavior { + return &mediatorValidationPipeline{logger: l} +} + +func (m mediatorValidationPipeline) Handle( + ctx context.Context, + request interface{}, + next mediatr.RequestHandlerFunc, +) (interface{}, error) { + v, ok := request.(validation.Validator) + if ok { + err := v.Validate() + if err != nil { + return nil, err + } + } + + return next(ctx) +} diff --git a/internal/pkg/validation/validation.go b/internal/pkg/validation/validation.go new file mode 100644 index 00000000..bcf5f758 --- /dev/null +++ b/internal/pkg/validation/validation.go @@ -0,0 +1,5 @@ +package validation + +type Validator interface { + Validate() error +} diff --git a/internal/services/catalogreadservice/.air.toml b/internal/services/catalogreadservice/.air.toml new file mode 100644 index 00000000..a4e5f123 --- /dev/null +++ b/internal/services/catalogreadservice/.air.toml @@ -0,0 +1,37 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "tmp\\app\\main.exe" + cmd = "go build -o ./tmp/app/main.exe ./cmd/app/main.go" + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + kill_delay = "0s" + log = "build-errors.log" + send_interrupt = false + stop_on_error = true + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false diff --git a/internal/services/catalogreadservice/.dockerignore b/internal/services/catalogreadservice/.dockerignore new file mode 100644 index 00000000..d2b3b429 --- /dev/null +++ b/internal/services/catalogreadservice/.dockerignore @@ -0,0 +1,2 @@ +.gitignore +Dockerfile diff --git a/internal/services/catalogreadservice/.gitignore b/internal/services/catalogreadservice/.gitignore new file mode 100644 index 00000000..7d501265 --- /dev/null +++ b/internal/services/catalogreadservice/.gitignore @@ -0,0 +1,46 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go tests -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +# Tools +.idea +.idea/* + +.terraform +*.tfstate +*.tfstate.*backup + +.go/ +.go-cache/ + +.vscode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix diff --git a/internal/services/catalogreadservice/.golangci.yml b/internal/services/catalogreadservice/.golangci.yml new file mode 100644 index 00000000..e311c166 --- /dev/null +++ b/internal/services/catalogreadservice/.golangci.yml @@ -0,0 +1,115 @@ +linters-settings: + dupl: + threshold: 120 + errorlint: + errorf: true + errcheck: + check-type-assertions: true + check-blank: true + exhaustive: + check-generated: false + default-signifies-exhaustive: false + funlen: + lines: 120 + statements: 50 + gocognit: + min-complexity: 40 + gocyclo: + min-complexity: 15 + goconst: + min-len: 2 + min-occurrences: 2 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - dupImport + - unnecessaryBlock + gofumpt: + extra-rules: true + gomnd: + settings: + mnd: + checks: + - argument + - case + - condition + - operation + - return + govet: + check-shadowing: true + misspell: + locale: US + nestif: + min-complexity: 4 + nolintlint: + require-explanation: true + require-specific: true + +linters: + disable-all: true + enable: + - asciicheck + - bodyclose + - cyclop + - dogsled + - dupl + - durationcheck + - errcheck + - errorlint + - exhaustive + - exportloopref + - forbidigo + - funlen + - gci + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - godox + - goerr113 + - gofmt + - gofumpt + - goimports + - gomnd + - gomodguard + - gosec + - gosimple + - govet + - ineffassign + - makezero + - misspell + - nakedret + - nestif + - nlreturn + - noctx + - nolintlint + - paralleltest + - predeclared + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - tparallel + - thelper + - unconvert + - unparam + - unused + - wsl + - whitespace + +run: + skip-dirs: + - docs + +issues: + # Show only new issues created after git revision: e016c66 + new-from-rev: e016c66 diff --git a/internal/services/catalogreadservice/Dockerfile b/internal/services/catalogreadservice/Dockerfile new file mode 100644 index 00000000..e69de29b diff --git a/internal/services/catalogreadservice/Makefile b/internal/services/catalogreadservice/Makefile new file mode 100644 index 00000000..ea7e219a --- /dev/null +++ b/internal/services/catalogreadservice/Makefile @@ -0,0 +1,58 @@ +GOPATH:=$(shell go env GOPATH) + +.PHONY: run +run: + go run ./cmd/main.go + +.PHONY: build +build: + go build ./cmd/main.go + +.PHONY: lint +lint: + revive -config revive-config.toml -formatter friendly ./... + staticcheck ./... + golangci-lint run ./... + +.PHONY: format +format: + golines -m 120 -w --ignore-generated . + gci write --skip-generated -s standard -s "prefix(github.com/mehdihadeli/go-food-delivery-microservices)" -s default -s blank -s dot --custom-order . + gofumpt -l -w . + +.PHONY: test +test: + go test -cover ./... + +.PHONY: update +update: + @go get -u + +.PHONY: tidy +tidy: + go mod tidy + +.PHONY: deps-reset +deps-reset: + git checkout -- go.mod + go mod tidy + +.PHONY: deps-upgrade +deps-upgrade: + go get -u -t -d -v ./... + go mod tidy + +.PHONY: deps-cleancache +deps-cleancache: + go clean -modcache + +# ============================================================================== +# Linters https://golangci-lint.run/usage/install/ +.PHONY: run-linter +run-linter: + @echo Starting linters + golangci-lint run ./... + +.PHONY: docker +docker: + @docker build -t go-catalogs-read:latest . diff --git a/internal/services/catalogreadservice/arch-go.yml b/internal/services/catalogreadservice/arch-go.yml new file mode 100644 index 00000000..74bf0f91 --- /dev/null +++ b/internal/services/catalogreadservice/arch-go.yml @@ -0,0 +1,5 @@ +#https://github.com/fdaines/arch-go +cyclesRules: + - package: "**.cmd" + shouldNotContainCycles: true + \ No newline at end of file diff --git a/internal/services/catalogreadservice/client.http b/internal/services/catalogreadservice/client.http new file mode 100644 index 00000000..e69de29b diff --git a/internal/services/catalogreadservice/cmd/app/main.go b/internal/services/catalogreadservice/cmd/app/main.go new file mode 100644 index 00000000..82298b40 --- /dev/null +++ b/internal/services/catalogreadservice/cmd/app/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "os" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/app" + + "github.com/pterm/pterm" + "github.com/pterm/pterm/putils" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "catalogs-read-microservices", + Short: "catalogs-read-microservices based on vertical slice architecture", + Long: `This is a command runner or cli for api architecture in golang.`, + TraverseChildren: true, + Run: func(cmd *cobra.Command, args []string) { + app.NewApp().Run() + }, +} + +// https://github.com/swaggo/swag#how-to-use-it-with-gin + +// @contact.name Mehdi Hadeli +// @contact.url https://github.com/mehdihadeli +// @title Catalogs Read-Service Api +// @version 1.0 +// @description Catalogs Read-Service Api. +func main() { + pterm.DefaultBigText.WithLetters( + putils.LettersFromStringWithStyle("Catalogs", pterm.FgLightGreen.ToStyle()), + putils.LettersFromStringWithStyle(" Read Service", pterm.FgLightMagenta.ToStyle())). + Render() + + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/internal/services/catalogreadservice/cmd/migration/.gitkeep b/internal/services/catalogreadservice/cmd/migration/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/internal/services/catalogreadservice/config/config.development.json b/internal/services/catalogreadservice/config/config.development.json new file mode 100644 index 00000000..6b988ae3 --- /dev/null +++ b/internal/services/catalogreadservice/config/config.development.json @@ -0,0 +1,112 @@ +{ + "appOptions": { + "serviceName": "catalogreadservice", + "deliveryType": "http" + }, + "grpcOptions": { + "name": "catalogreadservice", + "port": ":6004", + "host": "localhost", + "development": true + }, + "echoHttpOptions": { + "name": "catalogreadservice", + "port": ":7001", + "development": true, + "timeout": 30, + "basePath": "/api/v1", + "host": "http://localhost", + "productsPath": "products", + "debugHeaders": true, + "httpClientDebug": true, + "debugErrorsResponse": true, + "ignoreLogUrls": [ + "metrics" + ] + }, + "logOptions": { + "level": "debug", + "logType": 0, + "callerEnabled": false + }, + "rabbitmqOptions": { + "autoStart": true, + "reconnecting": true, + "rabbitmqHostOptions": { + "userName": "guest", + "password": "guest", + "hostName": "localhost", + "port": 5672, + "httpPort": 15672 + } + }, + "redisOptions": { + "host": "localhost", + "port": 6379, + "password": "", + "database": 0, + "poolSize": 300 + }, + "mongoDbOptions": { + "host": "localhost", + "port": 27017, + "user": "admin", + "password": "admin", + "database": "catalogs_service", + "useAuth": true + }, + "tracingOptions": { + "enable": true, + "serviceName": "catalogs-read-service", + "instrumentationName": "io.opentelemetry.traces.catalogs-read-service", + "id": 1, + "useStdout": false, + "alwaysOnSampler": true, + "jaegerExporterOptions": { + "otlpEndpoint": "localhost:4320", + "enabled": true + }, + "tempoExporterOptions": { + "otlpEndpoint": "localhost:4322", + "enabled": true + }, + "zipkinExporterOptions": { + "url": "http://localhost:9411/api/v2/spans" + }, + "otlpProviders": [ + { + "name": "uptrace", + "enabled": false, + "otlpEndpoint": "otlp.uptrace.dev:4317", + "otlpHeaders": { + "uptrace-dsn": "https://@uptrace.dev/" + } + }, + { + "name": "elastic-apm", + "enabled": false, + "otlpEndpoint": "host.docker.internal:4317", + "otlpHeaders": { + "Authorization": "Bearer ${ELASTIC_APM_SECRET_TOKEN}" + } + } + ] + }, + "metricsOptions": { + "metricsRoutePath": "/metrics", + "serviceName": "catalogs-read-service", + "instrumentationName": "io.opentelemetry.metrics.catalogs-write-service" + }, + "elasticOptions": { + "url": "http://localhost:9200", + "sniff": false, + "gzip": true, + "explain": true, + "fetchSource": true, + "version": true, + "pretty": true + }, + "elasticIndexes": { + "products": "products" + } +} diff --git a/internal/services/catalogreadservice/config/config.go b/internal/services/catalogreadservice/config/config.go new file mode 100644 index 00000000..70c18a83 --- /dev/null +++ b/internal/services/catalogreadservice/config/config.go @@ -0,0 +1,34 @@ +package config + +import ( + "strings" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" +) + +type Config struct { + AppOptions AppOptions `mapstructure:"appOptions" env:"AppOptions"` +} + +func NewConfig(env environment.Environment) (*Config, error) { + cfg, err := config.BindConfig[*Config](env) + if err != nil { + return nil, err + } + + return cfg, nil +} + +type AppOptions struct { + DeliveryType string `mapstructure:"deliveryType" env:"DeliveryType"` + ServiceName string `mapstructure:"serviceName" env:"serviceName"` +} + +func (cfg *AppOptions) GetMicroserviceNameUpper() string { + return strings.ToUpper(cfg.ServiceName) +} + +func (cfg *AppOptions) GetMicroserviceName() string { + return cfg.ServiceName +} diff --git a/internal/services/catalogreadservice/config/config.test.json b/internal/services/catalogreadservice/config/config.test.json new file mode 100644 index 00000000..5038d505 --- /dev/null +++ b/internal/services/catalogreadservice/config/config.test.json @@ -0,0 +1,106 @@ +{ + "appOptions": { + "serviceName": "catalogreadservice", + "deliveryType": "http" + }, + "grpcOptions": { + "name": "catalogsreadservice", + "port": ":3300", + "host": "localhost", + "development": true + }, + "echoHttpOptions": { + "name": "catalogreadservice", + "port": ":6000", + "development": true, + "timeout": 30, + "basePath": "/api/v1", + "host": "http://localhost", + "productsPath": "products", + "debugHeaders": true, + "httpClientDebug": true, + "debugErrorsResponse": true, + "ignoreLogUrls": ["metrics"] + }, + "logOptions": { + "level": "debug", + "logType": 0, + "callerEnabled": false + }, + "rabbitmqOptions": { + "autoStart": false, + "reconnecting": false, + "rabbitmqHostOptions": { + "userName": "guest", + "password": "guest", + "hostName": "localhost", + "port": 5672, + "httpPort": 15672 + } + }, + "redisOptions": { + "host": "localhost", + "port": 6379, + "password": "", + "database": 0, + "poolSize": 300 + }, + "mongoDbOptions": { + "host": "localhost", + "port": 27017, + "user": "admin", + "password": "admin", + "database": "catalogs_service", + "useAuth": true + }, + "tracingOptions": { + "enable": true, + "serviceName": "catalogs-read-service", + "instrumentationName": "io.opentelemetry.traces.catalogs-read-service", + "id": 1, + "useStdout": false, + "alwaysOnSampler": true, + "jaegerExporterOptions": { + "otlpEndpoint": "localhost:4320", + "enabled": true + }, + "zipkinExporterOptions": { + "url": "http://localhost:9411/api/v2/spans" + }, + "otlpProviders": [ + { + "name": "uptrace", + "enabled": false, + "otlpEndpoint": "otlp.uptrace.dev:4317", + "otlpHeaders": { + "uptrace-dsn": "https://@uptrace.dev/" + } + }, + { + "name": "elastic-apm", + "enabled": false, + "otlpEndpoint": "host.docker.internal:4317", + "otlpHeaders": { + "Authorization": "Bearer ${ELASTIC_APM_SECRET_TOKEN}" + } + } + ] + }, + "metricsOptions": { + "metricsRoutePath": "/metrics", + "serviceName": "catalogs-read-service", + "instrumentationName": "io.opentelemetry.metrics.catalogs-write-service" + }, + "elasticOptions": { + "url": "http://localhost:9200", + "sniff": false, + "gzip": true, + "explain": true, + "fetchSource": true, + "version": true, + "pretty": true + }, + "elasticIndexes": { + "products": "products" + } +} diff --git a/internal/services/catalogreadservice/config/confix_fx.go b/internal/services/catalogreadservice/config/confix_fx.go new file mode 100644 index 00000000..1ff806bc --- /dev/null +++ b/internal/services/catalogreadservice/config/confix_fx.go @@ -0,0 +1,17 @@ +package config + +import ( + "go.uber.org/fx" +) + +// https://uber-go.github.io/fx/modules.html + +var Module = fx.Module("app"+ + "configfx", + // - order is not important in provide + // - provide can have parameter and will resolve if registered + // - execute its func only if it requested + fx.Provide( + NewConfig, + ), +) diff --git a/internal/services/catalogreadservice/docs/docs.go b/internal/services/catalogreadservice/docs/docs.go new file mode 100644 index 00000000..e17b0bf4 --- /dev/null +++ b/internal/services/catalogreadservice/docs/docs.go @@ -0,0 +1,252 @@ +// Code generated by swaggo/swag. DO NOT EDIT. + +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/products": { + "get": { + "description": "Get all products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get all product", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/search": { + "get": { + "description": "Search products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Search products", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "description": "Get product by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get product", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto" + } + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "productId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto": { + "type": "object", + "properties": { + "product": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListQuery": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FilterModel" + } + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Catalogs Read-Service Api", + Description: "Catalogs Read-Service Api.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/internal/services/catalogreadservice/docs/swagger.json b/internal/services/catalogreadservice/docs/swagger.json new file mode 100644 index 00000000..f6ed6251 --- /dev/null +++ b/internal/services/catalogreadservice/docs/swagger.json @@ -0,0 +1,225 @@ +{ + "swagger": "2.0", + "info": { + "description": "Catalogs Read-Service Api.", + "title": "Catalogs Read-Service Api", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "1.0" + }, + "paths": { + "/api/v1/products": { + "get": { + "description": "Get all products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get all product", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/search": { + "get": { + "description": "Search products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Search products", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "description": "Get product by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get product", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto" + } + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "productId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto": { + "type": "object", + "properties": { + "product": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListQuery": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FilterModel" + } + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/internal/services/catalogreadservice/docs/swagger.yaml b/internal/services/catalogreadservice/docs/swagger.yaml new file mode 100644 index 00000000..0007d662 --- /dev/null +++ b/internal/services/catalogreadservice/docs/swagger.yaml @@ -0,0 +1,144 @@ +definitions: + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto: + properties: + createdAt: + type: string + description: + type: string + id: + type: string + name: + type: string + price: + type: number + productId: + type: string + updatedAt: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto + : properties: + product: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto + : properties: + products: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto + : properties: + products: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto' + type: object + utils.FilterModel: + properties: + comparison: + type: string + field: + type: string + value: + type: string + type: object + utils.ListQuery: + properties: + filters: + items: + $ref: '#/definitions/utils.FilterModel' + type: array + orderBy: + type: string + page: + type: integer + size: + type: integer + type: object + ? utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto_ProductDto + : properties: + items: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_dto.ProductDto' + type: array + page: + type: integer + size: + type: integer + totalItems: + type: integer + totalPage: + type: integer + type: object +info: + contact: + name: Mehdi Hadeli + url: https://github.com/mehdihadeli + description: Catalogs Read-Service Api. + title: Catalogs Read-Service Api + version: "1.0" +paths: + /api/v1/products: + get: + consumes: + - application/json + description: Get all products + parameters: + - in: query + name: orderBy + type: string + - in: query + name: page + type: integer + - in: query + name: size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto' + summary: Get all product + tags: + - Products + /api/v1/products/{id}: + get: + consumes: + - application/json + description: Get product by id + parameters: + - description: Product ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_get_product_by_id_v1_dtos.GetProductByIdResponseDto' + summary: Get product + tags: + - Products + /api/v1/products/search: + get: + consumes: + - application/json + description: Search products + parameters: + - in: query + name: search + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogreadservice_internal_products_features_searching_products_v1_dtos.SearchProductsResponseDto' + summary: Search products + tags: + - Products +swagger: "2.0" diff --git a/internal/services/catalogreadservice/go.mod b/internal/services/catalogreadservice/go.mod new file mode 100644 index 00000000..784aa6b0 --- /dev/null +++ b/internal/services/catalogreadservice/go.mod @@ -0,0 +1,237 @@ +module github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice + +go 1.22 + +// https://go.dev/doc/tutorial/call-module-code +replace github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg => ../../pkg/ + +require ( + emperror.dev/errors v0.8.1 + github.com/brianvoe/gofakeit/v6 v6.25.0 + github.com/gavv/httpexpect/v2 v2.3.1 + github.com/go-ozzo/ozzo-validation v3.6.0+incompatible + github.com/go-playground/validator v9.31.0+incompatible + github.com/labstack/echo/v4 v4.11.1 + github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg v0.0.0-20230831075934-be8df319f588 + github.com/mehdihadeli/go-mediatr v1.3.0 + github.com/michaelklishin/rabbit-hole v1.5.0 + github.com/pterm/pterm v0.12.69 + github.com/redis/go-redis/v9 v9.2.1 + github.com/satori/go.uuid v1.2.0 + github.com/smartystreets/goconvey v1.8.1 + github.com/spf13/cobra v1.7.0 + github.com/stretchr/testify v1.8.4 + github.com/swaggo/echo-swagger v1.4.1 + github.com/swaggo/swag v1.16.2 + go.mongodb.org/mongo-driver v1.12.1 + go.opentelemetry.io/otel v1.19.0 + go.opentelemetry.io/otel/metric v1.19.0 + go.opentelemetry.io/otel/trace v1.19.0 + go.uber.org/fx v1.20.0 + gorm.io/gorm v1.25.5 +) + +require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.1.0 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/hcsshim v0.11.1 // indirect + github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect + github.com/ahmetb/go-linq/v3 v3.2.0 // indirect + github.com/ajg/form v1.5.1 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/avast/retry-go v3.0.0+incompatible // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/caarlos0/env/v8 v8.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/containerd/containerd v1.7.6 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v24.0.6+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/doug-martin/goqu/v9 v9.18.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fatih/structs v1.0.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/glebarez/sqlite v1.10.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-reflect v1.2.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang-migrate/migrate/v4 v4.16.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/gookit/color v1.5.4 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/imkira/go-interpol v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.1 // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jackc/puddle v1.3.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/kamva/mgm/v3 v3.5.0 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect + github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mcuadros/go-defaults v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/nolleh/caption_json_formatter v0.2.2 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/opencontainers/runc v1.1.9 // indirect + github.com/openzipkin/zipkin-go v0.4.2 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rabbitmq/amqp091-go v1.8.1 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect + github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/shirou/gopsutil/v3 v3.23.9 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/smarty/assertions v1.15.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.16.0 // indirect + github.com/stretchr/objx v0.5.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/swaggo/files/v2 v2.0.0 // indirect + github.com/testcontainers/testcontainers-go v0.25.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/ulule/limiter/v3 v3.11.2 // indirect + github.com/uptrace/bun v1.1.16 // indirect + github.com/uptrace/bun/driver/pgdriver v1.1.16 // indirect + github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3 // indirect + github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3 // indirect + github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.47.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.0 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.45.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.19.0 // indirect + go.opentelemetry.io/otel/sdk v1.19.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.19.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/grpc v1.58.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.2 // indirect + gorm.io/plugin/opentelemetry v0.1.4 // indirect + mellium.im/sasl v0.3.1 // indirect + modernc.org/libc v1.24.1 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.6.0 // indirect + modernc.org/sqlite v1.25.0 // indirect + moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e // indirect +) diff --git a/internal/services/catalogreadservice/go.sum b/internal/services/catalogreadservice/go.sum new file mode 100644 index 00000000..58dd0c77 --- /dev/null +++ b/internal/services/catalogreadservice/go.sum @@ -0,0 +1,1247 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= +cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0= +emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.1 h1:hJ3s7GbWlGK4YVV92sO88BQSyF4ZLVy7/awqOlPxFbA= +github.com/Microsoft/hcsshim v0.11.1/go.mod h1:nFJmaO4Zr5Y7eADdFOpYswDDlNVbvcIJJNJLECr5JQg= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= +github.com/ahmetb/go-linq/v3 v3.2.0 h1:BEuMfp+b59io8g5wYzNoFe9pWPalRklhlhbiU3hYZDE= +github.com/ahmetb/go-linq/v3 v3.2.0/go.mod h1:haQ3JfOeWK8HpVxMtHHEMPVgBKiYyQ+f1/kLZh/cj9U= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +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/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/brianvoe/gofakeit/v6 v6.25.0 h1:ZpFjktOpLZUeF8q223o0rUuXtA+m5qW5srjvVi+JkXk= +github.com/brianvoe/gofakeit/v6 v6.25.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= +github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.7.6 h1:oNAVsnhPoy4BTPQivLgTzI9Oleml9l/+eYIDYXRCYo8= +github.com/containerd/containerd v1.7.6/go.mod h1:SY6lrkkuJT40BVNO37tlYTSnKJnP5AXBc0fhx0q+TJ4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +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/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= +github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY= +github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/fasthttp/websocket v1.4.3-rc.6 h1:omHqsl8j+KXpmzRjF8bmzOSYJ8GnS0E3efi1wYT+niY= +github.com/fasthttp/websocket v1.4.3-rc.6/go.mod h1:43W9OM2T8FeXpCWMsBd9Cb7nE2CACNqNvCqQCoty/Lc= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/httpexpect/v2 v2.3.1 h1:sGLlKMn8AuHS9ztK9Sb7AJ7OxIL8v2PcLdyxfKt1Fo4= +github.com/gavv/httpexpect/v2 v2.3.1/go.mod h1:yOE8m/aqFYQDNrgprMeXgq4YynfN9h1NgcE1+1suV64= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= +github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/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/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms= +github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= +github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= +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/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.0.0 h1:HrmLyvOLJyjR0YofMw8QGdCIuYOs4TJUBDNU5sJC09E= +github.com/imkira/go-interpol v1.0.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= +github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kamva/mgm/v3 v3.5.0 h1:/2mNshpqwAC9spdzJZ0VR/UZ/SY/PsNTrMjT111KQjM= +github.com/kamva/mgm/v3 v3.5.0/go.mod h1:F4J1hZnXQMkqL3DZgR7Z7BOuiTqQG/JTic3YzliG4jk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= +github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc= +github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY= +github.com/mehdihadeli/go-mediatr v1.3.0 h1:hrb5Scp/nsiR3Y62mjZ0Tc5UX/dRJl4nDFkINBEIESA= +github.com/mehdihadeli/go-mediatr v1.3.0/go.mod h1:lsG+hyH+pEOhmZiZl0KPO72BcZiEReF03CBk4GVJB0k= +github.com/michaelklishin/rabbit-hole v1.5.0 h1:Bex27BiFDsijCM9D0ezSHqyy0kehpYHuNKaPqq/a4RM= +github.com/michaelklishin/rabbit-hole v1.5.0/go.mod h1:vvI1uOitYZi0O5HEGXhaWC1XT80Gy+HvFheJ+5Krlhk= +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/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nolleh/caption_json_formatter v0.2.2 h1:EKsOr/fCllNQF2ZoajfbSDlV73BNV1bDu1aTTSRrlN0= +github.com/nolleh/caption_json_formatter v0.2.2/go.mod h1:5FYofZA8NAej/eFxa12FvyQKosU1LfyKizZPlY0JojU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA= +github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM= +github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= +github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.69 h1:fBCKnB8dSLAl8FlYRQAWYGp2WTI/Xm/tKJ21Hyo9USw= +github.com/pterm/pterm v0.12.69/go.mod h1:wl06ko9MHnqxz4oDV++IORDpjCzw6+mfrvf0MPj6fdk= +github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= +github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873 h1:N3Af8f13ooDKcIhsmFT7Z05CStZWu4C7Md0uDEy4q6o= +github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= +github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= +github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= +github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= +github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= +github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= +github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/testcontainers/testcontainers-go v0.25.0 h1:erH6cQjsaJrH+rJDU9qIf89KFdhK0Bft0aEZHlYC3Vs= +github.com/testcontainers/testcontainers-go v0.25.0/go.mod h1:4sC9SiJyzD1XFi59q8umTQYWxnkweEc5OjVtTUlJzqQ= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA= +github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI= +github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A= +github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8= +github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7QiNOCPLqPFJjK4= +github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= +github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3 h1:m5eNyOhch/7tyK6aN6eRRpNoD1vM8PNh64dA05X22Js= +github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3/go.mod h1:APPUXm9BbpH7NFkfpbw04raZSitzl19/3NOCu0rbI4E= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3 h1:LyGS9cIZV0YVhE81zwfMhIE2l2flcj3wn5IoK4VkbWA= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3/go.mod h1:RvCYhPchLhvQ9l9C9goblbgO7BaKt597kBMf5mgKyo0= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3 h1:2na5W81H38Z4qXCQCuzlcdSMiTWgPJ6XeZIArq6VIJE= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3/go.mod h1:9IVEh9mPv3NwFf99dVLX15FqVgdpZJ8RMDo/Cr0vK74= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= +github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= +github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vmihailenco/msgpack/v5 v5.4.0 h1:hRM0digJwyR6vll33NNAwCFguy5JuBD6jxDmQP3l608= +github.com/vmihailenco/msgpack/v5 v5.4.0/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= +go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= +go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0 h1:bldpPC7XAv7f7LKTwNfRkNdzRhjtXaWybZFFa16dAb8= +go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0/go.mod h1:xhkNpJG3D+kmuaciNTco7cdK27Fb77J9Iqcq5CMe4Y8= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= +go.opentelemetry.io/contrib/instrumentation/host v0.45.0 h1:1uzNKJDqZ6y6F5J6aKWgJjRREpKiGhBvKHlWon/bqB4= +go.opentelemetry.io/contrib/instrumentation/host v0.45.0/go.mod h1:vlqPvzDsmB4+jlERxBRXsdLCD6Q0LoBzxHqNXp3qvG4= +go.opentelemetry.io/contrib/propagators/ot v1.20.0 h1:duH7mgL6VGQH7e7QEAVOFkCQXWpCb4PjTtrhdrYrJRQ= +go.opentelemetry.io/contrib/propagators/ot v1.20.0/go.mod h1:gijQzxOq0JLj9lyZhTvqjDddGV/zaNagpPIn+2r8CEI= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 h1:ZtfnDL+tUrs1F0Pzfwbg2d59Gru9NCH3bgSHBM6LDwU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 h1:NmnYCiR0qNufkldjVvyQfZTHSdzeHoZ41zggMsdMcLM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0/go.mod h1:UVAO61+umUsHLtYb8KXXRoHtxUkdOPkYidzW3gipRLQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +go.opentelemetry.io/otel/exporters/prometheus v0.42.0 h1:jwV9iQdvp38fxXi8ZC+lNpxjK16MRcZlpDYvbuO1FiA= +go.opentelemetry.io/otel/exporters/prometheus v0.42.0/go.mod h1:f3bYiqNqhoPxkvI2LrXqQVC546K7BuRDL/kKuxkujhA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0 h1:4jJuoeOo9W6hZnz+r046fyoH5kykZPRvKfUXJVfMpB0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0/go.mod h1:/MtYTE1SfC2QIcE0bDot6fIX+h+WvXjgTqgn9P0LNPE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0 h1:EGY0h5mGliP9o/nIkVuLI0vRiQqmsYOcbwCuotksO1o= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0/go.mod h1:JQgTGJP11yi3o4GHzIWYodhPisxANdqxF1eHwDSnJrI= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= +go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= +go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c= +gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/plugin/opentelemetry v0.1.4 h1:7p0ocWELjSSRI7NCKPW2mVe6h43YPini99sNJcbsTuc= +gorm.io/plugin/opentelemetry v0.1.4/go.mod h1:tndJHOdvPT0pyGhOb8E2209eXJCUxhC5UpKw7bGVWeI= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= +mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= +modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= +modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= +modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= +modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e h1:C7q+e9M5nggAvWfVg9Nl66kebKeuJlP3FD58V4RR5wo= +moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e/go.mod h1:nejbQVfXh96n9dSF6cH3Jsk/QI1Z2oEL7sSI2ifXFNA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/services/catalogreadservice/internal/products/configurations/mappings/mapping_profile.go b/internal/services/catalogreadservice/internal/products/configurations/mappings/mapping_profile.go new file mode 100644 index 00000000..aa17fc87 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/configurations/mappings/mapping_profile.go @@ -0,0 +1,21 @@ +package mappings + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/dto" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" +) + +func ConfigureProductsMappings() error { + err := mapper.CreateMap[*models.Product, *dto.ProductDto]() + if err != nil { + return err + } + + err = mapper.CreateMap[*models.Product, *models.Product]() + if err != nil { + return err + } + + return nil +} diff --git a/internal/services/catalogreadservice/internal/products/configurations/mediator/mediator_configurations.go b/internal/services/catalogreadservice/internal/products/configurations/mediator/mediator_configurations.go new file mode 100644 index 00000000..f4eb2a9f --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/configurations/mediator/mediator_configurations.go @@ -0,0 +1,95 @@ +package mediator + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + v1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/creating_product/v1" + createProductDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/creating_product/v1/dtos" + deleteProductCommandV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/commands" + getProductByIdDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos" + getProductByIdQueryV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/queries" + getProductsDtoV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos" + getProductsQueryV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/getting_products/v1/queries" + searchProductsDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/searching_products/v1/dtos" + searchProductsQueryV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/searching_products/v1/queries" + updateProductCommandV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/updating_products/v1/commands" + + "emperror.dev/errors" + "github.com/mehdihadeli/go-mediatr" +) + +func ConfigProductsMediator( + logger logger.Logger, + mongoProductRepository data.ProductRepository, + cacheProductRepository data.ProductCacheRepository, + tracer tracing.AppTracer, +) error { + err := mediatr.RegisterRequestHandler[*v1.CreateProduct, *createProductDtosV1.CreateProductResponseDto]( + v1.NewCreateProductHandler( + logger, + mongoProductRepository, + cacheProductRepository, + tracer, + ), + ) + if err != nil { + return errors.WrapIf(err, "error while registering handlers in the mediator") + } + + err = mediatr.RegisterRequestHandler[*deleteProductCommandV1.DeleteProduct, *mediatr.Unit]( + deleteProductCommandV1.NewDeleteProductHandler( + logger, + mongoProductRepository, + cacheProductRepository, + tracer, + ), + ) + if err != nil { + return errors.WrapIf(err, "error while registering handlers in the mediator") + } + + err = mediatr.RegisterRequestHandler[*updateProductCommandV1.UpdateProduct, *mediatr.Unit]( + updateProductCommandV1.NewUpdateProductHandler( + logger, + mongoProductRepository, + cacheProductRepository, + tracer, + ), + ) + if err != nil { + return errors.WrapIf(err, "error while registering handlers in the mediator") + } + + err = mediatr.RegisterRequestHandler[*getProductsQueryV1.GetProducts, *getProductsDtoV1.GetProductsResponseDto]( + getProductsQueryV1.NewGetProductsHandler(logger, mongoProductRepository, tracer), + ) + if err != nil { + return errors.WrapIf(err, "error while registering handlers in the mediator") + } + + err = mediatr.RegisterRequestHandler[*searchProductsQueryV1.SearchProducts, *searchProductsDtosV1.SearchProductsResponseDto]( + searchProductsQueryV1.NewSearchProductsHandler( + logger, + mongoProductRepository, + tracer, + ), + ) + if err != nil { + return errors.WrapIf(err, "error while registering handlers in the mediator") + } + + err = mediatr.RegisterRequestHandler[*getProductByIdQueryV1.GetProductById, *getProductByIdDtosV1.GetProductByIdResponseDto]( + getProductByIdQueryV1.NewGetProductByIdHandler( + logger, + mongoProductRepository, + cacheProductRepository, + tracer, + ), + ) + if err != nil { + return errors.WrapIf(err, "error while registering handlers in the mediator") + } + + return nil +} diff --git a/internal/services/catalogreadservice/internal/products/configurations/products_module_configurator.go b/internal/services/catalogreadservice/internal/products/configurations/products_module_configurator.go new file mode 100644 index 00000000..d742ffee --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/configurations/products_module_configurator.go @@ -0,0 +1,57 @@ +package configurations + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + logger2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/configurations/mappings" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/configurations/mediator" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" +) + +type ProductsModuleConfigurator struct { + contracts.Application +} + +func NewProductsModuleConfigurator( + app contracts.Application, +) *ProductsModuleConfigurator { + return &ProductsModuleConfigurator{ + Application: app, + } +} + +func (c *ProductsModuleConfigurator) ConfigureProductsModule() { + c.ResolveFunc( + func(logger logger2.Logger, mongoRepository data.ProductRepository, cacheRepository data.ProductCacheRepository, tracer tracing.AppTracer) error { + // config Products Mediators + err := mediator.ConfigProductsMediator( + logger, + mongoRepository, + cacheRepository, + tracer, + ) + if err != nil { + return err + } + + // config Products Mappings + err = mappings.ConfigureProductsMappings() + if err != nil { + return err + } + return nil + }, + ) +} + +func (c *ProductsModuleConfigurator) MapProductsEndpoints() { + // config Products Http Endpoints + c.ResolveFuncWithParamTag(func(endpoints []route.Endpoint) { + for _, endpoint := range endpoints { + endpoint.MapEndpoint() + } + }, `group:"product-routes"`, + ) +} diff --git a/internal/services/catalogreadservice/internal/products/configurations/rabbitmq/rabbitmq_configuration.go b/internal/services/catalogreadservice/internal/products/configurations/rabbitmq/rabbitmq_configuration.go new file mode 100644 index 00000000..ab66dca3 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/configurations/rabbitmq/rabbitmq_configuration.go @@ -0,0 +1,81 @@ +package rabbitmq + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + rabbitmqConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/consumer/configurations" + createProductExternalEventV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/creating_product/v1/events/integrationevents/externalevents" + deleteProductExternalEventV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/events/integration_events/external_events" + updateProductExternalEventsV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/updating_products/v1/events/integration_events/external_events" + + "github.com/go-playground/validator" +) + +func ConfigProductsRabbitMQ( + builder rabbitmqConfigurations.RabbitMQConfigurationBuilder, + logger logger.Logger, + validator *validator.Validate, + tracer tracing.AppTracer, +) { + // add custom message type mappings + // utils.RegisterCustomMessageTypesToRegistrty(map[string]types.IMessage{"productCreatedV1": &creatingProductIntegration.ProductCreatedV1{}}) + + builder. + AddConsumer( + createProductExternalEventV1.ProductCreatedV1{}, + func(builder configurations.RabbitMQConsumerConfigurationBuilder) { + builder.WithHandlers( + func(handlersBuilder consumer.ConsumerHandlerConfigurationBuilder) { + handlersBuilder.AddHandler( + createProductExternalEventV1.NewProductCreatedConsumer( + logger, + validator, + tracer, + ), + ) + }, + ) + }). + AddConsumer( + deleteProductExternalEventV1.ProductDeletedV1{}, + func(builder configurations.RabbitMQConsumerConfigurationBuilder) { + builder.WithHandlers( + func(handlersBuilder consumer.ConsumerHandlerConfigurationBuilder) { + handlersBuilder.AddHandler( + deleteProductExternalEventV1.NewProductDeletedConsumer( + logger, + validator, + tracer, + ), + ) + deleteProductExternalEventV1.NewProductDeletedConsumer( + logger, + validator, + tracer, + ) + }, + ) + }). + AddConsumer( + updateProductExternalEventsV1.ProductUpdatedV1{}, + func(builder configurations.RabbitMQConsumerConfigurationBuilder) { + builder.WithHandlers( + func(handlersBuilder consumer.ConsumerHandlerConfigurationBuilder) { + handlersBuilder.AddHandler( + updateProductExternalEventsV1.NewProductUpdatedConsumer( + logger, + validator, + tracer, + ), + ) + updateProductExternalEventsV1.NewProductUpdatedConsumer( + logger, + validator, + tracer, + ) + }, + ) + }) +} diff --git a/internal/services/catalogreadservice/internal/products/consts/consts.go b/internal/services/catalogreadservice/internal/products/consts/consts.go new file mode 100644 index 00000000..b12df564 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/consts/consts.go @@ -0,0 +1,6 @@ +package consts + +const ( + ProductIdIndex = "productId" + ProductId = "productId" +) diff --git a/internal/services/catalogreadservice/internal/products/contracts/data/product_cache_repository.go b/internal/services/catalogreadservice/internal/products/contracts/data/product_cache_repository.go new file mode 100644 index 00000000..53201f71 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/contracts/data/product_cache_repository.go @@ -0,0 +1,14 @@ +package data + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" +) + +type ProductCacheRepository interface { + PutProduct(ctx context.Context, key string, product *models.Product) error + GetProductById(ctx context.Context, key string) (*models.Product, error) + DeleteProduct(ctx context.Context, key string) error + DeleteAllProducts(ctx context.Context) error +} diff --git a/internal/services/catalogreadservice/internal/products/contracts/data/product_repository.go b/internal/services/catalogreadservice/internal/products/contracts/data/product_repository.go new file mode 100644 index 00000000..98d9caf2 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/contracts/data/product_repository.go @@ -0,0 +1,25 @@ +package data + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" +) + +type ProductRepository interface { + GetAllProducts( + ctx context.Context, + listQuery *utils.ListQuery, + ) (*utils.ListResult[*models.Product], error) + SearchProducts( + ctx context.Context, + searchText string, + listQuery *utils.ListQuery, + ) (*utils.ListResult[*models.Product], error) + GetProductById(ctx context.Context, uuid string) (*models.Product, error) + GetProductByProductId(ctx context.Context, uuid string) (*models.Product, error) + CreateProduct(ctx context.Context, product *models.Product) (*models.Product, error) + UpdateProduct(ctx context.Context, product *models.Product) (*models.Product, error) + DeleteProductByID(ctx context.Context, uuid string) error +} diff --git a/internal/services/catalogreadservice/internal/products/contracts/params/product_route_params.go b/internal/services/catalogreadservice/internal/products/contracts/params/product_route_params.go new file mode 100644 index 00000000..d097055b --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/contracts/params/product_route_params.go @@ -0,0 +1,19 @@ +package params + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/contracts" + + "github.com/go-playground/validator" + "github.com/labstack/echo/v4" + "go.uber.org/fx" +) + +type ProductRouteParams struct { + fx.In + + CatalogsMetrics *contracts.CatalogsMetrics + Logger logger.Logger + ProductsGroup *echo.Group `name:"product-echo-group"` + Validator *validator.Validate +} diff --git a/internal/services/catalogreadservice/internal/products/data/repositories/mongo_product_repository.go b/internal/services/catalogreadservice/internal/products/data/repositories/mongo_product_repository.go new file mode 100644 index 00000000..a8bad40c --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/data/repositories/mongo_product_repository.go @@ -0,0 +1,295 @@ +package repositories + +// https://github.com/Kamva/mgm +// https://github.com/mongodb/mongo-go-driver +// https://blog.logrocket.com/how-to-use-mongodb-with-go/ +// https://www.mongodb.com/docs/drivers/go/current/quick-reference/ +// https://www.mongodb.com/docs/drivers/go/current/fundamentals/bson/ +// https://www.mongodb.com/docs + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb/repository" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + utils2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + data2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" + + "emperror.dev/errors" + uuid2 "github.com/satori/go.uuid" + "go.mongodb.org/mongo-driver/mongo" + attribute2 "go.opentelemetry.io/otel/attribute" +) + +const ( + productCollection = "products" +) + +type mongoProductRepository struct { + log logger.Logger + mongoGenericRepository data.GenericRepository[*models.Product] + tracer tracing.AppTracer +} + +func NewMongoProductRepository( + log logger.Logger, + db *mongo.Client, + mongoOptions *mongodb.MongoDbOptions, + tracer tracing.AppTracer, +) data2.ProductRepository { + mongoRepo := repository.NewGenericMongoRepository[*models.Product]( + db, + mongoOptions.Database, + productCollection, + ) + return &mongoProductRepository{ + log: log, + mongoGenericRepository: mongoRepo, + tracer: tracer, + } +} + +func (p *mongoProductRepository) GetAllProducts( + ctx context.Context, + listQuery *utils.ListQuery, +) (*utils.ListResult[*models.Product], error) { + ctx, span := p.tracer.Start(ctx, "mongoProductRepository.GetAllProducts") + defer span.End() + + // https://www.mongodb.com/docs/drivers/go/current/fundamentals/crud/read-operations/query-document/ + result, err := p.mongoGenericRepository.GetAll(ctx, listQuery) + if err != nil { + return nil, utils2.TraceErrStatusFromSpan( + span, + errors.WrapIf( + err, + "error in the paginate", + ), + ) + } + + p.log.Infow( + "products loaded", + logger.Fields{"ProductsResult": result}, + ) + + span.SetAttributes(attribute.Object("ProductsResult", result)) + + return result, nil +} + +func (p *mongoProductRepository) SearchProducts( + ctx context.Context, + searchText string, + listQuery *utils.ListQuery, +) (*utils.ListResult[*models.Product], error) { + ctx, span := p.tracer.Start(ctx, "mongoProductRepository.SearchProducts") + span.SetAttributes(attribute2.String("SearchText", searchText)) + defer span.End() + + result, err := p.mongoGenericRepository.Search(ctx, searchText, listQuery) + if err != nil { + return nil, utils2.TraceErrStatusFromSpan( + span, + errors.WrapIf( + err, + "error in the paginate", + ), + ) + } + + p.log.Infow( + fmt.Sprintf( + "products loaded for search term '%s'", + searchText, + ), + logger.Fields{"ProductsResult": result}, + ) + + span.SetAttributes(attribute.Object("ProductsResult", result)) + + return result, nil +} + +func (p *mongoProductRepository) GetProductById( + ctx context.Context, + uuid string, +) (*models.Product, error) { + ctx, span := p.tracer.Start(ctx, "mongoProductRepository.GetProductById") + span.SetAttributes(attribute2.String("Id", uuid)) + defer span.End() + + id, err := uuid2.FromString(uuid) + if err != nil { + return nil, err + } + + product, err := p.mongoGenericRepository.GetById(ctx, id) + if err != nil { + return nil, utils2.TraceStatusFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "can't find the product with id %s into the database.", + uuid, + ), + ), + ) + } + + span.SetAttributes(attribute.Object("Product", product)) + + p.log.Infow( + fmt.Sprintf("product with id %s laoded", uuid), + logger.Fields{"Product": product, "Id": uuid}, + ) + + return product, nil +} + +func (p *mongoProductRepository) GetProductByProductId( + ctx context.Context, + uuid string, +) (*models.Product, error) { + productId := uuid + ctx, span := p.tracer.Start( + ctx, + "mongoProductRepository.GetProductByProductId", + ) + span.SetAttributes(attribute2.String("ProductId", productId)) + defer span.End() + + product, err := p.mongoGenericRepository.FirstOrDefault( + ctx, + map[string]interface{}{"productId": uuid}, + ) + if err != nil { + return nil, utils2.TraceStatusFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "can't find the product with productId %s into the database.", + uuid, + ), + ), + ) + } + + span.SetAttributes(attribute.Object("Product", product)) + + p.log.Infow( + fmt.Sprintf( + "product with productId %s laoded", + productId, + ), + logger.Fields{"Product": product, "ProductId": uuid}, + ) + + return product, nil +} + +func (p *mongoProductRepository) CreateProduct( + ctx context.Context, + product *models.Product, +) (*models.Product, error) { + ctx, span := p.tracer.Start(ctx, "mongoProductRepository.CreateProduct") + defer span.End() + + err := p.mongoGenericRepository.Add(ctx, product) + if err != nil { + return nil, utils2.TraceErrStatusFromSpan( + span, + errors.WrapIf( + err, + "error in the inserting product into the database.", + ), + ) + } + + span.SetAttributes(attribute.Object("Product", product)) + + p.log.Infow( + fmt.Sprintf( + "product with id '%s' created", + product.ProductId, + ), + logger.Fields{"Product": product, "Id": product.ProductId}, + ) + + return product, nil +} + +func (p *mongoProductRepository) UpdateProduct( + ctx context.Context, + updateProduct *models.Product, +) (*models.Product, error) { + ctx, span := p.tracer.Start(ctx, "mongoProductRepository.UpdateProduct") + defer span.End() + + err := p.mongoGenericRepository.Update(ctx, updateProduct) + // https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndUpdate/ + if err != nil { + return nil, utils2.TraceErrStatusFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "error in updating product with id %s into the database.", + updateProduct.ProductId, + ), + ), + ) + } + + span.SetAttributes(attribute.Object("Product", updateProduct)) + p.log.Infow( + fmt.Sprintf( + "product with id '%s' updated", + updateProduct.ProductId, + ), + logger.Fields{"Product": updateProduct, "Id": updateProduct.ProductId}, + ) + + return updateProduct, nil +} + +func (p *mongoProductRepository) DeleteProductByID( + ctx context.Context, + uuid string, +) error { + ctx, span := p.tracer.Start(ctx, "mongoProductRepository.DeleteProductByID") + span.SetAttributes(attribute2.String("Id", uuid)) + defer span.End() + + id, err := uuid2.FromString(uuid) + if err != nil { + return err + } + + err = p.mongoGenericRepository.Delete(ctx, id) + if err != nil { + return utils2.TraceErrStatusFromSpan( + span, + errors.WrapIf(err, fmt.Sprintf( + "error in deleting product with id %s from the database.", + uuid, + )), + ) + } + + p.log.Infow( + fmt.Sprintf("product with id %s deleted", uuid), + logger.Fields{"Product": uuid}, + ) + + return nil +} diff --git a/internal/services/catalogreadservice/internal/products/data/repositories/redis_product_repository.go b/internal/services/catalogreadservice/internal/products/data/repositories/redis_product_repository.go new file mode 100644 index 00000000..5347ad9e --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/data/repositories/redis_product_repository.go @@ -0,0 +1,214 @@ +package repositories + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" + + "emperror.dev/errors" + "github.com/redis/go-redis/v9" + attribute2 "go.opentelemetry.io/otel/attribute" +) + +const ( + redisProductPrefixKey = "product_read_service" +) + +type redisProductRepository struct { + log logger.Logger + redisClient redis.UniversalClient + tracer tracing.AppTracer +} + +func NewRedisProductRepository( + log logger.Logger, + redisClient redis.UniversalClient, + tracer tracing.AppTracer, +) data.ProductCacheRepository { + return &redisProductRepository{ + log: log, + redisClient: redisClient, + tracer: tracer, + } +} + +func (r *redisProductRepository) PutProduct( + ctx context.Context, + key string, + product *models.Product, +) error { + ctx, span := r.tracer.Start(ctx, "redisRepository.PutProduct") + span.SetAttributes( + attribute2.String("PrefixKey", r.getRedisProductPrefixKey()), + ) + span.SetAttributes(attribute2.String("Key", key)) + defer span.End() + + productBytes, err := json.Marshal(product) + if err != nil { + return utils.TraceErrStatusFromSpan( + span, + errors.WrapIf( + err, + "error marshalling product", + ), + ) + } + + if err := r.redisClient.HSetNX(ctx, r.getRedisProductPrefixKey(), key, productBytes).Err(); err != nil { + return utils.TraceErrStatusFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "error in updating product with key %s", + key, + ), + ), + ) + } + + span.SetAttributes(attribute.Object("Product", product)) + + r.log.Infow( + fmt.Sprintf( + "product with key '%s', prefix '%s' updated successfully", + key, + r.getRedisProductPrefixKey(), + ), + logger.Fields{ + "Product": product, + "Id": product.ProductId, + "Key": key, + "PrefixKey": r.getRedisProductPrefixKey(), + }, + ) + + return nil +} + +func (r *redisProductRepository) GetProductById( + ctx context.Context, + key string, +) (*models.Product, error) { + ctx, span := r.tracer.Start(ctx, "redisRepository.GetProductById") + span.SetAttributes( + attribute2.String("PrefixKey", r.getRedisProductPrefixKey()), + ) + span.SetAttributes(attribute2.String("Key", key)) + defer span.End() + + productBytes, err := r.redisClient.HGet(ctx, r.getRedisProductPrefixKey(), key). + Bytes() + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, nil + } + + return nil, utils.TraceErrStatusFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "error in getting product with Key %s from database", + key, + ), + ), + ) + } + + var product models.Product + if err := json.Unmarshal(productBytes, &product); err != nil { + return nil, utils.TraceErrStatusFromSpan(span, err) + } + + span.SetAttributes(attribute.Object("Product", product)) + + r.log.Infow( + fmt.Sprintf( + "product with with key '%s', prefix '%s' laoded", + key, + r.getRedisProductPrefixKey(), + ), + logger.Fields{ + "Product": product, + "Id": product.ProductId, + "Key": key, + "PrefixKey": r.getRedisProductPrefixKey(), + }, + ) + + return &product, nil +} + +func (r *redisProductRepository) DeleteProduct( + ctx context.Context, + key string, +) error { + ctx, span := r.tracer.Start(ctx, "redisRepository.DeleteProduct") + span.SetAttributes( + attribute2.String("PrefixKey", r.getRedisProductPrefixKey()), + ) + span.SetAttributes(attribute2.String("Key", key)) + defer span.End() + + if err := r.redisClient.HDel(ctx, r.getRedisProductPrefixKey(), key).Err(); err != nil { + return utils.TraceErrStatusFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "error in deleting product with key %s", + key, + ), + ), + ) + } + + r.log.Infow( + fmt.Sprintf( + "product with key %s, prefix: %s deleted successfully", + key, + r.getRedisProductPrefixKey(), + ), + logger.Fields{"Key": key, "PrefixKey": r.getRedisProductPrefixKey()}, + ) + + return nil +} + +func (r *redisProductRepository) DeleteAllProducts(ctx context.Context) error { + ctx, span := r.tracer.Start(ctx, "redisRepository.DeleteAllProducts") + span.SetAttributes( + attribute2.String("PrefixKey", r.getRedisProductPrefixKey()), + ) + defer span.End() + + if err := r.redisClient.Del(ctx, r.getRedisProductPrefixKey()).Err(); err != nil { + return utils.TraceErrStatusFromSpan( + span, + errors.WrapIf( + err, + "error in deleting all products", + ), + ) + } + + r.log.Infow( + "all products deleted", + logger.Fields{"PrefixKey": r.getRedisProductPrefixKey()}, + ) + + return nil +} + +func (r *redisProductRepository) getRedisProductPrefixKey() string { + return redisProductPrefixKey +} diff --git a/internal/services/catalogreadservice/internal/products/dto/product_dto.go b/internal/services/catalogreadservice/internal/products/dto/product_dto.go new file mode 100644 index 00000000..5dcd8609 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/dto/product_dto.go @@ -0,0 +1,15 @@ +package dto + +import ( + "time" +) + +type ProductDto struct { + Id string `json:"id"` + ProductId string `json:"productId"` + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/internal/services/catalogreadservice/internal/products/features/creating_product/v1/create_product.go b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/create_product.go new file mode 100644 index 00000000..2fb186d0 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/create_product.go @@ -0,0 +1,49 @@ +package v1 + +import ( + "time" + + validation "github.com/go-ozzo/ozzo-validation" + uuid "github.com/satori/go.uuid" +) + +type CreateProduct struct { + // we generate id ourselves because auto generate mongo string id column with type _id is not an uuid + Id string + ProductId string + Name string + Description string + Price float64 + CreatedAt time.Time +} + +func NewCreateProduct( + productId string, + name string, + description string, + price float64, + createdAt time.Time, +) (*CreateProduct, error) { + command := &CreateProduct{ + Id: uuid.NewV4().String(), + ProductId: productId, + Name: name, + Description: description, + Price: price, + CreatedAt: createdAt, + } + if err := command.Validate(); err != nil { + return nil, err + } + + return command, nil +} + +func (p *CreateProduct) Validate() error { + return validation.ValidateStruct(p, validation.Field(&p.Id, validation.Required), + validation.Field(&p.ProductId, validation.Required), + validation.Field(&p.Name, validation.Required, validation.Length(3, 250)), + validation.Field(&p.Description, validation.Required, validation.Length(3, 500)), + validation.Field(&p.Price, validation.Required), + validation.Field(&p.CreatedAt, validation.Required)) +} diff --git a/internal/services/catalogreadservice/internal/products/features/creating_product/v1/create_product_handler.go b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/create_product_handler.go new file mode 100644 index 00000000..522b18db --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/create_product_handler.go @@ -0,0 +1,76 @@ +package v1 + +import ( + "context" + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/creating_product/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" +) + +type CreateProductHandler struct { + log logger.Logger + mongoRepository data.ProductRepository + redisRepository data.ProductCacheRepository + tracer tracing.AppTracer +} + +func NewCreateProductHandler( + log logger.Logger, + mongoRepository data.ProductRepository, + redisRepository data.ProductCacheRepository, + tracer tracing.AppTracer, +) *CreateProductHandler { + return &CreateProductHandler{ + log: log, + mongoRepository: mongoRepository, + redisRepository: redisRepository, + tracer: tracer, + } +} + +func (c *CreateProductHandler) Handle( + ctx context.Context, + command *CreateProduct, +) (*dtos.CreateProductResponseDto, error) { + product := &models.Product{ + Id: command.Id, // we generate id ourselves because auto generate mongo string id column with type _id is not an uuid + ProductId: command.ProductId, + Name: command.Name, + Description: command.Description, + Price: command.Price, + CreatedAt: command.CreatedAt, + } + + createdProduct, err := c.mongoRepository.CreateProduct(ctx, product) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in creating product in the mongo repository", + ) + } + + err = c.redisRepository.PutProduct(ctx, createdProduct.Id, createdProduct) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in creating product in the redis repository", + ) + } + + response := &dtos.CreateProductResponseDto{Id: createdProduct.Id} + + c.log.Infow( + fmt.Sprintf( + "product with id: {%s} created", + product.Id, + ), + logger.Fields{"ProductId": command.ProductId, "Id": product.Id}, + ) + + return response, nil +} diff --git a/internal/services/catalogreadservice/internal/products/features/creating_product/v1/dtos/create_product_response_dto.go b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/dtos/create_product_response_dto.go new file mode 100644 index 00000000..32e3adfd --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/dtos/create_product_response_dto.go @@ -0,0 +1,5 @@ +package dtos + +type CreateProductResponseDto struct { + Id string `json:"Id"` +} diff --git a/internal/services/catalogreadservice/internal/products/features/creating_product/v1/events/integrationevents/externalevents/product_created.go b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/events/integrationevents/externalevents/product_created.go new file mode 100644 index 00000000..00b4c296 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/events/integrationevents/externalevents/product_created.go @@ -0,0 +1,16 @@ +package externalEvents + +import ( + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" +) + +type ProductCreatedV1 struct { + *types.Message + ProductId string `json:"productId,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Price float64 `json:"price,omitempty"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/internal/services/catalogreadservice/internal/products/features/creating_product/v1/events/integrationevents/externalevents/product_created_consumer.go b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/events/integrationevents/externalevents/product_created_consumer.go new file mode 100644 index 00000000..75992f3d --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/creating_product/v1/events/integrationevents/externalevents/product_created_consumer.go @@ -0,0 +1,78 @@ +package externalEvents + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + v1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/creating_product/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/creating_product/v1/dtos" + + "emperror.dev/errors" + "github.com/go-playground/validator" + "github.com/mehdihadeli/go-mediatr" +) + +type productCreatedConsumer struct { + logger logger.Logger + validator *validator.Validate + tracer tracing.AppTracer +} + +func NewProductCreatedConsumer( + logger logger.Logger, + validator *validator.Validate, + tracer tracing.AppTracer, +) consumer.ConsumerHandler { + return &productCreatedConsumer{ + logger: logger, + validator: validator, + tracer: tracer, + } +} + +func (c *productCreatedConsumer) Handle( + ctx context.Context, + consumeContext types.MessageConsumeContext, +) error { + product, ok := consumeContext.Message().(*ProductCreatedV1) + if !ok { + return errors.New("error in casting message to ProductCreatedV1") + } + + command, err := v1.NewCreateProduct( + product.ProductId, + product.Name, + product.Description, + product.Price, + product.CreatedAt, + ) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "command validation failed", + ) + + return validationErr + } + _, err = mediatr.Send[*v1.CreateProduct, *dtos.CreateProductResponseDto]( + ctx, + command, + ) + if err != nil { + return errors.WithMessage( + err, + fmt.Sprintf( + "error in sending CreateProduct with id: {%s}", + command.ProductId, + ), + ) + } + c.logger.Info("Product consumer handled.") + + return err +} diff --git a/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/commands/delete_product.go b/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/commands/delete_product.go new file mode 100644 index 00000000..96f65105 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/commands/delete_product.go @@ -0,0 +1,25 @@ +package commands + +import ( + validation "github.com/go-ozzo/ozzo-validation" + "github.com/go-ozzo/ozzo-validation/is" + uuid "github.com/satori/go.uuid" +) + +type DeleteProduct struct { + ProductId uuid.UUID +} + +func NewDeleteProduct(productId uuid.UUID) (*DeleteProduct, error) { + delProduct := &DeleteProduct{ProductId: productId} + if err := delProduct.Validate(); err != nil { + return nil, err + } + + return delProduct, nil +} + +func (p *DeleteProduct) Validate() error { + return validation.ValidateStruct(p, + validation.Field(&p.ProductId, validation.Required, is.UUIDv4)) +} diff --git a/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/commands/delete_product_handler.go b/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/commands/delete_product_handler.go new file mode 100644 index 00000000..44cfe9f2 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/commands/delete_product_handler.go @@ -0,0 +1,87 @@ +package commands + +import ( + "context" + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + + "github.com/mehdihadeli/go-mediatr" +) + +type DeleteProductCommand struct { + log logger.Logger + mongoRepository data.ProductRepository + redisRepository data.ProductCacheRepository + tracer tracing.AppTracer +} + +func NewDeleteProductHandler( + log logger.Logger, + repository data.ProductRepository, + redisRepository data.ProductCacheRepository, + tracer tracing.AppTracer, +) *DeleteProductCommand { + return &DeleteProductCommand{ + log: log, + mongoRepository: repository, + redisRepository: redisRepository, + tracer: tracer, + } +} + +func (c *DeleteProductCommand) Handle( + ctx context.Context, + command *DeleteProduct, +) (*mediatr.Unit, error) { + product, err := c.mongoRepository.GetProductByProductId( + ctx, + command.ProductId.String(), + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + fmt.Sprintf( + "error in fetching product with productId %s in the mongo repository", + command.ProductId, + ), + ) + } + if product == nil { + return nil, customErrors.NewNotFoundErrorWrap( + err, + fmt.Sprintf( + "product with productId %s not found", + command.ProductId, + ), + ) + } + + if err := c.mongoRepository.DeleteProductByID(ctx, product.Id); err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in deleting product in the mongo repository", + ) + } + + err = c.redisRepository.DeleteProduct(ctx, product.Id) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in deleting product in the redis repository", + ) + } + + c.log.Infow( + fmt.Sprintf( + "product with id: {%s} deleted", + product.Id, + ), + logger.Fields{"ProductId": command.ProductId, "Id": product.Id}, + ) + + return &mediatr.Unit{}, nil +} diff --git a/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/events/integration_events/external_events/product_deleted.go b/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/events/integration_events/external_events/product_deleted.go new file mode 100644 index 00000000..a5346494 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/events/integration_events/external_events/product_deleted.go @@ -0,0 +1,10 @@ +package externalEvents + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" +) + +type ProductDeletedV1 struct { + *types.Message + ProductId string `json:"productId,omitempty"` +} diff --git a/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/events/integration_events/external_events/product_deleted_consumer.go b/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/events/integration_events/external_events/product_deleted_consumer.go new file mode 100644 index 00000000..ad794cf0 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/events/integration_events/external_events/product_deleted_consumer.go @@ -0,0 +1,71 @@ +package externalEvents + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/commands" + + "emperror.dev/errors" + "github.com/go-playground/validator" + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" +) + +type productDeletedConsumer struct { + logger logger.Logger + validator *validator.Validate + tracer tracing.AppTracer +} + +func NewProductDeletedConsumer( + logger logger.Logger, + validator *validator.Validate, + tracer tracing.AppTracer, +) consumer.ConsumerHandler { + return &productDeletedConsumer{ + logger: logger, + validator: validator, + tracer: tracer, + } +} + +func (c *productDeletedConsumer) Handle( + ctx context.Context, + consumeContext types.MessageConsumeContext, +) error { + message, ok := consumeContext.Message().(*ProductDeletedV1) + if !ok { + return errors.New("error in casting message to ProductDeletedV1") + } + + productUUID, err := uuid.FromString(message.ProductId) + if err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the converting uuid", + ) + + return badRequestErr + } + + command, err := commands.NewDeleteProduct(productUUID) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "command validation failed", + ) + + return validationErr + } + + _, err = mediatr.Send[*commands.DeleteProduct, *mediatr.Unit](ctx, command) + + c.logger.Info("productDeletedConsumer executed successfully.") + + return err +} diff --git a/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos/get_product_by_id_request_dto.go b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos/get_product_by_id_request_dto.go new file mode 100644 index 00000000..caa090ab --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos/get_product_by_id_request_dto.go @@ -0,0 +1,7 @@ +package dtos + +import uuid "github.com/satori/go.uuid" + +type GetProductByIdRequestDto struct { + Id uuid.UUID `param:"id" json:"-"` +} diff --git a/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos/get_product_by_id_response_dto.go b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos/get_product_by_id_response_dto.go new file mode 100644 index 00000000..aff704dd --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos/get_product_by_id_response_dto.go @@ -0,0 +1,7 @@ +package dtos + +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/dto" + +type GetProductByIdResponseDto struct { + Product *dto.ProductDto `json:"product"` +} diff --git a/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/endpoints/get_product_by_id_endpoint.go b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/endpoints/get_product_by_id_endpoint.go new file mode 100644 index 00000000..76ac64b1 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/endpoints/get_product_by_id_endpoint.go @@ -0,0 +1,79 @@ +package endpoints + +import ( + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/params" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/queries" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type getProductByIdEndpoint struct { + params.ProductRouteParams +} + +func NewGetProductByIdEndpoint( + params params.ProductRouteParams, +) route.Endpoint { + return &getProductByIdEndpoint{ + ProductRouteParams: params, + } +} + +func (ep *getProductByIdEndpoint) MapEndpoint() { + ep.ProductsGroup.GET("/:id", ep.handler()) +} + +// GetProductByID +// @Tags Products +// @Summary Get product +// @Description Get product by id +// @Accept json +// @Produce json +// @Param id path string true "Product ID" +// @Success 200 {object} dtos.GetProductByIdResponseDto +// @Router /api/v1/products/{id} [get] +func (ep *getProductByIdEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + + request := &dtos.GetProductByIdRequestDto{} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the binding request", + ) + + return badRequestErr + } + + query, err := queries.NewGetProductById(request.Id) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "query validation failed", + ) + + return validationErr + } + + queryResult, err := mediatr.Send[*queries.GetProductById, *dtos.GetProductByIdResponseDto]( + ctx, + query, + ) + if err != nil { + return errors.WithMessage( + err, + "error in sending GetProductById", + ) + } + + return c.JSON(http.StatusOK, queryResult) + } +} diff --git a/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/queries/get_product_by_id.go b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/queries/get_product_by_id.go new file mode 100644 index 00000000..20803c0f --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/queries/get_product_by_id.go @@ -0,0 +1,24 @@ +package queries + +import ( + validation "github.com/go-ozzo/ozzo-validation" + "github.com/go-ozzo/ozzo-validation/is" + uuid "github.com/satori/go.uuid" +) + +type GetProductById struct { + Id uuid.UUID +} + +func NewGetProductById(id uuid.UUID) (*GetProductById, error) { + product := &GetProductById{Id: id} + if err := product.Validate(); err != nil { + return nil, err + } + + return product, nil +} + +func (p *GetProductById) Validate() error { + return validation.ValidateStruct(p, validation.Field(&p.Id, validation.Required, is.UUIDv4)) +} diff --git a/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/queries/get_product_by_id_handler.go b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/queries/get_product_by_id_handler.go new file mode 100644 index 00000000..8018f056 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/queries/get_product_by_id_handler.go @@ -0,0 +1,97 @@ +package queries + +import ( + "context" + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/dto" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" +) + +type GetProductByIdHandler struct { + log logger.Logger + mongoRepository data.ProductRepository + redisRepository data.ProductCacheRepository + tracer tracing.AppTracer +} + +func NewGetProductByIdHandler( + log logger.Logger, + mongoRepository data.ProductRepository, + redisRepository data.ProductCacheRepository, + tracer tracing.AppTracer, +) *GetProductByIdHandler { + return &GetProductByIdHandler{ + log: log, + mongoRepository: mongoRepository, + redisRepository: redisRepository, + tracer: tracer, + } +} + +func (q *GetProductByIdHandler) Handle( + ctx context.Context, + query *GetProductById, +) (*dtos.GetProductByIdResponseDto, error) { + redisProduct, err := q.redisRepository.GetProductById( + ctx, + query.Id.String(), + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + fmt.Sprintf( + "error in getting product with id %d in the redis repository", + query.Id, + ), + ) + } + + var product *models.Product + + if redisProduct != nil { + product = redisProduct + } else { + var mongoProduct *models.Product + mongoProduct, err = q.mongoRepository.GetProductById(ctx, query.Id.String()) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap(err, fmt.Sprintf("error in getting product with id %d in the mongo repository", query.Id)) + } + if mongoProduct == nil { + mongoProduct, err = q.mongoRepository.GetProductByProductId(ctx, query.Id.String()) + } + if err != nil { + return nil, err + } + + product = mongoProduct + err = q.redisRepository.PutProduct(ctx, product.Id, product) + if err != nil { + return new(dtos.GetProductByIdResponseDto), err + } + } + + productDto, err := mapper.Map[*dto.ProductDto](product) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in the mapping product", + ) + } + + q.log.Infow( + fmt.Sprintf( + "product with id: {%s} fetched", + query.Id, + ), + logger.Fields{"ProductId": product.ProductId, "Id": product.Id}, + ) + + return &dtos.GetProductByIdResponseDto{Product: productDto}, nil +} diff --git a/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos/get_products_request_dto.go b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos/get_products_request_dto.go new file mode 100644 index 00000000..99a94a2b --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos/get_products_request_dto.go @@ -0,0 +1,7 @@ +package dtos + +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + +type GetProductsRequestDto struct { + *utils.ListQuery +} diff --git a/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos/get_products_response_dto.go b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos/get_products_response_dto.go new file mode 100644 index 00000000..b2ecaf7c --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos/get_products_response_dto.go @@ -0,0 +1,10 @@ +package dtos + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/dto" +) + +type GetProductsResponseDto struct { + Products *utils.ListResult[*dto.ProductDto] +} diff --git a/internal/services/catalogreadservice/internal/products/features/getting_products/v1/endpoints/get_products_endpoint.go b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/endpoints/get_products_endpoint.go new file mode 100644 index 00000000..6218216d --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/endpoints/get_products_endpoint.go @@ -0,0 +1,81 @@ +package endpoints + +import ( + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/params" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/getting_products/v1/queries" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type getProductsEndpoint struct { + params.ProductRouteParams +} + +func NewGetProductsEndpoint( + params params.ProductRouteParams, +) route.Endpoint { + return &getProductsEndpoint{ + ProductRouteParams: params, + } +} + +func (ep *getProductsEndpoint) MapEndpoint() { + ep.ProductsGroup.GET("", ep.handler()) +} + +// GetAllProducts +// @Tags Products +// @Summary Get all product +// @Description Get all products +// @Accept json +// @Produce json +// @Param getProductsRequestDto query dtos.GetProductsRequestDto false "GetProductsRequestDto" +// @Success 200 {object} dtos.GetProductsResponseDto +// @Router /api/v1/products [get] +func (ep *getProductsEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + + listQuery, err := utils.GetListQueryFromCtx(c) + if err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in getting data from query string", + ) + + return badRequestErr + } + + request := queries.NewGetProducts(listQuery) + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the binding request", + ) + + return badRequestErr + } + query := &queries.GetProducts{ListQuery: request.ListQuery} + + queryResult, err := mediatr.Send[*queries.GetProducts, *dtos.GetProductsResponseDto]( + ctx, + query, + ) + if err != nil { + return errors.WithMessage( + err, + "error in sending GetProducts", + ) + } + + return c.JSON(http.StatusOK, queryResult) + } +} diff --git a/internal/services/catalogreadservice/internal/products/features/getting_products/v1/queries/get_products.go b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/queries/get_products.go new file mode 100644 index 00000000..483a482b --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/queries/get_products.go @@ -0,0 +1,13 @@ +package queries + +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + +// Ref: https://golangbot.com/inheritance/ + +type GetProducts struct { + *utils.ListQuery +} + +func NewGetProducts(query *utils.ListQuery) *GetProducts { + return &GetProducts{ListQuery: query} +} diff --git a/internal/services/catalogreadservice/internal/products/features/getting_products/v1/queries/get_products_handler.go b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/queries/get_products_handler.go new file mode 100644 index 00000000..01fc1530 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/getting_products/v1/queries/get_products_handler.go @@ -0,0 +1,58 @@ +package queries + +import ( + "context" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/dto" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos" +) + +type GetProductsHandler struct { + log logger.Logger + mongoRepository data.ProductRepository + tracer tracing.AppTracer +} + +func NewGetProductsHandler( + log logger.Logger, + mongoRepository data.ProductRepository, + tracer tracing.AppTracer, +) *GetProductsHandler { + return &GetProductsHandler{ + log: log, + mongoRepository: mongoRepository, + tracer: tracer, + } +} + +func (c *GetProductsHandler) Handle( + ctx context.Context, + query *GetProducts, +) (*dtos.GetProductsResponseDto, error) { + products, err := c.mongoRepository.GetAllProducts(ctx, query.ListQuery) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in getting products in the repository", + ) + } + + listResultDto, err := utils.ListResultToListResultDto[*dto.ProductDto]( + products, + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in the mapping ListResultToListResultDto", + ) + } + + c.log.Info("products fetched") + + return &dtos.GetProductsResponseDto{Products: listResultDto}, nil +} diff --git a/internal/services/catalogreadservice/internal/products/features/searching_products/v1/dtos/search_products_request_dto.go b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/dtos/search_products_request_dto.go new file mode 100644 index 00000000..d7920bdc --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/dtos/search_products_request_dto.go @@ -0,0 +1,10 @@ +package dtos + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" +) + +type SearchProductsRequestDto struct { + SearchText string `query:"search" json:"search"` + *utils.ListQuery ` json:"listQuery"` +} diff --git a/internal/services/catalogreadservice/internal/products/features/searching_products/v1/dtos/search_products_response_dto.go b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/dtos/search_products_response_dto.go new file mode 100644 index 00000000..ac490629 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/dtos/search_products_response_dto.go @@ -0,0 +1,10 @@ +package dtos + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/dto" +) + +type SearchProductsResponseDto struct { + Products *utils.ListResult[*dto.ProductDto] +} diff --git a/internal/services/catalogreadservice/internal/products/features/searching_products/v1/endpoints/search_products_endpoint.go b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/endpoints/search_products_endpoint.go new file mode 100644 index 00000000..5f7fbd04 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/endpoints/search_products_endpoint.go @@ -0,0 +1,95 @@ +package endpoints + +import ( + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/params" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/searching_products/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/searching_products/v1/queries" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type searchProductsEndpoint struct { + params.ProductRouteParams +} + +func NewSearchProductsEndpoint( + params params.ProductRouteParams, +) route.Endpoint { + return &searchProductsEndpoint{ + ProductRouteParams: params, + } +} + +func (ep *searchProductsEndpoint) MapEndpoint() { + ep.ProductsGroup.GET("/search", ep.handler()) +} + +// SearchProducts +// @Tags Products +// @Summary Search products +// @Description Search products +// @Accept json +// @Produce json +// @Param searchProductsRequestDto query dtos.SearchProductsRequestDto false "SearchProductsRequestDto" +// @Success 200 {object} dtos.SearchProductsResponseDto +// @Router /api/v1/products/search [get] +func (ep *searchProductsEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + + listQuery, err := utils.GetListQueryFromCtx(c) + if err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in getting data from query string", + ) + + return badRequestErr + } + + request := &dtos.SearchProductsRequestDto{ListQuery: listQuery} + + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the binding request", + ) + + return badRequestErr + } + + query := &queries.SearchProducts{ + SearchText: request.SearchText, + ListQuery: request.ListQuery, + } + + if err := query.Validate(); err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "query validation failed", + ) + + return validationErr + } + + queryResult, err := mediatr.Send[*queries.SearchProducts, *dtos.SearchProductsResponseDto]( + ctx, + query, + ) + if err != nil { + return errors.WithMessage( + err, + "error in sending SearchProducts", + ) + } + + return c.JSON(http.StatusOK, queryResult) + } +} diff --git a/internal/services/catalogreadservice/internal/products/features/searching_products/v1/queries/search_products.go b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/queries/search_products.go new file mode 100644 index 00000000..660d0fa0 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/queries/search_products.go @@ -0,0 +1,16 @@ +package queries + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + + validation "github.com/go-ozzo/ozzo-validation" +) + +type SearchProducts struct { + SearchText string + *utils.ListQuery +} + +func (s *SearchProducts) Validate() error { + return validation.ValidateStruct(s, validation.Field(&s.SearchText, validation.Required)) +} diff --git a/internal/services/catalogreadservice/internal/products/features/searching_products/v1/queries/search_products_handler.go b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/queries/search_products_handler.go new file mode 100644 index 00000000..bbd28047 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/searching_products/v1/queries/search_products_handler.go @@ -0,0 +1,61 @@ +package queries + +import ( + "context" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/dto" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/searching_products/v1/dtos" +) + +type SearchProductsHandler struct { + log logger.Logger + mongoRepository data.ProductRepository + tracer tracing.AppTracer +} + +func NewSearchProductsHandler( + log logger.Logger, + repository data.ProductRepository, + tracer tracing.AppTracer, +) *SearchProductsHandler { + return &SearchProductsHandler{ + log: log, + mongoRepository: repository, + tracer: tracer, + } +} + +func (c *SearchProductsHandler) Handle( + ctx context.Context, + query *SearchProducts, +) (*dtos.SearchProductsResponseDto, error) { + products, err := c.mongoRepository.SearchProducts( + ctx, + query.SearchText, + query.ListQuery, + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in searching products in the repository", + ) + } + + listResultDto, err := utils.ListResultToListResultDto[*dto.ProductDto]( + products, + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in the mapping ListResultToListResultDto", + ) + } + c.log.Info("products fetched") + + return &dtos.SearchProductsResponseDto{Products: listResultDto}, nil +} diff --git a/internal/services/catalogreadservice/internal/products/features/updating_products/v1/commands/update_product.go b/internal/services/catalogreadservice/internal/products/features/updating_products/v1/commands/update_product.go new file mode 100644 index 00000000..332cd9be --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/updating_products/v1/commands/update_product.go @@ -0,0 +1,40 @@ +package commands + +import ( + "time" + + validation "github.com/go-ozzo/ozzo-validation" + "github.com/go-ozzo/ozzo-validation/is" + uuid "github.com/satori/go.uuid" +) + +type UpdateProduct struct { + ProductId uuid.UUID + Name string + Description string + Price float64 + UpdatedAt time.Time +} + +func NewUpdateProduct(productId uuid.UUID, name string, description string, price float64) (*UpdateProduct, error) { + product := &UpdateProduct{ + ProductId: productId, + Name: name, + Description: description, + Price: price, + UpdatedAt: time.Now(), + } + if err := product.Validate(); err != nil { + return nil, err + } + return product, nil +} + +func (p *UpdateProduct) Validate() error { + return validation.ValidateStruct(p, validation.Field(&p.ProductId, validation.Required, is.UUIDv4), + validation.Field(&p.Name, validation.Required, validation.Length(0, 255)), + validation.Field(&p.Description, validation.Required, validation.Length(0, 5000)), + validation.Field(&p.Price, validation.Required, validation.Min(0.0)), + validation.Field(&p.UpdatedAt, validation.Required), + ) +} diff --git a/internal/services/catalogreadservice/internal/products/features/updating_products/v1/commands/update_product_handler.go b/internal/services/catalogreadservice/internal/products/features/updating_products/v1/commands/update_product_handler.go new file mode 100644 index 00000000..c7693a16 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/updating_products/v1/commands/update_product_handler.go @@ -0,0 +1,94 @@ +package commands + +import ( + "context" + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + + "github.com/mehdihadeli/go-mediatr" +) + +type UpdateProductHandler struct { + log logger.Logger + mongoRepository data.ProductRepository + redisRepository data.ProductCacheRepository + tracer tracing.AppTracer +} + +func NewUpdateProductHandler( + log logger.Logger, + mongoRepository data.ProductRepository, + redisRepository data.ProductCacheRepository, + tracer tracing.AppTracer, +) *UpdateProductHandler { + return &UpdateProductHandler{ + log: log, + mongoRepository: mongoRepository, + redisRepository: redisRepository, + tracer: tracer, + } +} + +func (c *UpdateProductHandler) Handle( + ctx context.Context, + command *UpdateProduct, +) (*mediatr.Unit, error) { + product, err := c.mongoRepository.GetProductByProductId( + ctx, + command.ProductId.String(), + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + fmt.Sprintf( + "error in fetching product with productId %s in the mongo repository", + command.ProductId, + ), + ) + } + + if product == nil { + return nil, customErrors.NewNotFoundErrorWrap( + err, + fmt.Sprintf( + "product with productId %s not found", + command.ProductId, + ), + ) + } + + product.Price = command.Price + product.Name = command.Name + product.Description = command.Description + product.UpdatedAt = command.UpdatedAt + + _, err = c.mongoRepository.UpdateProduct(ctx, product) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in updating product in the mongo repository", + ) + } + + err = c.redisRepository.PutProduct(ctx, product.Id, product) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in updating product in the redis repository", + ) + } + + c.log.Infow( + fmt.Sprintf( + "product with id: {%s} updated", + product.Id, + ), + logger.Fields{"ProductId": command.ProductId, "Id": product.Id}, + ) + + return &mediatr.Unit{}, nil +} diff --git a/internal/services/catalogreadservice/internal/products/features/updating_products/v1/events/integration_events/external_events/product_updated.go b/internal/services/catalogreadservice/internal/products/features/updating_products/v1/events/integration_events/external_events/product_updated.go new file mode 100644 index 00000000..b30f2dda --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/updating_products/v1/events/integration_events/external_events/product_updated.go @@ -0,0 +1,16 @@ +package externalEvents + +import ( + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" +) + +type ProductUpdatedV1 struct { + *types.Message + ProductId string `json:"productId,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Price float64 `json:"price,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty"` +} diff --git a/internal/services/catalogreadservice/internal/products/features/updating_products/v1/events/integration_events/external_events/product_updated_consumer.go b/internal/services/catalogreadservice/internal/products/features/updating_products/v1/events/integration_events/external_events/product_updated_consumer.go new file mode 100644 index 00000000..ffdf3d54 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/features/updating_products/v1/events/integration_events/external_events/product_updated_consumer.go @@ -0,0 +1,107 @@ +package externalEvents + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/consumer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/updating_products/v1/commands" + + "emperror.dev/errors" + "github.com/go-playground/validator" + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" +) + +type productUpdatedConsumer struct { + logger logger.Logger + validator *validator.Validate + tracer tracing.AppTracer +} + +func NewProductUpdatedConsumer( + logger logger.Logger, + validator *validator.Validate, + tracer tracing.AppTracer, +) consumer.ConsumerHandler { + return &productUpdatedConsumer{ + logger: logger, + validator: validator, + tracer: tracer, + } +} + +func (c *productUpdatedConsumer) Handle( + ctx context.Context, + consumeContext types.MessageConsumeContext, +) error { + message, ok := consumeContext.Message().(*ProductUpdatedV1) + if !ok { + return errors.New("error in casting message to ProductUpdatedV1") + } + + ctx, span := c.tracer.Start(ctx, "productUpdatedConsumer.Handle") + span.SetAttributes(attribute.Object("Message", consumeContext.Message())) + defer span.End() + + productUUID, err := uuid.FromString(message.ProductId) + if err != nil { + c.logger.WarnMsg("uuid.FromString", err) + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "[updateProductConsumer_Consume.uuid.FromString] error in the converting uuid", + ) + c.logger.Errorf( + fmt.Sprintf( + "[updateProductConsumer_Consume.uuid.FromString] err: %v", + utils.TraceErrStatusFromSpan(span, badRequestErr), + ), + ) + return err + } + + command, err := commands.NewUpdateProduct( + productUUID, + message.Name, + message.Description, + message.Price, + ) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "[updateProductConsumer_Consume.NewValidationErrorWrap] command validation failed", + ) + c.logger.Errorf( + fmt.Sprintf( + "[updateProductConsumer_Consume.StructCtx] err: {%v}", + utils.TraceErrStatusFromSpan(span, validationErr), + ), + ) + return err + } + + _, err = mediatr.Send[*commands.UpdateProduct, *mediatr.Unit](ctx, command) + if err != nil { + err = errors.WithMessage( + err, + "[updateProductConsumer_Consume.Send] error in sending UpdateProduct", + ) + c.logger.Errorw( + fmt.Sprintf( + "[updateProductConsumer_Consume.Send] id: {%s}, err: {%v}", + command.ProductId, + utils.TraceErrStatusFromSpan(span, err), + ), + logger.Fields{"Id": command.ProductId}, + ) + return err + } + + return nil +} diff --git a/internal/services/catalogreadservice/internal/products/models/product.go b/internal/services/catalogreadservice/internal/products/models/product.go new file mode 100644 index 00000000..be3c6f31 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/models/product.go @@ -0,0 +1,24 @@ +package models + +import ( + "time" +) + +type Product struct { + // we generate id ourselves because auto generate mongo string id column with type _id is not an uuid + Id string `json:"id" bson:"_id,omitempty"` // https://www.mongodb.com/docs/drivers/go/current/fundamentals/crud/write-operations/insert/#the-_id-field + ProductId string `json:"productId" bson:"productId"` + Name string `json:"name,omitempty" bson:"name,omitempty"` + Description string `json:"description,omitempty" bson:"description,omitempty"` + Price float64 `json:"price,omitempty" bson:"price,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty" bson:"updatedAt,omitempty"` +} + +type ProductsList struct { + TotalCount int64 `json:"totalCount" bson:"totalCount"` + TotalPages int64 `json:"totalPages" bson:"totalPages"` + Page int64 `json:"page" bson:"page"` + Size int64 `json:"size" bson:"size"` + Products []*Product `json:"products" bson:"products"` +} diff --git a/internal/services/catalogreadservice/internal/products/products_fx.go b/internal/services/catalogreadservice/internal/products/products_fx.go new file mode 100644 index 00000000..9b41b280 --- /dev/null +++ b/internal/services/catalogreadservice/internal/products/products_fx.go @@ -0,0 +1,37 @@ +package products + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/data/repositories" + getProductByIdV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/endpoints" + getProductsV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/getting_products/v1/endpoints" + searchProductV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/searching_products/v1/endpoints" + + "github.com/labstack/echo/v4" + "go.uber.org/fx" +) + +var Module = fx.Module( + "productsfx", + + // Other provides + fx.Provide(repositories.NewRedisProductRepository), + fx.Provide(repositories.NewMongoProductRepository), + + fx.Provide(fx.Annotate(func(catalogsServer customEcho.EchoHttpServer) *echo.Group { + var g *echo.Group + catalogsServer.RouteBuilder().RegisterGroupFunc("/api/v1", func(v1 *echo.Group) { + group := v1.Group("/products") + g = group + }) + + return g + }, fx.ResultTags(`name:"product-echo-group"`))), + + fx.Provide( + route.AsRoute(getProductsV1.NewGetProductsEndpoint, "product-routes"), + route.AsRoute(searchProductV1.NewSearchProductsEndpoint, "product-routes"), + route.AsRoute(getProductByIdV1.NewGetProductByIdEndpoint, "product-routes"), + ), +) diff --git a/internal/services/catalogreadservice/internal/shared/app/app.go b/internal/services/catalogreadservice/internal/shared/app/app.go new file mode 100644 index 00000000..c16995e6 --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/app/app.go @@ -0,0 +1,25 @@ +package app + +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/configurations/catalogs" + +type App struct{} + +func NewApp() *App { + return &App{} +} + +func (a *App) Run() { + // configure dependencies + appBuilder := NewCatalogsReadApplicationBuilder() + appBuilder.ProvideModule(catalogs.CatalogsServiceModule) + + app := appBuilder.Build() + + // configure application + app.ConfigureCatalogs() + + app.MapCatalogsEndpoints() + + app.Logger().Info("Starting catalog_service application") + app.Run() +} diff --git a/internal/services/catalogreadservice/internal/shared/app/application.go b/internal/services/catalogreadservice/internal/shared/app/application.go new file mode 100644 index 00000000..6bac5bb8 --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/app/application.go @@ -0,0 +1,27 @@ +package app + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/configurations/catalogs" + + "go.uber.org/fx" +) + +type CatalogsReadApplication struct { + *catalogs.CatalogsServiceConfigurator +} + +func NewCatalogsReadApplication( + providers []interface{}, + decorates []interface{}, + options []fx.Option, + logger logger.Logger, + environment environment.Environment, +) *CatalogsReadApplication { + app := fxapp.NewApplication(providers, decorates, options, logger, environment) + return &CatalogsReadApplication{ + CatalogsServiceConfigurator: catalogs.NewCatalogsServiceConfigurator(app), + } +} diff --git a/internal/services/catalogreadservice/internal/shared/app/application_builder.go b/internal/services/catalogreadservice/internal/shared/app/application_builder.go new file mode 100644 index 00000000..d52501dc --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/app/application_builder.go @@ -0,0 +1,26 @@ +package app + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" +) + +type CatalogsReadApplicationBuilder struct { + contracts.ApplicationBuilder +} + +func NewCatalogsReadApplicationBuilder() *CatalogsReadApplicationBuilder { + builder := &CatalogsReadApplicationBuilder{fxapp.NewApplicationBuilder()} + + return builder +} + +func (a *CatalogsReadApplicationBuilder) Build() *CatalogsReadApplication { + return NewCatalogsReadApplication( + a.GetProvides(), + a.GetDecorates(), + a.Options(), + a.Logger(), + a.Environment(), + ) +} diff --git a/internal/services/catalogreadservice/internal/shared/app/test/test_app.go b/internal/services/catalogreadservice/internal/shared/app/test/test_app.go new file mode 100644 index 00000000..842880ec --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/app/test/test_app.go @@ -0,0 +1,119 @@ +package test + +import ( + "context" + "os" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + config3 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/bus" + config2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/redis" + mongo2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/mongo" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/rabbitmq" + redis2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/redis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + catalogs2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/configurations/catalogs" + + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/mongo" + "go.opentelemetry.io/otel/trace" +) + +type TestApp struct{} + +type TestAppResult struct { + Cfg *config.Config + Bus bus.RabbitmqBus + Container contracts.Container + Logger logger.Logger + RabbitmqOptions *config2.RabbitmqOptions + EchoHttpOptions *config3.EchoHttpOptions + MongoDbOptions *mongodb.MongoDbOptions + RedisOptions *redis.RedisOptions + ProductCacheRepository data.ProductCacheRepository + ProductRepository data.ProductRepository + MongoClient *mongo.Client + Tracer trace.Tracer +} + +func NewTestApp() *TestApp { + return &TestApp{} +} + +func (a *TestApp) Run(t *testing.T) (result *TestAppResult) { + lifetimeCtx := context.Background() + + // ref: https://github.com/uber-go/fx/blob/master/app_test.go + appBuilder := NewCatalogsReadTestApplicationBuilder(t) + appBuilder.ProvideModule(catalogs2.CatalogsServiceModule) + + // replace real options with docker container options for testing + appBuilder.Decorate(rabbitmq.RabbitmqContainerOptionsDecorator(t, lifetimeCtx)) + appBuilder.Decorate(mongo2.MongoContainerOptionsDecorator(t, lifetimeCtx)) + appBuilder.Decorate(redis2.RedisContainerOptionsDecorator(t, lifetimeCtx)) + + testApp := appBuilder.Build() + + testApp.ConfigureCatalogs() + + testApp.MapCatalogsEndpoints() + + testApp.ResolveFunc( + func(cfg *config.Config, + bus bus.RabbitmqBus, + logger logger.Logger, + rabbitmqOptions *config2.RabbitmqOptions, + mongoOptions *mongodb.MongoDbOptions, + redisOptions *redis.RedisOptions, + productCacheRepository data.ProductCacheRepository, + productRepository data.ProductRepository, + echoOptions *config3.EchoHttpOptions, + mongoClient *mongo.Client, + tracer trace.Tracer, + ) { + result = &TestAppResult{ + Bus: bus, + Cfg: cfg, + Container: testApp, + Logger: logger, + RabbitmqOptions: rabbitmqOptions, + MongoDbOptions: mongoOptions, + ProductRepository: productRepository, + ProductCacheRepository: productCacheRepository, + EchoHttpOptions: echoOptions, + MongoClient: mongoClient, + RedisOptions: redisOptions, + Tracer: tracer, + } + }, + ) + + // we need a longer timout for up and running our testcontainers + duration := time.Second * 300 + + // short timeout for handling start hooks and setup dependencies + startCtx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + err := testApp.Start(startCtx) + if err != nil { + t.Errorf("Error starting, err: %v", err) + os.Exit(1) + } + + t.Cleanup(func() { + // short timeout for handling stop hooks + stopCtx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + err = testApp.Stop(stopCtx) + require.NoError(t, err) + }) + + return +} diff --git a/internal/services/catalogreadservice/internal/shared/app/test/test_application.go b/internal/services/catalogreadservice/internal/shared/app/test/test_application.go new file mode 100644 index 00000000..dfabeeef --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/app/test/test_application.go @@ -0,0 +1,44 @@ +package test + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/test" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/app" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/configurations/catalogs" + + "go.uber.org/fx" + "go.uber.org/fx/fxtest" +) + +type CatalogsReadTestApplication struct { + *app.CatalogsReadApplication + tb fxtest.TB +} + +func NewCatalogsReadTestApplication( + tb fxtest.TB, + providers []interface{}, + decorates []interface{}, + options []fx.Option, + logger logger.Logger, + environment environment.Environment, +) *CatalogsReadTestApplication { + testApp := test.NewTestApplication( + tb, + providers, + decorates, + options, + logger, + environment, + ) + + catalogApplication := &app.CatalogsReadApplication{ + CatalogsServiceConfigurator: catalogs.NewCatalogsServiceConfigurator(testApp), + } + + return &CatalogsReadTestApplication{ + CatalogsReadApplication: catalogApplication, + tb: tb, + } +} diff --git a/internal/services/catalogreadservice/internal/shared/app/test/test_application_builder.go b/internal/services/catalogreadservice/internal/shared/app/test/test_application_builder.go new file mode 100644 index 00000000..2ff18db0 --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/app/test/test_application_builder.go @@ -0,0 +1,31 @@ +package test + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/test" + + "go.uber.org/fx/fxtest" +) + +type CatalogsReadTestApplicationBuilder struct { + contracts.ApplicationBuilder + tb fxtest.TB +} + +func NewCatalogsReadTestApplicationBuilder(tb fxtest.TB) *CatalogsReadTestApplicationBuilder { + return &CatalogsReadTestApplicationBuilder{ + ApplicationBuilder: test.NewTestApplicationBuilder(tb), + tb: tb, + } +} + +func (a *CatalogsReadTestApplicationBuilder) Build() *CatalogsReadTestApplication { + return NewCatalogsReadTestApplication( + a.tb, + a.GetProvides(), + a.GetDecorates(), + a.Options(), + a.Logger(), + a.Environment(), + ) +} diff --git a/internal/services/catalogreadservice/internal/shared/configurations/catalogs/catalogs_configurator.go b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/catalogs_configurator.go new file mode 100644 index 00000000..31c3e0af --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/catalogs_configurator.go @@ -0,0 +1,76 @@ +package catalogs + +import ( + "fmt" + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/configurations/catalogs/infrastructure" + + "github.com/labstack/echo/v4" +) + +type CatalogsServiceConfigurator struct { + contracts.Application + infrastructureConfigurator *infrastructure.InfrastructureConfigurator + productsModuleConfigurator *configurations.ProductsModuleConfigurator +} + +func NewCatalogsServiceConfigurator(app contracts.Application) *CatalogsServiceConfigurator { + infraConfigurator := infrastructure.NewInfrastructureConfigurator(app) + productModuleConfigurator := configurations.NewProductsModuleConfigurator(app) + + return &CatalogsServiceConfigurator{ + Application: app, + infrastructureConfigurator: infraConfigurator, + productsModuleConfigurator: productModuleConfigurator, + } +} + +func (ic *CatalogsServiceConfigurator) ConfigureCatalogs() { + // Shared + // Infrastructure + ic.infrastructureConfigurator.ConfigInfrastructures() + + // Shared + // Catalogs configurations + + // Modules + // Product module + ic.productsModuleConfigurator.ConfigureProductsModule() +} + +func (ic *CatalogsServiceConfigurator) MapCatalogsEndpoints() { + // Shared + ic.ResolveFunc( + func(catalogsServer customEcho.EchoHttpServer, cfg *config.Config) error { + catalogsServer.SetupDefaultMiddlewares() + + // config catalogs root endpoint + catalogsServer.RouteBuilder(). + RegisterRoutes(func(e *echo.Echo) { + e.GET("", func(ec echo.Context) error { + return ec.String( + http.StatusOK, + fmt.Sprintf( + "%s is running...", + cfg.AppOptions.GetMicroserviceNameUpper(), + ), + ) + }) + }) + + // config catalogs swagger + ic.configSwagger(catalogsServer.RouteBuilder()) + + return nil + }, + ) + + // Modules + // Products CatalogsServiceModule endpoints + ic.productsModuleConfigurator.MapProductsEndpoints() +} diff --git a/internal/services/catalogreadservice/internal/shared/configurations/catalogs/catalogs_configurator_swagger.go b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/catalogs_configurator_swagger.go new file mode 100644 index 00000000..07e25417 --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/catalogs_configurator_swagger.go @@ -0,0 +1,20 @@ +package catalogs + +import ( + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/docs" + + "github.com/labstack/echo/v4" + echoSwagger "github.com/swaggo/echo-swagger" +) + +func (ic *CatalogsServiceConfigurator) configSwagger(routeBuilder *customEcho.RouteBuilder) { + // https://github.com/swaggo/swag#how-to-use-it-with-gin + docs.SwaggerInfo.Version = "1.0" + docs.SwaggerInfo.Title = "Catalogs Read-Service Api" + docs.SwaggerInfo.Description = "Catalogs Read-Service Api." + + routeBuilder.RegisterRoutes(func(e *echo.Echo) { + e.GET("/swagger/*", echoSwagger.WrapHandler) + }) +} diff --git a/internal/services/catalogreadservice/internal/shared/configurations/catalogs/catalogs_fx.go b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/catalogs_fx.go new file mode 100644 index 00000000..86b80240 --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/catalogs_fx.go @@ -0,0 +1,189 @@ +package catalogs + +import ( + "fmt" + + appconfig "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/configurations/catalogs/infrastructure" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/contracts" + + "go.opentelemetry.io/otel/metric" + api "go.opentelemetry.io/otel/metric" + "go.uber.org/fx" +) + +// https://pmihaylov.com/shared-components-go-microservices/ +var CatalogsServiceModule = fx.Module( + "catalogsfx", + // Shared Modules + appconfig.Module, + infrastructure.Module, + + // Features Modules + products.Module, + + // Other provides + fx.Provide(provideCatalogsMetrics), +) + +// ref: https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go +func provideCatalogsMetrics( + cfg *appconfig.Config, + meter metric.Meter, +) (*contracts.CatalogsMetrics, error) { + if meter == nil { + return nil, nil + } + + if meter == nil { + return nil, nil + } + appOptions := cfg.AppOptions + createProductGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_create_product_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of create product grpc requests"), + ) + if err != nil { + return nil, err + } + + updateProductGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_update_product_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of update product grpc requests"), + ) + if err != nil { + return nil, err + } + + deleteProductGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_delete_product_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of delete product grpc requests"), + ) + if err != nil { + return nil, err + } + + getProductByIdGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_get_product_by_id_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of get product by id grpc requests"), + ) + if err != nil { + return nil, err + } + + searchProductGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_search_product_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of search product grpc requests"), + ) + if err != nil { + return nil, err + } + + createProductRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf("%s_create_product_rabbitmq_messages_total", appOptions.ServiceName), + api.WithDescription("The total number of create product rabbirmq messages"), + ) + if err != nil { + return nil, err + } + + updateProductRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf("%s_update_product_rabbitmq_messages_total", appOptions.ServiceName), + api.WithDescription("The total number of update product rabbirmq messages"), + ) + if err != nil { + return nil, err + } + + deleteProductRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf("%s_delete_product_rabbitmq_messages_total", appOptions.ServiceName), + api.WithDescription("The total number of delete product rabbirmq messages"), + ) + if err != nil { + return nil, err + } + + successRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf("%s_search_product_rabbitmq_messages_total", appOptions.ServiceName), + api.WithDescription("The total number of success rabbitmq processed messages"), + ) + if err != nil { + return nil, err + } + + errorRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf("%s_error_rabbitmq_processed_messages_total", appOptions.ServiceName), + api.WithDescription("The total number of error rabbitmq processed messages"), + ) + if err != nil { + return nil, err + } + + createProductHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_create_product_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of create product http requests"), + ) + if err != nil { + return nil, err + } + + updateProductHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_update_product_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of update product http requests"), + ) + if err != nil { + return nil, err + } + + deleteProductHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_delete_product_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of delete product http requests"), + ) + if err != nil { + return nil, err + } + + getProductByIdHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_get_product_by_id_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of get product by id http requests"), + ) + if err != nil { + return nil, err + } + + getProductsHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_get_products_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of get products http requests"), + ) + if err != nil { + return nil, err + } + + searchProductHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_search_product_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of search product http requests"), + ) + if err != nil { + return nil, err + } + + return &contracts.CatalogsMetrics{ + CreateProductRabbitMQMessages: createProductRabbitMQMessages, + GetProductByIdGrpcRequests: getProductByIdGrpcRequests, + CreateProductGrpcRequests: createProductGrpcRequests, + CreateProductHttpRequests: createProductHttpRequests, + DeleteProductRabbitMQMessages: deleteProductRabbitMQMessages, + DeleteProductGrpcRequests: deleteProductGrpcRequests, + DeleteProductHttpRequests: deleteProductHttpRequests, + ErrorRabbitMQMessages: errorRabbitMQMessages, + GetProductByIdHttpRequests: getProductByIdHttpRequests, + GetProductsHttpRequests: getProductsHttpRequests, + SearchProductGrpcRequests: searchProductGrpcRequests, + SearchProductHttpRequests: searchProductHttpRequests, + SuccessRabbitMQMessages: successRabbitMQMessages, + UpdateProductRabbitMQMessages: updateProductRabbitMQMessages, + UpdateProductGrpcRequests: updateProductGrpcRequests, + UpdateProductHttpRequests: updateProductHttpRequests, + }, nil +} diff --git a/internal/services/catalogreadservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_configurator.go b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_configurator.go new file mode 100644 index 00000000..c55217be --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_configurator.go @@ -0,0 +1,48 @@ +package infrastructure + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + loggingpipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/pipelines" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics" + metricspipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics/mediatr/pipelines" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + tracingpipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/mediatr/pipelines" + postgrespipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/pipelines" + + "github.com/mehdihadeli/go-mediatr" + "gorm.io/gorm" +) + +type InfrastructureConfigurator struct { + contracts.Application +} + +func NewInfrastructureConfigurator( + app contracts.Application, +) *InfrastructureConfigurator { + return &InfrastructureConfigurator{ + Application: app, + } +} + +func (ic *InfrastructureConfigurator) ConfigInfrastructures() { + ic.ResolveFunc( + func(l logger.Logger, tracer tracing.AppTracer, metrics metrics.AppMetrics, db *gorm.DB) error { + err := mediatr.RegisterRequestPipelineBehaviors( + loggingpipelines.NewMediatorLoggingPipeline(l), + tracingpipelines.NewMediatorTracingPipeline( + tracer, + tracingpipelines.WithLogger(l), + ), + metricspipelines.NewMediatorMetricsPipeline( + metrics, + metricspipelines.WithLogger(l), + ), + postgrespipelines.NewMediatorTransactionPipeline(l, db), + ) + + return err + }, + ) +} diff --git a/internal/services/catalogreadservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_fx.go b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_fx.go new file mode 100644 index 00000000..693875b2 --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_fx.go @@ -0,0 +1,43 @@ +package infrastructure + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/redis" + rabbitmq2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/configurations/rabbitmq" + + "github.com/go-playground/validator" + "go.uber.org/fx" +) + +// https://pmihaylov.com/shared-components-go-microservices/ +var Module = fx.Module( + "infrastructurefx", + // Modules + core.Module, + customEcho.Module, + grpc.Module, + mongodb.Module, + redis.Module, + rabbitmq.ModuleFunc( + func(v *validator.Validate, l logger.Logger, tracer tracing.AppTracer) configurations.RabbitMQConfigurationBuilderFuc { + return func(builder configurations.RabbitMQConfigurationBuilder) { + rabbitmq2.ConfigProductsRabbitMQ(builder, l, v, tracer) + } + }, + ), + health.Module, + tracing.Module, + metrics.Module, + + // Other provides + fx.Provide(validator.New), +) diff --git a/internal/services/catalogreadservice/internal/shared/contracts/catalogs_metrics.go b/internal/services/catalogreadservice/internal/shared/contracts/catalogs_metrics.go new file mode 100644 index 00000000..7db30b63 --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/contracts/catalogs_metrics.go @@ -0,0 +1,18 @@ +package contracts + +import ( + "go.opentelemetry.io/otel/metric" +) + +type CatalogsMetrics struct { + CreateProductGrpcRequests metric.Float64Counter + UpdateProductGrpcRequests metric.Float64Counter + DeleteProductGrpcRequests metric.Float64Counter + GetProductByIdGrpcRequests metric.Float64Counter + SearchProductGrpcRequests metric.Float64Counter + SuccessRabbitMQMessages metric.Float64Counter + ErrorRabbitMQMessages metric.Float64Counter + CreateProductRabbitMQMessages metric.Float64Counter + UpdateProductRabbitMQMessages metric.Float64Counter + DeleteProductRabbitMQMessages metric.Float64Counter +} diff --git a/internal/services/catalogreadservice/internal/shared/testfixture/integration/integration_test_fixture.go b/internal/services/catalogreadservice/internal/shared/testfixture/integration/integration_test_fixture.go new file mode 100644 index 00000000..74341ed0 --- /dev/null +++ b/internal/services/catalogreadservice/internal/shared/testfixture/integration/integration_test_fixture.go @@ -0,0 +1,205 @@ +package integration + +import ( + "context" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/bus" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + config2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/contracts/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/app/test" + + "emperror.dev/errors" + "github.com/brianvoe/gofakeit/v6" + rabbithole "github.com/michaelklishin/rabbit-hole" + uuid "github.com/satori/go.uuid" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.opentelemetry.io/otel/trace" +) + +type IntegrationTestSharedFixture struct { + Cfg *config.Config + Log logger.Logger + Bus bus.Bus + ProductRepository data.ProductRepository + ProductCacheRepository data.ProductCacheRepository + Container contracts.Container + RabbitmqCleaner *rabbithole.Client + rabbitmqOptions *config2.RabbitmqOptions + MongoOptions *mongodb.MongoDbOptions + BaseAddress string + mongoClient *mongo.Client + Items []*models.Product + Tracer trace.Tracer +} + +func NewIntegrationTestSharedFixture( + t *testing.T, +) *IntegrationTestSharedFixture { + result := test.NewTestApp().Run(t) + + // https://github.com/michaelklishin/rabbit-hole + rmqc, err := rabbithole.NewClient( + result.RabbitmqOptions.RabbitmqHostOptions.HttpEndPoint(), + result.RabbitmqOptions.RabbitmqHostOptions.UserName, + result.RabbitmqOptions.RabbitmqHostOptions.Password) + if err != nil { + result.Logger.Error(errors.WrapIf(err, "error in creating rabbithole client")) + } + + shared := &IntegrationTestSharedFixture{ + Log: result.Logger, + Container: result.Container, + Cfg: result.Cfg, + RabbitmqCleaner: rmqc, + ProductRepository: result.ProductRepository, + ProductCacheRepository: result.ProductCacheRepository, + Bus: result.Bus, + rabbitmqOptions: result.RabbitmqOptions, + MongoOptions: result.MongoDbOptions, + BaseAddress: result.EchoHttpOptions.BasePathAddress(), + mongoClient: result.MongoClient, + Tracer: result.Tracer, + } + + return shared +} + +func (i *IntegrationTestSharedFixture) SetupTest() { + i.Log.Info("SetupTest started") + + // seed data in each test + res, err := seedData(i.mongoClient, i.MongoOptions.Database) + if err != nil { + i.Log.Error(errors.WrapIf(err, "error in seeding mongodb data")) + } + + i.Items = res +} + +func (i *IntegrationTestSharedFixture) TearDownTest() { + i.Log.Info("TearDownTest started") + + // cleanup test containers with their hooks + if err := i.cleanupRabbitmqData(); err != nil { + i.Log.Error(errors.WrapIf(err, "error in cleanup rabbitmq data")) + } + + if err := i.cleanupMongoData(); err != nil { + i.Log.Error(errors.WrapIf(err, "error in cleanup mongodb data")) + } +} + +func seedData( + db *mongo.Client, + databaseName string, +) ([]*models.Product, error) { + ctx := context.Background() + + products := []*models.Product{ + { + Id: uuid.NewV4().String(), + ProductId: uuid.NewV4().String(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + { + Id: uuid.NewV4().String(), + ProductId: uuid.NewV4().String(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + } + + //// https://go.dev/doc/faq#convert_slice_of_interface + productsData := make([]interface{}, len(products)) + + for i, v := range products { + productsData[i] = v + } + + collection := db.Database(databaseName).Collection("products") + _, err := collection.InsertMany( + context.Background(), + productsData, + &options.InsertManyOptions{}, + ) + if err != nil { + return nil, errors.WrapIf(err, "error in seed database") + } + + result, err := mongodb.Paginate[*models.Product]( + ctx, + utils.NewListQuery(10, 1), + collection, + nil, + ) + + return result.Items, nil +} + +func (i *IntegrationTestSharedFixture) cleanupRabbitmqData() error { + // https://github.com/michaelklishin/rabbit-hole + // Get all queues + queues, err := i.RabbitmqCleaner.ListQueuesIn( + i.rabbitmqOptions.RabbitmqHostOptions.VirtualHost, + ) + if err != nil { + return err + } + + // clear each queue + for _, queue := range queues { + _, err = i.RabbitmqCleaner.PurgeQueue( + i.rabbitmqOptions.RabbitmqHostOptions.VirtualHost, + queue.Name, + ) + + return err + } + + return nil +} + +func (i *IntegrationTestSharedFixture) cleanupMongoData() error { + collections := []string{"products"} + err := cleanupCollections( + i.mongoClient, + collections, + i.MongoOptions.Database, + ) + + return err +} + +func cleanupCollections( + db *mongo.Client, + collections []string, + databaseName string, +) error { + database := db.Database(databaseName) + ctx := context.Background() + + // Iterate over the collections and delete all collections + for _, collection := range collections { + collection := database.Collection(collection) + + err := collection.Drop(ctx) + if err != nil { + return err + } + } + return nil +} diff --git a/internal/services/catalogreadservice/mocks/ProductCacheRepository.go b/internal/services/catalogreadservice/mocks/ProductCacheRepository.go new file mode 100644 index 00000000..380a7c58 --- /dev/null +++ b/internal/services/catalogreadservice/mocks/ProductCacheRepository.go @@ -0,0 +1,222 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + models "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" +) + +// ProductCacheRepository is an autogenerated mock type for the ProductCacheRepository type +type ProductCacheRepository struct { + mock.Mock +} + +type ProductCacheRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *ProductCacheRepository) EXPECT() *ProductCacheRepository_Expecter { + return &ProductCacheRepository_Expecter{mock: &_m.Mock} +} + +// DeleteAllProducts provides a mock function with given fields: ctx +func (_m *ProductCacheRepository) DeleteAllProducts(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ProductCacheRepository_DeleteAllProducts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAllProducts' +type ProductCacheRepository_DeleteAllProducts_Call struct { + *mock.Call +} + +// DeleteAllProducts is a helper method to define mock.On call +// - ctx context.Context +func (_e *ProductCacheRepository_Expecter) DeleteAllProducts(ctx interface{}) *ProductCacheRepository_DeleteAllProducts_Call { + return &ProductCacheRepository_DeleteAllProducts_Call{Call: _e.mock.On("DeleteAllProducts", ctx)} +} + +func (_c *ProductCacheRepository_DeleteAllProducts_Call) Run(run func(ctx context.Context)) *ProductCacheRepository_DeleteAllProducts_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *ProductCacheRepository_DeleteAllProducts_Call) Return(_a0 error) *ProductCacheRepository_DeleteAllProducts_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProductCacheRepository_DeleteAllProducts_Call) RunAndReturn(run func(context.Context) error) *ProductCacheRepository_DeleteAllProducts_Call { + _c.Call.Return(run) + return _c +} + +// DeleteProduct provides a mock function with given fields: ctx, key +func (_m *ProductCacheRepository) DeleteProduct(ctx context.Context, key string) error { + ret := _m.Called(ctx, key) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ProductCacheRepository_DeleteProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteProduct' +type ProductCacheRepository_DeleteProduct_Call struct { + *mock.Call +} + +// DeleteProduct is a helper method to define mock.On call +// - ctx context.Context +// - key string +func (_e *ProductCacheRepository_Expecter) DeleteProduct(ctx interface{}, key interface{}) *ProductCacheRepository_DeleteProduct_Call { + return &ProductCacheRepository_DeleteProduct_Call{Call: _e.mock.On("DeleteProduct", ctx, key)} +} + +func (_c *ProductCacheRepository_DeleteProduct_Call) Run(run func(ctx context.Context, key string)) *ProductCacheRepository_DeleteProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProductCacheRepository_DeleteProduct_Call) Return(_a0 error) *ProductCacheRepository_DeleteProduct_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProductCacheRepository_DeleteProduct_Call) RunAndReturn(run func(context.Context, string) error) *ProductCacheRepository_DeleteProduct_Call { + _c.Call.Return(run) + return _c +} + +// GetProductById provides a mock function with given fields: ctx, key +func (_m *ProductCacheRepository) GetProductById(ctx context.Context, key string) (*models.Product, error) { + ret := _m.Called(ctx, key) + + var r0 *models.Product + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*models.Product, error)); ok { + return rf(ctx, key) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *models.Product); ok { + r0 = rf(ctx, key) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Product) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, key) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductCacheRepository_GetProductById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProductById' +type ProductCacheRepository_GetProductById_Call struct { + *mock.Call +} + +// GetProductById is a helper method to define mock.On call +// - ctx context.Context +// - key string +func (_e *ProductCacheRepository_Expecter) GetProductById(ctx interface{}, key interface{}) *ProductCacheRepository_GetProductById_Call { + return &ProductCacheRepository_GetProductById_Call{Call: _e.mock.On("GetProductById", ctx, key)} +} + +func (_c *ProductCacheRepository_GetProductById_Call) Run(run func(ctx context.Context, key string)) *ProductCacheRepository_GetProductById_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProductCacheRepository_GetProductById_Call) Return(_a0 *models.Product, _a1 error) *ProductCacheRepository_GetProductById_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductCacheRepository_GetProductById_Call) RunAndReturn(run func(context.Context, string) (*models.Product, error)) *ProductCacheRepository_GetProductById_Call { + _c.Call.Return(run) + return _c +} + +// PutProduct provides a mock function with given fields: ctx, key, product +func (_m *ProductCacheRepository) PutProduct(ctx context.Context, key string, product *models.Product) error { + ret := _m.Called(ctx, key, product) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, *models.Product) error); ok { + r0 = rf(ctx, key, product) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ProductCacheRepository_PutProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PutProduct' +type ProductCacheRepository_PutProduct_Call struct { + *mock.Call +} + +// PutProduct is a helper method to define mock.On call +// - ctx context.Context +// - key string +// - product *models.Product +func (_e *ProductCacheRepository_Expecter) PutProduct(ctx interface{}, key interface{}, product interface{}) *ProductCacheRepository_PutProduct_Call { + return &ProductCacheRepository_PutProduct_Call{Call: _e.mock.On("PutProduct", ctx, key, product)} +} + +func (_c *ProductCacheRepository_PutProduct_Call) Run(run func(ctx context.Context, key string, product *models.Product)) *ProductCacheRepository_PutProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*models.Product)) + }) + return _c +} + +func (_c *ProductCacheRepository_PutProduct_Call) Return(_a0 error) *ProductCacheRepository_PutProduct_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProductCacheRepository_PutProduct_Call) RunAndReturn(run func(context.Context, string, *models.Product) error) *ProductCacheRepository_PutProduct_Call { + _c.Call.Return(run) + return _c +} + +// NewProductCacheRepository creates a new instance of ProductCacheRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProductCacheRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *ProductCacheRepository { + mock := &ProductCacheRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/catalogreadservice/mocks/ProductRepository.go b/internal/services/catalogreadservice/mocks/ProductRepository.go new file mode 100644 index 00000000..b1b83a41 --- /dev/null +++ b/internal/services/catalogreadservice/mocks/ProductRepository.go @@ -0,0 +1,414 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + models "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" + + utils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" +) + +// ProductRepository is an autogenerated mock type for the ProductRepository type +type ProductRepository struct { + mock.Mock +} + +type ProductRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *ProductRepository) EXPECT() *ProductRepository_Expecter { + return &ProductRepository_Expecter{mock: &_m.Mock} +} + +// CreateProduct provides a mock function with given fields: ctx, product +func (_m *ProductRepository) CreateProduct(ctx context.Context, product *models.Product) (*models.Product, error) { + ret := _m.Called(ctx, product) + + var r0 *models.Product + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *models.Product) (*models.Product, error)); ok { + return rf(ctx, product) + } + if rf, ok := ret.Get(0).(func(context.Context, *models.Product) *models.Product); ok { + r0 = rf(ctx, product) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Product) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *models.Product) error); ok { + r1 = rf(ctx, product) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_CreateProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateProduct' +type ProductRepository_CreateProduct_Call struct { + *mock.Call +} + +// CreateProduct is a helper method to define mock.On call +// - ctx context.Context +// - product *models.Product +func (_e *ProductRepository_Expecter) CreateProduct(ctx interface{}, product interface{}) *ProductRepository_CreateProduct_Call { + return &ProductRepository_CreateProduct_Call{Call: _e.mock.On("CreateProduct", ctx, product)} +} + +func (_c *ProductRepository_CreateProduct_Call) Run(run func(ctx context.Context, product *models.Product)) *ProductRepository_CreateProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*models.Product)) + }) + return _c +} + +func (_c *ProductRepository_CreateProduct_Call) Return(_a0 *models.Product, _a1 error) *ProductRepository_CreateProduct_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_CreateProduct_Call) RunAndReturn(run func(context.Context, *models.Product) (*models.Product, error)) *ProductRepository_CreateProduct_Call { + _c.Call.Return(run) + return _c +} + +// DeleteProductByID provides a mock function with given fields: ctx, uuid +func (_m *ProductRepository) DeleteProductByID(ctx context.Context, uuid string) error { + ret := _m.Called(ctx, uuid) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, uuid) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ProductRepository_DeleteProductByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteProductByID' +type ProductRepository_DeleteProductByID_Call struct { + *mock.Call +} + +// DeleteProductByID is a helper method to define mock.On call +// - ctx context.Context +// - uuid string +func (_e *ProductRepository_Expecter) DeleteProductByID(ctx interface{}, uuid interface{}) *ProductRepository_DeleteProductByID_Call { + return &ProductRepository_DeleteProductByID_Call{Call: _e.mock.On("DeleteProductByID", ctx, uuid)} +} + +func (_c *ProductRepository_DeleteProductByID_Call) Run(run func(ctx context.Context, uuid string)) *ProductRepository_DeleteProductByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProductRepository_DeleteProductByID_Call) Return(_a0 error) *ProductRepository_DeleteProductByID_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProductRepository_DeleteProductByID_Call) RunAndReturn(run func(context.Context, string) error) *ProductRepository_DeleteProductByID_Call { + _c.Call.Return(run) + return _c +} + +// GetAllProducts provides a mock function with given fields: ctx, listQuery +func (_m *ProductRepository) GetAllProducts(ctx context.Context, listQuery *utils.ListQuery) (*utils.ListResult[*models.Product], error) { + ret := _m.Called(ctx, listQuery) + + var r0 *utils.ListResult[*models.Product] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) (*utils.ListResult[*models.Product], error)); ok { + return rf(ctx, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) *utils.ListResult[*models.Product]); ok { + r0 = rf(ctx, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*models.Product]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *utils.ListQuery) error); ok { + r1 = rf(ctx, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_GetAllProducts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllProducts' +type ProductRepository_GetAllProducts_Call struct { + *mock.Call +} + +// GetAllProducts is a helper method to define mock.On call +// - ctx context.Context +// - listQuery *utils.ListQuery +func (_e *ProductRepository_Expecter) GetAllProducts(ctx interface{}, listQuery interface{}) *ProductRepository_GetAllProducts_Call { + return &ProductRepository_GetAllProducts_Call{Call: _e.mock.On("GetAllProducts", ctx, listQuery)} +} + +func (_c *ProductRepository_GetAllProducts_Call) Run(run func(ctx context.Context, listQuery *utils.ListQuery)) *ProductRepository_GetAllProducts_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*utils.ListQuery)) + }) + return _c +} + +func (_c *ProductRepository_GetAllProducts_Call) Return(_a0 *utils.ListResult[*models.Product], _a1 error) *ProductRepository_GetAllProducts_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_GetAllProducts_Call) RunAndReturn(run func(context.Context, *utils.ListQuery) (*utils.ListResult[*models.Product], error)) *ProductRepository_GetAllProducts_Call { + _c.Call.Return(run) + return _c +} + +// GetProductById provides a mock function with given fields: ctx, uuid +func (_m *ProductRepository) GetProductById(ctx context.Context, uuid string) (*models.Product, error) { + ret := _m.Called(ctx, uuid) + + var r0 *models.Product + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*models.Product, error)); ok { + return rf(ctx, uuid) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *models.Product); ok { + r0 = rf(ctx, uuid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Product) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, uuid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_GetProductById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProductById' +type ProductRepository_GetProductById_Call struct { + *mock.Call +} + +// GetProductById is a helper method to define mock.On call +// - ctx context.Context +// - uuid string +func (_e *ProductRepository_Expecter) GetProductById(ctx interface{}, uuid interface{}) *ProductRepository_GetProductById_Call { + return &ProductRepository_GetProductById_Call{Call: _e.mock.On("GetProductById", ctx, uuid)} +} + +func (_c *ProductRepository_GetProductById_Call) Run(run func(ctx context.Context, uuid string)) *ProductRepository_GetProductById_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProductRepository_GetProductById_Call) Return(_a0 *models.Product, _a1 error) *ProductRepository_GetProductById_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_GetProductById_Call) RunAndReturn(run func(context.Context, string) (*models.Product, error)) *ProductRepository_GetProductById_Call { + _c.Call.Return(run) + return _c +} + +// GetProductByProductId provides a mock function with given fields: ctx, uuid +func (_m *ProductRepository) GetProductByProductId(ctx context.Context, uuid string) (*models.Product, error) { + ret := _m.Called(ctx, uuid) + + var r0 *models.Product + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*models.Product, error)); ok { + return rf(ctx, uuid) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *models.Product); ok { + r0 = rf(ctx, uuid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Product) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, uuid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_GetProductByProductId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProductByProductId' +type ProductRepository_GetProductByProductId_Call struct { + *mock.Call +} + +// GetProductByProductId is a helper method to define mock.On call +// - ctx context.Context +// - uuid string +func (_e *ProductRepository_Expecter) GetProductByProductId(ctx interface{}, uuid interface{}) *ProductRepository_GetProductByProductId_Call { + return &ProductRepository_GetProductByProductId_Call{Call: _e.mock.On("GetProductByProductId", ctx, uuid)} +} + +func (_c *ProductRepository_GetProductByProductId_Call) Run(run func(ctx context.Context, uuid string)) *ProductRepository_GetProductByProductId_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ProductRepository_GetProductByProductId_Call) Return(_a0 *models.Product, _a1 error) *ProductRepository_GetProductByProductId_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_GetProductByProductId_Call) RunAndReturn(run func(context.Context, string) (*models.Product, error)) *ProductRepository_GetProductByProductId_Call { + _c.Call.Return(run) + return _c +} + +// SearchProducts provides a mock function with given fields: ctx, searchText, listQuery +func (_m *ProductRepository) SearchProducts(ctx context.Context, searchText string, listQuery *utils.ListQuery) (*utils.ListResult[*models.Product], error) { + ret := _m.Called(ctx, searchText, listQuery) + + var r0 *utils.ListResult[*models.Product] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*models.Product], error)); ok { + return rf(ctx, searchText, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) *utils.ListResult[*models.Product]); ok { + r0 = rf(ctx, searchText, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*models.Product]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *utils.ListQuery) error); ok { + r1 = rf(ctx, searchText, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_SearchProducts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SearchProducts' +type ProductRepository_SearchProducts_Call struct { + *mock.Call +} + +// SearchProducts is a helper method to define mock.On call +// - ctx context.Context +// - searchText string +// - listQuery *utils.ListQuery +func (_e *ProductRepository_Expecter) SearchProducts(ctx interface{}, searchText interface{}, listQuery interface{}) *ProductRepository_SearchProducts_Call { + return &ProductRepository_SearchProducts_Call{Call: _e.mock.On("SearchProducts", ctx, searchText, listQuery)} +} + +func (_c *ProductRepository_SearchProducts_Call) Run(run func(ctx context.Context, searchText string, listQuery *utils.ListQuery)) *ProductRepository_SearchProducts_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*utils.ListQuery)) + }) + return _c +} + +func (_c *ProductRepository_SearchProducts_Call) Return(_a0 *utils.ListResult[*models.Product], _a1 error) *ProductRepository_SearchProducts_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_SearchProducts_Call) RunAndReturn(run func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*models.Product], error)) *ProductRepository_SearchProducts_Call { + _c.Call.Return(run) + return _c +} + +// UpdateProduct provides a mock function with given fields: ctx, product +func (_m *ProductRepository) UpdateProduct(ctx context.Context, product *models.Product) (*models.Product, error) { + ret := _m.Called(ctx, product) + + var r0 *models.Product + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *models.Product) (*models.Product, error)); ok { + return rf(ctx, product) + } + if rf, ok := ret.Get(0).(func(context.Context, *models.Product) *models.Product); ok { + r0 = rf(ctx, product) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Product) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *models.Product) error); ok { + r1 = rf(ctx, product) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_UpdateProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateProduct' +type ProductRepository_UpdateProduct_Call struct { + *mock.Call +} + +// UpdateProduct is a helper method to define mock.On call +// - ctx context.Context +// - product *models.Product +func (_e *ProductRepository_Expecter) UpdateProduct(ctx interface{}, product interface{}) *ProductRepository_UpdateProduct_Call { + return &ProductRepository_UpdateProduct_Call{Call: _e.mock.On("UpdateProduct", ctx, product)} +} + +func (_c *ProductRepository_UpdateProduct_Call) Run(run func(ctx context.Context, product *models.Product)) *ProductRepository_UpdateProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*models.Product)) + }) + return _c +} + +func (_c *ProductRepository_UpdateProduct_Call) Return(_a0 *models.Product, _a1 error) *ProductRepository_UpdateProduct_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_UpdateProduct_Call) RunAndReturn(run func(context.Context, *models.Product) (*models.Product, error)) *ProductRepository_UpdateProduct_Call { + _c.Call.Return(run) + return _c +} + +// NewProductRepository creates a new instance of ProductRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProductRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *ProductRepository { + mock := &ProductRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/catalogreadservice/mocks/UnsafeProductsServiceServer.go b/internal/services/catalogreadservice/mocks/UnsafeProductsServiceServer.go new file mode 100644 index 00000000..33f9a17d --- /dev/null +++ b/internal/services/catalogreadservice/mocks/UnsafeProductsServiceServer.go @@ -0,0 +1,64 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// UnsafeProductsServiceServer is an autogenerated mock type for the UnsafeProductsServiceServer type +type UnsafeProductsServiceServer struct { + mock.Mock +} + +type UnsafeProductsServiceServer_Expecter struct { + mock *mock.Mock +} + +func (_m *UnsafeProductsServiceServer) EXPECT() *UnsafeProductsServiceServer_Expecter { + return &UnsafeProductsServiceServer_Expecter{mock: &_m.Mock} +} + +// mustEmbedUnimplementedProductsServiceServer provides a mock function with given fields: +func (_m *UnsafeProductsServiceServer) mustEmbedUnimplementedProductsServiceServer() { + _m.Called() +} + +// UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'mustEmbedUnimplementedProductsServiceServer' +type UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call struct { + *mock.Call +} + +// mustEmbedUnimplementedProductsServiceServer is a helper method to define mock.On call +func (_e *UnsafeProductsServiceServer_Expecter) mustEmbedUnimplementedProductsServiceServer() *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call { + return &UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call{Call: _e.mock.On("mustEmbedUnimplementedProductsServiceServer")} +} + +func (_c *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call) Run(run func()) *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call) Return() *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call { + _c.Call.Return() + return _c +} + +func (_c *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call) RunAndReturn(run func()) *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call { + _c.Call.Return(run) + return _c +} + +// NewUnsafeProductsServiceServer creates a new instance of UnsafeProductsServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewUnsafeProductsServiceServer(t interface { + mock.TestingT + Cleanup(func()) +}) *UnsafeProductsServiceServer { + mock := &UnsafeProductsServiceServer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/catalogreadservice/readme.md b/internal/services/catalogreadservice/readme.md new file mode 100644 index 00000000..73af95fb --- /dev/null +++ b/internal/services/catalogreadservice/readme.md @@ -0,0 +1,6 @@ +# Catalogs Read Service + +## Project Layout and Structure + +Based on these projects structure: +- [Standard Go Project Layout](https://github.com/golang-standards/project-layout) diff --git a/internal/services/catalogreadservice/revive-config.toml b/internal/services/catalogreadservice/revive-config.toml new file mode 100644 index 00000000..f9e2405d --- /dev/null +++ b/internal/services/catalogreadservice/revive-config.toml @@ -0,0 +1,30 @@ +ignoreGeneratedHeader = false +severity = "warning" +confidence = 0.8 +errorCode = 0 +warningCode = 0 + +[rule.blank-imports] +[rule.context-as-argument] +[rule.context-keys-type] +[rule.dot-imports] +[rule.error-return] +[rule.error-strings] +[rule.error-naming] +[rule.exported] +[rule.if-return] +[rule.increment-decrement] +[rule.var-naming] +[rule.var-declaration] +[rule.package-comments] +[rule.range] +[rule.receiver-naming] +[rule.time-naming] +[rule.unexported-return] +[rule.indent-error-flow] +[rule.errorf] +[rule.empty-block] +[rule.superfluous-else] +[rule.unused-parameter] +[rule.unreachable-code] +[rule.redefines-builtin-id] diff --git a/internal/services/catalogreadservice/staticcheck.conf b/internal/services/catalogreadservice/staticcheck.conf new file mode 100644 index 00000000..33023e1a --- /dev/null +++ b/internal/services/catalogreadservice/staticcheck.conf @@ -0,0 +1 @@ +checks = ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023"] diff --git a/internal/services/catalogreadservice/taskfile.yml b/internal/services/catalogreadservice/taskfile.yml new file mode 100644 index 00000000..ed72a03d --- /dev/null +++ b/internal/services/catalogreadservice/taskfile.yml @@ -0,0 +1,3 @@ +#https://taskfile.dev/#/installation + +version: "3" diff --git a/internal/services/catalogreadservice/taskfile_db.yml b/internal/services/catalogreadservice/taskfile_db.yml new file mode 100644 index 00000000..ed72a03d --- /dev/null +++ b/internal/services/catalogreadservice/taskfile_db.yml @@ -0,0 +1,3 @@ +#https://taskfile.dev/#/installation + +version: "3" diff --git a/internal/services/catalogreadservice/taskfile_test.yml b/internal/services/catalogreadservice/taskfile_test.yml new file mode 100644 index 00000000..e93f7ac0 --- /dev/null +++ b/internal/services/catalogreadservice/taskfile_test.yml @@ -0,0 +1,23 @@ +#https://taskfile.dev/#/installation + +version: "3" +tasks: + mock: + desc: Generate interfaces mocks + cmds: + - mockery --output mocks --all + + integration: + desc: Run integration tests + cmds: + - go test -v -tags=integration ./... + + e2e: + desc: Run integration tests + cmds: + - go test -v -tags=e2e ./... + + unit: + desc: Run unit tests + cmds: + - go test -v -tags=unit ./... diff --git a/internal/services/catalogreadservice/test/end_to_end/products/features/getting_product_by_id/v1/get_product_by_id_test.go b/internal/services/catalogreadservice/test/end_to_end/products/features/getting_product_by_id/v1/get_product_by_id_test.go new file mode 100644 index 00000000..58af7419 --- /dev/null +++ b/internal/services/catalogreadservice/test/end_to_end/products/features/getting_product_by_id/v1/get_product_by_id_test.go @@ -0,0 +1,44 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/gavv/httpexpect/v2" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestGetProductById(t *testing.T) { + e2eFixture := integration.NewIntegrationTestSharedFixture(t) + + Convey("Get Product By Id Feature", t, func() { + e2eFixture.SetupTest() + + ctx := context.Background() + id := e2eFixture.Items[0].Id + + // "Scenario" step for testing the get product by ID API with a valid ID + Convey("Get product by ID with a valid ID returns ok status", func() { + Convey("When A valid request is made with a valid ID", func() { + expect := httpexpect.New(t, e2eFixture.BaseAddress) + + Convey("Then the response status should be OK", func() { + expect.GET("products/{id}"). + WithPath("id", id). + WithContext(ctx). + Expect(). + Status(http.StatusOK) + }) + }) + }) + + e2eFixture.TearDownTest() + }) +} diff --git a/internal/services/catalogreadservice/test/end_to_end/products/features/getting_products/v1/get_products_endpoint_test.go b/internal/services/catalogreadservice/test/end_to_end/products/features/getting_products/v1/get_products_endpoint_test.go new file mode 100644 index 00000000..7212b82b --- /dev/null +++ b/internal/services/catalogreadservice/test/end_to_end/products/features/getting_products/v1/get_products_endpoint_test.go @@ -0,0 +1,40 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/gavv/httpexpect/v2" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestGetAllProducts(t *testing.T) { + e2eFixture := integration.NewIntegrationTestSharedFixture(t) + + Convey("Get All Products Feature", t, func() { + e2eFixture.SetupTest() + ctx := context.Background() + + Convey("Get all products returns ok status", func() { + Convey("When a request is made to get all products", func() { + expect := httpexpect.New(t, e2eFixture.BaseAddress) + + Convey("Then the response status should be OK", func() { + expect.GET("products"). + WithContext(ctx). + Expect(). + Status(http.StatusOK) + }) + }) + }) + + e2eFixture.TearDownTest() + }) +} diff --git a/internal/services/catalogreadservice/test/integration/products/data/mongo_product_repository_test.go b/internal/services/catalogreadservice/test/integration/products/data/mongo_product_repository_test.go new file mode 100644 index 00000000..49808702 --- /dev/null +++ b/internal/services/catalogreadservice/test/integration/products/data/mongo_product_repository_test.go @@ -0,0 +1,170 @@ +//go:build integration +// +build integration + +package data + +import ( + "context" + "testing" + "time" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestProductPostgresRepository(t *testing.T) { + integrationTestSharedFixture := integration.NewIntegrationTestSharedFixture(t) + + // scenario + Convey("Product Repository", t, func() { + integrationTestSharedFixture.SetupTest() + + Convey("When we create the new product in the database", func() { + ctx := context.Background() + product := &models.Product{ + Id: uuid.NewV4().String(), + ProductId: uuid.NewV4().String(), + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + CreatedAt: time.Now(), + } + + createdProduct, err := integrationTestSharedFixture.ProductRepository.CreateProduct(ctx, product) + + Convey("Then the product should be created successfully", func() { + // Assert that there is no error during creation. + So(err, ShouldBeNil) + + Convey("And we should be able to retrieve the product by ID", func() { + retrievedProduct, err := integrationTestSharedFixture.ProductRepository.GetProductById( + ctx, + createdProduct.Id, + ) + + Convey("And retrieved product should match the created product", func() { + // Assert that there is no error during retrieval. + So(err, ShouldBeNil) + + // Assert that the retrieved product matches the created product. + So(retrievedProduct.Id, ShouldEqual, createdProduct.Id) + }) + }) + }) + }) + + Convey("When we delete the existing product", func() { + ctx := context.Background() + + id := integrationTestSharedFixture.Items[0].Id + err := integrationTestSharedFixture.ProductRepository.DeleteProductByID(ctx, id) + + Convey("Then the product should be deleted successfully", func() { + // Ensure there is no error during deletion. + So(err, ShouldBeNil) + + Convey("And when we attempt to retrieve the product by ID", func() { + product, err := integrationTestSharedFixture.ProductRepository.GetProductById(ctx, id) + + Convey("And error should occur indicating the product is not found", func() { + // Verify that there is an error. + So(err, ShouldNotBeNil) + + // Check if the error is of a specific type (e.g., a not found error). + So(customErrors.IsNotFoundError(err), ShouldBeTrue) + + // Verify that the retrieved product is nil. + So(product, ShouldBeNil) + }) + }) + }) + }) + + Convey("When we update the existing product", func() { + Convey("Then the product should be updated successfully", func() { + ctx := context.Background() + + id := integrationTestSharedFixture.Items[0].Id + existingProduct, err := integrationTestSharedFixture.ProductRepository.GetProductById(ctx, id) + + // Make sure the existing product exists and there is no error. + So(err, ShouldBeNil) + So(existingProduct, ShouldNotBeNil) + + // Modify the existing product's name. + existingProduct.Name = "test_update_product" + + // Update the product in the database. + _, err = integrationTestSharedFixture.ProductRepository.UpdateProduct(ctx, existingProduct) + + // Ensure there is no error during the update. + So(err, ShouldBeNil) + + // Retrieve the updated product from the database. + updatedProduct, err := integrationTestSharedFixture.ProductRepository.GetProductById(ctx, id) + So(err, ShouldBeNil) + + // Verify that the updated product's name matches the new name. + So(updatedProduct.Name, ShouldEqual, "test_update_product") + }) + }) + + Convey("When attempting to get a product that does not exist", func() { + ctx := context.Background() + + res, err := integrationTestSharedFixture.ProductRepository.GetProductById(ctx, uuid.NewV4().String()) + + Convey("Then it should return a NotFound error and nil result", func() { + // Verify that there is an error. + So(err, ShouldNotBeNil) + + // Check if the error is of a specific type (e.g., a not found error). + So(customErrors.IsNotFoundError(err), ShouldBeTrue) + + // Verify that the retrieved result is nil. + So(res, ShouldBeNil) + }) + }) + + Convey("When attempting to get an existing product from the database", func() { + ctx := context.Background() + + id := integrationTestSharedFixture.Items[0].Id + res, err := integrationTestSharedFixture.ProductRepository.GetProductById(ctx, id) + + Convey("Then it should return the product and no error", func() { + // Ensure there is no error. + So(err, ShouldBeNil) + + // Verify that the result is not nil. + So(res, ShouldNotBeNil) + + // Verify that the retrieved product's ID matches the expected ID. + So(res.Id, ShouldEqual, id) + }) + }) + + Convey("When attempting to get all existing products from the database", func() { + ctx := context.Background() + + res, err := integrationTestSharedFixture.ProductRepository.GetAllProducts(ctx, utils.NewListQuery(10, 1)) + + Convey("Then it should return the list of products and no error", func() { + // Ensure there is no error. + So(err, ShouldBeNil) + + // Verify the expected number of products in the list. + So(len(res.Items), ShouldEqual, 2) + }) + }) + + integrationTestSharedFixture.TearDownTest() + }) +} diff --git a/internal/services/catalogreadservice/test/integration/products/features/creating_product/v1/commands/create_product_test.go b/internal/services/catalogreadservice/test/integration/products/features/creating_product/v1/commands/create_product_test.go new file mode 100644 index 00000000..0a202e00 --- /dev/null +++ b/internal/services/catalogreadservice/test/integration/products/features/creating_product/v1/commands/create_product_test.go @@ -0,0 +1,83 @@ +//go:build integration +// +build integration + +package commands + +import ( + "context" + "testing" + "time" + + v1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/creating_product/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/creating_product/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/brianvoe/gofakeit/v6" + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestCreateProduct(t *testing.T) { + integrationTestSharedFixture := integration.NewIntegrationTestSharedFixture(t) + + Convey("Creating Product Feature", t, func() { + ctx := context.Background() + integrationTestSharedFixture.SetupTest() + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey( + "Creating a new product and saving it to the database for a none-existing product", + func() { + Convey("Given new product doesn't exists in the system", func() { + command, err := v1.NewCreateProduct( + uuid.NewV4().String(), + gofakeit.Name(), + gofakeit.AdjectiveDescriptive(), + gofakeit.Price(150, 6000), + time.Now(), + ) + So(err, ShouldBeNil) + + Convey( + "When the CreateProduct command is executed and product doesn't exists", + func() { + result, err := mediatr.Send[*v1.CreateProduct, *dtos.CreateProductResponseDto]( + ctx, + command, + ) + + Convey("Then the product should be created successfully", func() { + So(err, ShouldBeNil) + So(result, ShouldNotBeNil) + + Convey( + "And the product ID should not be empty and same as commandId", + func() { + So(result.Id, ShouldEqual, command.Id) + + Convey( + "And product detail should be retrievable from the database", + func() { + createdProduct, err := integrationTestSharedFixture.ProductRepository.GetProductById( + ctx, + result.Id, + ) + So(err, ShouldBeNil) + So(createdProduct, ShouldNotBeNil) + }, + ) + }, + ) + }) + }, + ) + }) + }, + ) + + integrationTestSharedFixture.TearDownTest() + }) +} diff --git a/internal/services/catalogreadservice/test/integration/products/features/creating_product/v1/events/product_created_test.go b/internal/services/catalogreadservice/test/integration/products/features/creating_product/v1/events/product_created_test.go new file mode 100644 index 00000000..40350a5a --- /dev/null +++ b/internal/services/catalogreadservice/test/integration/products/features/creating_product/v1/events/product_created_test.go @@ -0,0 +1,116 @@ +//go:build integration +// +build integration + +package events + +// https://github.com/smartystreets/goconvey/wiki + +import ( + "context" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" + externalEvents "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/creating_product/v1/events/integrationevents/externalevents" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestProductCreatedConsumer(t *testing.T) { + // Setup and initialization code here. + integrationTestSharedFixture := integration.NewIntegrationTestSharedFixture(t) + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + integrationTestSharedFixture.Bus.Start(context.Background()) + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + + Convey("Product Created Feature", t, func() { + // will execute with each subtest + integrationTestSharedFixture.SetupTest() + ctx := context.Background() + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey("Consume ProductCreated event by consumer", func() { + fakeProduct := &externalEvents.ProductCreatedV1{ + Message: types.NewMessage(uuid.NewV4().String()), + ProductId: uuid.NewV4().String(), + Name: gofakeit.FirstName(), + Price: gofakeit.Price(150, 6000), + CreatedAt: time.Now(), + Description: gofakeit.EmojiDescription(), + } + hypothesis := messaging.ShouldConsume[*externalEvents.ProductCreatedV1]( + ctx, + integrationTestSharedFixture.Bus, + nil, + ) + + Convey("When a ProductCreated event consumed", func() { + err := integrationTestSharedFixture.Bus.PublishMessage(ctx, fakeProduct, nil) + So(err, ShouldBeNil) + + Convey("Then it should consume the ProductCreated event", func() { + hypothesis.Validate(ctx, "there is no consumed message", 30*time.Second) + }) + }) + }) + + Convey("Create product in mongo database when a ProductCreated event consumed", func() { + fakeProduct := &externalEvents.ProductCreatedV1{ + Message: types.NewMessage(uuid.NewV4().String()), + ProductId: uuid.NewV4().String(), + Name: gofakeit.FirstName(), + Price: gofakeit.Price(150, 6000), + CreatedAt: time.Now(), + Description: gofakeit.EmojiDescription(), + } + + Convey("When a ProductCreated event consumed", func() { + err := integrationTestSharedFixture.Bus.PublishMessage(ctx, fakeProduct, nil) + So(err, ShouldBeNil) + + Convey("It should store product in the mongo database", func() { + ctx := context.Background() + pid := uuid.NewV4().String() + productCreated := &externalEvents.ProductCreatedV1{ + Message: types.NewMessage(uuid.NewV4().String()), + ProductId: pid, + CreatedAt: time.Now(), + Name: gofakeit.Name(), + Price: gofakeit.Price(150, 6000), + Description: gofakeit.AdjectiveDescriptive(), + } + + err := integrationTestSharedFixture.Bus.PublishMessage(ctx, productCreated, nil) + So(err, ShouldBeNil) + + var product *models.Product + + err = testUtils.WaitUntilConditionMet(func() bool { + product, err = integrationTestSharedFixture.ProductRepository.GetProductByProductId(ctx, pid) + + return err == nil && product != nil + }) + + So(err, ShouldBeNil) + So(product, ShouldNotBeNil) + So(product.ProductId, ShouldEqual, pid) + }) + }) + }) + + integrationTestSharedFixture.TearDownTest() + }) + + integrationTestSharedFixture.Log.Info("TearDownSuite started") + integrationTestSharedFixture.Bus.Stop() + time.Sleep(1 * time.Second) +} diff --git a/internal/services/catalogreadservice/test/integration/products/features/deleting_product/v1/commands/delete_product_test.go b/internal/services/catalogreadservice/test/integration/products/features/deleting_product/v1/commands/delete_product_test.go new file mode 100644 index 00000000..8c5ef154 --- /dev/null +++ b/internal/services/catalogreadservice/test/integration/products/features/deleting_product/v1/commands/delete_product_test.go @@ -0,0 +1,70 @@ +//go:build integration +// +build integration + +package commands + +import ( + "context" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/commands" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestDeleteProduct(t *testing.T) { + integrationTestSharedFixture := integration.NewIntegrationTestSharedFixture( + t, + ) + + Convey("Deleting Product Feature", t, func() { + ctx := context.Background() + integrationTestSharedFixture.SetupTest() + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey("Deleting an existing product from the database", func() { + Convey("Given an existing product in the mongo database", func() { + productId, err := uuid.FromString( + integrationTestSharedFixture.Items[0].ProductId, + ) + So(err, ShouldBeNil) + + command, err := commands.NewDeleteProduct(productId) + So(err, ShouldBeNil) + + Convey("When we execute the DeleteProduct command", func() { + result, err := mediatr.Send[*commands.DeleteProduct, *mediatr.Unit]( + context.Background(), + command, + ) + + Convey( + "Then the product should be deleted successfully in mongo database", + func() { + So(err, ShouldBeNil) + So(result, ShouldNotBeNil) + + Convey( + "And the product should no longer exist in the system", + func() { + deletedProduct, _ := integrationTestSharedFixture.ProductRepository.GetProductByProductId( + ctx, + productId.String(), + ) + So(deletedProduct, ShouldBeNil) + }, + ) + }, + ) + }) + }) + }) + + integrationTestSharedFixture.TearDownTest() + }) +} diff --git a/internal/services/catalogreadservice/test/integration/products/features/deleting_product/v1/events/product_deleted_test.go b/internal/services/catalogreadservice/test/integration/products/features/deleting_product/v1/events/product_deleted_test.go new file mode 100644 index 00000000..2072aca4 --- /dev/null +++ b/internal/services/catalogreadservice/test/integration/products/features/deleting_product/v1/events/product_deleted_test.go @@ -0,0 +1,114 @@ +//go:build integration +// +build integration + +package events + +import ( + "context" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" + externalEvents "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/deleting_products/v1/events/integration_events/external_events" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + uuid "github.com/satori/go.uuid" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestProductDeleted(t *testing.T) { + // Setup and initialization code here. + integrationTestSharedFixture := integration.NewIntegrationTestSharedFixture(t) + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + integrationTestSharedFixture.Bus.Start(context.Background()) + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + + Convey("Product Deleted Feature", t, func() { + ctx := context.Background() + // will execute with each subtest + integrationTestSharedFixture.SetupTest() + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey("Consume ProductDeleted event by consumer", func() { + event := &externalEvents.ProductDeletedV1{ + Message: types.NewMessage(uuid.NewV4().String()), + ProductId: integrationTestSharedFixture.Items[0].ProductId, + } + // check for consuming `ProductDeletedV1` message with existing consumer + hypothesis := messaging.ShouldConsume[*externalEvents.ProductDeletedV1]( + ctx, + integrationTestSharedFixture.Bus, + nil, + ) + + Convey("When a ProductDeleted event consumed", func() { + err := integrationTestSharedFixture.Bus.PublishMessage( + ctx, + event, + nil, + ) + So(err, ShouldBeNil) + + Convey("Then it should consume the ProductDeleted event", func() { + hypothesis.Validate(ctx, "there is no consumed message", 30*time.Second) + }) + }) + }) + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey("Delete product in mongo database when a ProductDeleted event consumed", func() { + event := &externalEvents.ProductDeletedV1{ + Message: types.NewMessage(uuid.NewV4().String()), + ProductId: integrationTestSharedFixture.Items[0].ProductId, + } + + Convey("When a ProductDeleted event consumed", func() { + err := integrationTestSharedFixture.Bus.PublishMessage( + ctx, + event, + nil, + ) + So(err, ShouldBeNil) + + Convey("It should delete product in the mongo database", func() { + ctx := context.Background() + + productDeleted := &externalEvents.ProductDeletedV1{ + Message: types.NewMessage(uuid.NewV4().String()), + ProductId: integrationTestSharedFixture.Items[0].ProductId, + } + + err := integrationTestSharedFixture.Bus.PublishMessage(ctx, productDeleted, nil) + So(err, ShouldBeNil) + + var p *models.Product + + So(testUtils.WaitUntilConditionMet(func() bool { + p, err = integrationTestSharedFixture.ProductRepository.GetProductByProductId( + ctx, + integrationTestSharedFixture.Items[0].ProductId, + ) + So(err, ShouldBeNil) + + return p == nil + }), ShouldBeNil) + + So(p, ShouldBeNil) + }) + }) + }) + + integrationTestSharedFixture.TearDownTest() + }) + + integrationTestSharedFixture.Log.Info("TearDownSuite started") + integrationTestSharedFixture.Bus.Stop() + time.Sleep(1 * time.Second) +} diff --git a/internal/services/catalogreadservice/test/integration/products/features/getting_product_by_id/v1/queries/get_product_by_id_test.go b/internal/services/catalogreadservice/test/integration/products/features/getting_product_by_id/v1/queries/get_product_by_id_test.go new file mode 100644 index 00000000..5ad2a43d --- /dev/null +++ b/internal/services/catalogreadservice/test/integration/products/features/getting_product_by_id/v1/queries/get_product_by_id_test.go @@ -0,0 +1,86 @@ +//go:build integration +// +build integration + +package queries + +import ( + "context" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/get_product_by_id/v1/queries" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestGetProductById(t *testing.T) { + integrationTestSharedFixture := integration.NewIntegrationTestSharedFixture(t) + ctx := context.Background() + + Convey("Get Product by ID Feature", t, func() { + integrationTestSharedFixture.SetupTest() + + knownProductID, err := uuid.FromString(integrationTestSharedFixture.Items[0].Id) + unknownProductID := uuid.NewV4() + So(err, ShouldBeNil) + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey("Returning an existing product with valid Id from the database with correct properties", func() { + Convey("Given a product with a known ID exists in the database", func() { + query, err := queries.NewGetProductById(knownProductID) + So(err, ShouldBeNil) + + Convey("When we execute GetProductById query for a product with known ID", func() { + result, err := mediatr.Send[*queries.GetProductById, *dtos.GetProductByIdResponseDto]( + ctx, + query, + ) + + Convey("Then it should retrieve product successfully", func() { + So(result, ShouldNotBeNil) + So(result.Product, ShouldNotBeNil) + So(err, ShouldBeNil) + + Convey("And the retrieved product should have the correct ID", func() { + // Assert that the retrieved product's ID matches the known ID. + So(result.Product.Id, ShouldEqual, knownProductID.String()) + }) + + Convey("And other product properties should be correct", func() { + // Assert other properties of the retrieved product as needed. + }) + }) + }) + }) + }) + + Convey("Returning a NotFound error when product with specific id does not exist", func() { + Convey("Given a product with a unknown ID in the database", func() { + // Create a test context and an unknown product ID. + + query, err := queries.NewGetProductById(unknownProductID) + So(err, ShouldBeNil) + + Convey("When GetProductById executed for a product with an unknown ID", func() { + result, err := mediatr.Send[*queries.GetProductById, *dtos.GetProductByIdResponseDto]( + ctx, + query, + ) + + Convey("Then the product should not be found and null result", func() { + // Assert that the error indicates that the product was not found. + So(result, ShouldBeNil) + So(err, ShouldNotBeNil) + }) + }) + }) + }) + + integrationTestSharedFixture.TearDownTest() + }) +} diff --git a/internal/services/catalogreadservice/test/integration/products/features/getting_products/v1/queries/get_products_handler_test.go b/internal/services/catalogreadservice/test/integration/products/features/getting_products/v1/queries/get_products_handler_test.go new file mode 100644 index 00000000..f93dd66e --- /dev/null +++ b/internal/services/catalogreadservice/test/integration/products/features/getting_products/v1/queries/get_products_handler_test.go @@ -0,0 +1,66 @@ +//go:build integration +// +build integration + +package queries + +import ( + "context" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/getting_products/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/getting_products/v1/queries" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/mehdihadeli/go-mediatr" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestGetProducts(t *testing.T) { + integrationTestSharedFixture := integration.NewIntegrationTestSharedFixture(t) + + Convey("Get All Products Feature", t, func() { + ctx := context.Background() + integrationTestSharedFixture.SetupTest() + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey("Getting a list of existing products from the database", func() { + Convey("Given a set of existing products in the system", func() { + query := queries.NewGetProducts(utils.NewListQuery(10, 1)) + + Convey("When GetProduct query executed for existing products", func() { + queryResult, err := mediatr.Send[*queries.GetProducts, *dtos.GetProductsResponseDto]( + ctx, + query, + ) + + Convey("Then the products should be retrieved successfully", func() { + // Assert that the error is nil (indicating success). + So(err, ShouldBeNil) + So(queryResult, ShouldNotBeNil) + So(queryResult.Products, ShouldNotBeNil) + + Convey("And the list of products should not be empty", func() { + // Assert that the list of products is not empty. + So(queryResult.Products.Items, ShouldNotBeEmpty) + + Convey("And each product should have the correct properties", func() { + for _, product := range queryResult.Products.Items { + // Assert properties of each product as needed. + // For example: + So(product.Name, ShouldNotBeEmpty) + So(product.Price, ShouldBeGreaterThan, 0.0) + // Add more assertions as needed. + } + }) + }) + }) + }) + }) + }) + + integrationTestSharedFixture.TearDownTest() + }) +} diff --git a/internal/services/catalogreadservice/test/integration/products/features/updating_product/v1/commands/update_product_handler_test.go b/internal/services/catalogreadservice/test/integration/products/features/updating_product/v1/commands/update_product_handler_test.go new file mode 100644 index 00000000..f467dc4a --- /dev/null +++ b/internal/services/catalogreadservice/test/integration/products/features/updating_product/v1/commands/update_product_handler_test.go @@ -0,0 +1,71 @@ +//go:build integration +// +build integration + +package commands + +import ( + "context" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/updating_products/v1/commands" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/brianvoe/gofakeit/v6" + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestUpdateProduct(t *testing.T) { + integrationTestSharedFixture := integration.NewIntegrationTestSharedFixture(t) + + Convey("Updating Product Feature", t, func() { + ctx := context.Background() + integrationTestSharedFixture.SetupTest() + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey("Updating an existing product in the database", func() { + Convey("Given an existing product in the system", func() { + productId, err := uuid.FromString(integrationTestSharedFixture.Items[0].ProductId) + So(err, ShouldBeNil) + + updateProduct, err := commands.NewUpdateProduct( + productId, + gofakeit.Name(), + gofakeit.AdjectiveDescriptive(), + gofakeit.Price(150, 6000), + ) + So(err, ShouldBeNil) + + Convey("When a UpdateProduct command executed for a existing product", func() { + result, err := mediatr.Send[*commands.UpdateProduct, *mediatr.Unit](ctx, updateProduct) + + Convey("Then the product should be updated successfully", func() { + // Assert that the error is nil (indicating success). + So(err, ShouldBeNil) + So(result, ShouldNotBeNil) + + Convey("And the updated product details should be reflected in the system", func() { + // Fetch the updated product from the database. + updatedProduct, _ := integrationTestSharedFixture.ProductRepository.GetProductByProductId( + ctx, + productId.String(), + ) + + Convey("And the product's properties should match the updated data", func() { + // Assert that the product properties match the updated data. + So(updatedProduct.Name, ShouldEqual, updatedProduct.Name) + So(updatedProduct.Price, ShouldEqual, updatedProduct.Price) + // Add more assertions as needed for other properties. + }) + }) + }) + }) + }) + }) + + integrationTestSharedFixture.TearDownTest() + }) +} diff --git a/internal/services/catalogreadservice/test/integration/products/features/updating_product/v1/events/product_updated_test.go b/internal/services/catalogreadservice/test/integration/products/features/updating_product/v1/events/product_updated_test.go new file mode 100644 index 00000000..f3351bdd --- /dev/null +++ b/internal/services/catalogreadservice/test/integration/products/features/updating_product/v1/events/product_updated_test.go @@ -0,0 +1,152 @@ +//go:build integration +// +build integration + +package events + +import ( + "context" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" + externalEvents "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/features/updating_products/v1/events/integration_events/external_events" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogreadservice/internal/shared/testfixture/integration" + + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestProductUpdatedConsumer(t *testing.T) { + // Setup and initialization code here. + integrationTestSharedFixture := integration.NewIntegrationTestSharedFixture( + t, + ) + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + integrationTestSharedFixture.Bus.Start(context.Background()) + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + + Convey("Product Created Feature", t, func() { + ctx := context.Background() + integrationTestSharedFixture.SetupTest() + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey("Consume ProductUpdated event by consumer", func() { + hypothesis := messaging.ShouldConsume[*externalEvents.ProductUpdatedV1]( + ctx, + integrationTestSharedFixture.Bus, + nil, + ) + + fakeUpdateProduct := &externalEvents.ProductUpdatedV1{ + Message: types.NewMessage(uuid.NewV4().String()), + ProductId: integrationTestSharedFixture.Items[0].ProductId, + Name: gofakeit.Name(), + Price: gofakeit.Price(100, 1000), + Description: gofakeit.EmojiDescription(), + UpdatedAt: time.Now(), + } + + Convey("When a ProductUpdated event consumed", func() { + err := integrationTestSharedFixture.Bus.PublishMessage( + ctx, + fakeUpdateProduct, + nil, + ) + So(err, ShouldBeNil) + + Convey( + "Then it should consume the ProductUpdated event", + func() { + hypothesis.Validate( + ctx, + "there is no consumed message", + 30*time.Second, + ) + }, + ) + }) + }) + + // https://specflow.org/learn/gherkin/#learn-gherkin + // scenario + Convey( + "Update product in mongo database when a ProductDeleted event consumed", + func() { + fakeUpdateProduct := &externalEvents.ProductUpdatedV1{ + Message: types.NewMessage(uuid.NewV4().String()), + ProductId: integrationTestSharedFixture.Items[0].ProductId, + Name: gofakeit.Name(), + Price: gofakeit.Price(100, 1000), + Description: gofakeit.EmojiDescription(), + UpdatedAt: time.Now(), + } + + Convey("When a ProductUpdated event consumed", func() { + err := integrationTestSharedFixture.Bus.PublishMessage( + ctx, + fakeUpdateProduct, + nil, + ) + So(err, ShouldBeNil) + + Convey( + "Then It should update product in the mongo database", + func() { + ctx := context.Background() + productUpdated := &externalEvents.ProductUpdatedV1{ + Message: types.NewMessage( + uuid.NewV4().String(), + ), + ProductId: integrationTestSharedFixture.Items[0].ProductId, + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(150, 6000), + UpdatedAt: time.Now(), + } + + err := integrationTestSharedFixture.Bus.PublishMessage( + ctx, + productUpdated, + nil, + ) + So(err, ShouldBeNil) + + var product *models.Product + + err = testUtils.WaitUntilConditionMet(func() bool { + product, err = integrationTestSharedFixture.ProductRepository.GetProductByProductId( + ctx, + integrationTestSharedFixture.Items[0].ProductId, + ) + + return product != nil && + product.Name == productUpdated.Name + }) + + So(err, ShouldBeNil) + So(product, ShouldNotBeNil) + So( + productUpdated.ProductId, + ShouldEqual, + product.ProductId, + ) + }, + ) + }) + }, + ) + + integrationTestSharedFixture.TearDownTest() + }) + + integrationTestSharedFixture.Log.Info("TearDownSuite started") + integrationTestSharedFixture.Bus.Stop() + time.Sleep(1 * time.Second) +} diff --git a/internal/services/catalogreadservice/test/load_tests/.openapi-generator-ignore b/internal/services/catalogreadservice/test/load_tests/.openapi-generator-ignore new file mode 100644 index 00000000..7484ee59 --- /dev/null +++ b/internal/services/catalogreadservice/test/load_tests/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/internal/services/catalogreadservice/test/load_tests/.openapi-generator/FILES b/internal/services/catalogreadservice/test/load_tests/.openapi-generator/FILES new file mode 100644 index 00000000..297b5aa9 --- /dev/null +++ b/internal/services/catalogreadservice/test/load_tests/.openapi-generator/FILES @@ -0,0 +1,3 @@ +.openapi-generator-ignore +README.md +script.js diff --git a/internal/services/catalogreadservice/test/load_tests/.openapi-generator/VERSION b/internal/services/catalogreadservice/test/load_tests/.openapi-generator/VERSION new file mode 100644 index 00000000..66672d4e --- /dev/null +++ b/internal/services/catalogreadservice/test/load_tests/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.1.0-SNAPSHOT \ No newline at end of file diff --git a/internal/services/catalogreadservice/test/load_tests/README.md b/internal/services/catalogreadservice/test/load_tests/README.md new file mode 100644 index 00000000..64a75216 --- /dev/null +++ b/internal/services/catalogreadservice/test/load_tests/README.md @@ -0,0 +1,15 @@ +# Generated k6 script + +The `script.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs. + +Global header variables are defined at the top of the file, like `api_key`. Each path in the specification is converted into a [group](https://docs.k6.io/docs/tags-and-groups) in k6 and each group contains all the request methods related to that path. Path and query parameters are extracted from the specification and put at the start of the group. The URL is constructed from the base URL plus path and query. + +If the Swagger/OpenAPI specification used as the input spec contains examples at parameter level, those will be extracted and utilized as parameter values. The `handleParamValue` custom Mustache lambda registered for use in the K6 `script.mustache` template handles the conditional checks, formatting, and outputting of parameter values. If a given parameter has value specified – either in `example` or `examples` field, defined at the parameter level – that value will be used. For list (`examples`), entire list will be output in the generated script and the first element from that list will be assigned as parameter value. If a given parameter does not have an example defined, a placeholder value with `TODO_EDIT_THE_` prefix will be generated for that parameter, and you will have to assign a value before you can run the script. In other words, you can now generate K6 test scripts which are ready to run, provided the Swagger/OpenAPI specification used as the input spec contains examples for all of the path/query parameters; see `modules/openapi-generator/src/test/resources/3_0/examples.yaml` for an example of such specification, and https://swagger.io/docs/specification/adding-examples/ for more information about adding examples. + +k6 specific parameters are in the [`params`](https://docs.k6.io/docs/params-k6http) object, and `body` contains the [request](https://docs.k6.io/docs/http-requests) body which is in the form of `identifier: type`, which the `type` should be substituted by a proper value. Then goes the request and the check. + +[Check](https://docs.k6.io/docs/checks) are like asserts but differ in that they don't halt execution, instead they just store the result of the check, pass or fail, and let the script execution continue. + +Each request is always followed by a 0.1 second [sleep](https://docs.k6.io/docs/sleep-t-1) to prevent the script execution from flooding the system with too many requests simultaneously. + +Note that the default iteration count and VU count is 1. So each request in each group will be executed once. For more information, see the [k6 options](https://docs.k6.io/docs/options). diff --git a/internal/services/catalogreadservice/test/load_tests/script.js b/internal/services/catalogreadservice/test/load_tests/script.js new file mode 100644 index 00000000..3922df33 --- /dev/null +++ b/internal/services/catalogreadservice/test/load_tests/script.js @@ -0,0 +1,71 @@ +/* + * Catalogs Read-Service Api + * Catalogs Read-Service Api. + * + * OpenAPI spec version: 1.0 + * + * NOTE: This class is auto generated by OpenAPI Generator. + * https://github.com/OpenAPITools/openapi-generator + * + * OpenAPI generator version: 6.1.0-SNAPSHOT + */ + + +import http from "k6/http"; +import { group, check, sleep } from "k6"; + +const BASE_URL = ""; +// Sleep duration between successive requests. +// You might want to edit the value of this variable or remove calls to the sleep function on the script. +const SLEEP_DURATION = 0.1; +// Global variables should be initialized. + +export default function() { + group("/api/v1/products/search", () => { + let search = 'TODO_EDIT_THE_SEARCH'; // specify value as there is no example value for this parameter in OpenAPI spec + let size = 'TODO_EDIT_THE_SIZE'; // specify value as there is no example value for this parameter in OpenAPI spec + let orderBy = 'TODO_EDIT_THE_ORDERBY'; // specify value as there is no example value for this parameter in OpenAPI spec + let page = 'TODO_EDIT_THE_PAGE'; // specify value as there is no example value for this parameter in OpenAPI spec + + // Request No. 1 + { + let url = BASE_URL + `/api/v1/products/search?orderBy=${orderBy}&page=${page}&search=${search}&size=${size}`; + let request = http.get(url); + + check(request, { + "OK": (r) => r.status === 200 + }); + } + }); + + group("/api/v1/products", () => { + let size = 'TODO_EDIT_THE_SIZE'; // specify value as there is no example value for this parameter in OpenAPI spec + let orderBy = 'TODO_EDIT_THE_ORDERBY'; // specify value as there is no example value for this parameter in OpenAPI spec + let page = 'TODO_EDIT_THE_PAGE'; // specify value as there is no example value for this parameter in OpenAPI spec + + // Request No. 1 + { + let url = BASE_URL + `/api/v1/products?orderBy=${orderBy}&page=${page}&size=${size}`; + let request = http.get(url); + + check(request, { + "OK": (r) => r.status === 200 + }); + } + }); + + group("/api/v1/products/{id}", () => { + let id = 'TODO_EDIT_THE_ID'; // specify value as there is no example value for this parameter in OpenAPI spec + + // Request No. 1 + { + let url = BASE_URL + `/api/v1/products/${id}`; + let request = http.get(url); + + check(request, { + "OK": (r) => r.status === 200 + }); + } + }); + +} diff --git a/internal/services/catalogwriteservice/.air.toml b/internal/services/catalogwriteservice/.air.toml new file mode 100644 index 00000000..a4e5f123 --- /dev/null +++ b/internal/services/catalogwriteservice/.air.toml @@ -0,0 +1,37 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "tmp\\app\\main.exe" + cmd = "go build -o ./tmp/app/main.exe ./cmd/app/main.go" + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + kill_delay = "0s" + log = "build-errors.log" + send_interrupt = false + stop_on_error = true + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false diff --git a/internal/services/catalogwriteservice/.dockerignore b/internal/services/catalogwriteservice/.dockerignore new file mode 100644 index 00000000..d2b3b429 --- /dev/null +++ b/internal/services/catalogwriteservice/.dockerignore @@ -0,0 +1,2 @@ +.gitignore +Dockerfile diff --git a/internal/services/catalogwriteservice/.gitignore b/internal/services/catalogwriteservice/.gitignore new file mode 100644 index 00000000..7d501265 --- /dev/null +++ b/internal/services/catalogwriteservice/.gitignore @@ -0,0 +1,46 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go tests -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +# Tools +.idea +.idea/* + +.terraform +*.tfstate +*.tfstate.*backup + +.go/ +.go-cache/ + +.vscode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix diff --git a/internal/services/catalogwriteservice/.golangci.yml b/internal/services/catalogwriteservice/.golangci.yml new file mode 100644 index 00000000..e311c166 --- /dev/null +++ b/internal/services/catalogwriteservice/.golangci.yml @@ -0,0 +1,115 @@ +linters-settings: + dupl: + threshold: 120 + errorlint: + errorf: true + errcheck: + check-type-assertions: true + check-blank: true + exhaustive: + check-generated: false + default-signifies-exhaustive: false + funlen: + lines: 120 + statements: 50 + gocognit: + min-complexity: 40 + gocyclo: + min-complexity: 15 + goconst: + min-len: 2 + min-occurrences: 2 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - dupImport + - unnecessaryBlock + gofumpt: + extra-rules: true + gomnd: + settings: + mnd: + checks: + - argument + - case + - condition + - operation + - return + govet: + check-shadowing: true + misspell: + locale: US + nestif: + min-complexity: 4 + nolintlint: + require-explanation: true + require-specific: true + +linters: + disable-all: true + enable: + - asciicheck + - bodyclose + - cyclop + - dogsled + - dupl + - durationcheck + - errcheck + - errorlint + - exhaustive + - exportloopref + - forbidigo + - funlen + - gci + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - godox + - goerr113 + - gofmt + - gofumpt + - goimports + - gomnd + - gomodguard + - gosec + - gosimple + - govet + - ineffassign + - makezero + - misspell + - nakedret + - nestif + - nlreturn + - noctx + - nolintlint + - paralleltest + - predeclared + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - tparallel + - thelper + - unconvert + - unparam + - unused + - wsl + - whitespace + +run: + skip-dirs: + - docs + +issues: + # Show only new issues created after git revision: e016c66 + new-from-rev: e016c66 diff --git a/internal/services/catalogwriteservice/Dockerfile b/internal/services/catalogwriteservice/Dockerfile new file mode 100644 index 00000000..a2169f30 --- /dev/null +++ b/internal/services/catalogwriteservice/Dockerfile @@ -0,0 +1,38 @@ +FROM golang:1.19.0-alpine AS builder + +# Set Go env +ENV CGO_ENABLED=0 GOOS=linux +WORKDIR /services/catalogs/write_service + +# Install dependencies +RUN apk --update --no-cache add ca-certificates make protoc + +# Download grpc_health_probe +RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ + wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ + chmod +x /bin/grpc_health_probe + +# Build Go binary +COPY Makefile go.mod go.sum ./ +RUN go env -w GOPROXY=https://goproxy.io,direct/ +RUN make init && go mod download +COPY . . +RUN make proto tidy + +# Skaffold passes in debug-oriented compiler flags +ARG SKAFFOLD_GO_GCFLAGS +RUN go build.sh -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /services/catalogs_write/catalogs_write . + +# Deployment container +FROM scratch + +WORKDIR /write_service + +# Definition of this variable is used by 'skaffold debug' to identify a golang binary. +# Default behavior - a failure prints a stack trace for the current goroutine. +# See https://golang.org/pkg/runtime/ +ENV GOTRACEBACK=single + +COPY --from=builder /services/catalogs/write_service /services/catalogs-write + +ENTRYPOINT ["/services/catalogs-write"] diff --git a/internal/services/catalogwriteservice/Makefile b/internal/services/catalogwriteservice/Makefile new file mode 100644 index 00000000..f1638f27 --- /dev/null +++ b/internal/services/catalogwriteservice/Makefile @@ -0,0 +1,88 @@ +GOPATH:=$(shell go env GOPATH) + +.PHONY: run +run: + go run ./cmd/main.go + +.PHONY: build +build: + go build ./cmd/main.go + +.PHONY: test +test: + go test -cover ./... + +.PHONY: lint +lint: + revive -config revive-config.toml -formatter friendly ./... + staticcheck ./... + golangci-lint run ./... + +.PHONY: format +format: + golines -m 120 -w --ignore-generated . + gci write --skip-generated -s standard -s "prefix(github.com/mehdihadeli/go-food-delivery-microservices)" -s default -s blank -s dot --custom-order . + gofumpt -l -w . + +.PHONY: update +update: + @go get -u + +.PHONY: tidy +tidy: + go mod tidy + +.PHONY: deps-reset +deps-reset: + git checkout -- go.mod + go mod tidy + +.PHONY: deps-upgrade +deps-upgrade: + go get -u -t -d -v ./... + go mod tidy + +.PHONY: deps-cleancache +deps-cleancache: + go clean -modcache + +# ============================================================================== +# Linters https://golangci-lint.run/usage/install/ +.PHONY: run-linter +run-linter: + @echo Starting linters + golangci-lint run ./... + +.PHONY: docker +docker: + @docker build -t go-catalogs-write:latest . + +# ============================================================================== +# Go migrate postgresql https://github.com/golang-migrate/migrate + +DB_NAME = catalogs.service +DB_HOST = localhost +DB_USER = postgres +DB_PASS = postgres +DB_HOST = localhost +DB_PORT = 5432 +SSL_MODE = disable + +# go the last successful version, which is 1 here +# https://github.com/golang-migrate/migrate/blob/master/GETTING_STARTED.md#forcing-your-database-version +# https://github.com/golang-migrate/migrate/issues/282#issuecomment-530743258 +# https://github.com/golang-migrate/migrate/issues/35 +# https://github.com/golang-migrate/migrate/issues/21 +# https://dev.to/techschoolguru/how-to-write-run-database-migration-in-golang-5h6g + +.PHONY: postgres +postgres: + docker run --name postgres -p $(DB_PORT)\:$(DB_PORT) -e POSTGRES_USER=$(DB_USER) -e POSTGRES_PASSWORD=$(DB_PASS) -d postgres:11.1-alpine + +.PHONY: create_db +create_db: + docker exec -it postgres createdb -U $(DB_USER) -O $(DB_USER) $(DB_NAME) + +.PHONY: drop_db +drop_db: + docker exec -it postgres dropdb -U $(DB_USER) $(DB_NAME) diff --git a/internal/services/catalogwriteservice/arch-go.yml b/internal/services/catalogwriteservice/arch-go.yml new file mode 100644 index 00000000..ca3e481b --- /dev/null +++ b/internal/services/catalogwriteservice/arch-go.yml @@ -0,0 +1,4 @@ +#https://github.com/fdaines/arch-go +cyclesRules: + - package: "**.cmd" + shouldNotContainCycles: true \ No newline at end of file diff --git a/internal/services/catalogwriteservice/atlas.hcl b/internal/services/catalogwriteservice/atlas.hcl new file mode 100644 index 00000000..c2befff1 --- /dev/null +++ b/internal/services/catalogwriteservice/atlas.hcl @@ -0,0 +1,40 @@ +#https://atlasgo.io/atlas-schema/projects + +data "external_schema" "gorm" { + program = [ + "go", + "run", + "-mod=mod", + "ariga.io/atlas-provider-gorm", + "load", + "--path", "./internal/products/models", + "--dialect", "postgres", + ] +} + +env "gorm" { + src = data.external_schema.gorm.url + dev = "postgres://postgres:postgres@localhost:5432/catalogs_service?sslmode=disable" + migration { + dir = "file://db/migrations/atlas" + } + format { + migrate { + diff = "{{ sql . \" \" }}" + } + } +} + +env "go-migrate" { + src = "file://db/migrations/go-migrate/schema.sql" + dev = "postgres://postgres:postgres@localhost:5432/catalogs_service?sslmode=disable" + migration { + dir = "file://db/migrations/go-migrate" + format = golang-migrate + } + format { + migrate { + diff = "{{ sql . \" \" }}" + } + } +} diff --git a/internal/services/catalogwriteservice/client.http b/internal/services/catalogwriteservice/client.http new file mode 100644 index 00000000..e69de29b diff --git a/internal/services/catalogwriteservice/cmd/app/main.go b/internal/services/catalogwriteservice/cmd/app/main.go new file mode 100644 index 00000000..5999f149 --- /dev/null +++ b/internal/services/catalogwriteservice/cmd/app/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "os" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/app" + + "github.com/pterm/pterm" + "github.com/pterm/pterm/putils" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "catalogs-write-microservice", + Short: "catalogs-write-microservice based on vertical slice architecture", + Long: `This is a command runner or cli for api architecture in golang.`, + TraverseChildren: true, + Run: func(cmd *cobra.Command, args []string) { + app.NewApp().Run() + }, +} + +// https://github.com/swaggo/swag#how-to-use-it-with-gin + +// @contact.name Mehdi Hadeli +// @contact.url https://github.com/mehdihadeli +// @title Catalogs Write-Service Api +// @version 1.0 +// @description Catalogs Write-Service Api. +func main() { + pterm.DefaultBigText.WithLetters( + putils.LettersFromStringWithStyle("Catalogs", pterm.FgLightGreen.ToStyle()), + putils.LettersFromStringWithStyle(" Write Service", pterm.FgLightMagenta.ToStyle())). + Render() + + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/internal/services/catalogwriteservice/cmd/migration/main.go b/internal/services/catalogwriteservice/cmd/migration/main.go new file mode 100644 index 00000000..3a4cddc2 --- /dev/null +++ b/internal/services/catalogwriteservice/cmd/migration/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "context" + "os" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration/goose" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + appconfig "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/config" + + "github.com/spf13/cobra" + "go.uber.org/fx" +) + +func init() { + // Add flags to specify the version + cmdUp.Flags().Uint("version", 0, "Migration version") + cmdDown.Flags().Uint("version", 0, "Migration version") + + // Add commands to the root command + rootCmd.AddCommand(cmdUp) + rootCmd.AddCommand(cmdDown) +} + +var ( + rootCmd = &cobra.Command{ //nolint:gochecknoglobals + Use: "migration", + Short: "A tool for running migrations", + Run: func(cmd *cobra.Command, args []string) { + // Execute the "up" subcommand when no subcommand is specified + if len(args) == 0 { + cmd.SetArgs([]string{"up"}) + if err := cmd.Execute(); err != nil { + defaultLogger.GetLogger().Error(err) + os.Exit(1) + } + } + }, + } + + cmdDown = &cobra.Command{ //nolint:gochecknoglobals + Use: "down", + Short: "Run a down migration", + Run: func(cmd *cobra.Command, args []string) { + executeMigration(cmd, migration.Down) + }, + } + + cmdUp = &cobra.Command{ //nolint:gochecknoglobals + Use: "up", + Short: "Run an up migration", + Run: func(cmd *cobra.Command, args []string) { + executeMigration(cmd, migration.Up) + }, + } +) + +func executeMigration(cmd *cobra.Command, commandType migration.CommandType) { + version, err := cmd.Flags().GetUint("version") + if err != nil { + defaultLogger.GetLogger().Fatal(err) + } + + app := fx.New( + config.ModuleFunc(environment.Development), + zap.Module, + fxlog.FxLogger, + gormPostgres.Module, + appconfig.Module, + //// use go-migrate library for migration + //gomigrate.Module, + // use go-migrate library for migration + goose.Module, + fx.Invoke( + func(migrationRunner contracts.PostgresMigrationRunner, logger logger.Logger) { + logger.Info("Migration process started...") + switch commandType { + case migration.Up: + err = migrationRunner.Up(context.Background(), version) + case migration.Down: + err = migrationRunner.Down(context.Background(), version) + } + if err != nil { + logger.Fatalf("migration failed, err: %s", err) + } + logger.Info("Migration completed...") + }, + ), + ) + + err = app.Start(context.Background()) + if err != nil { + defaultLogger.GetLogger().Fatal(err) + } + + err = app.Stop(context.Background()) + if err != nil { + defaultLogger.GetLogger().Fatal(err) + } +} + +func main() { + if err := rootCmd.Execute(); err != nil { + defaultLogger.GetLogger().Error(err) + os.Exit(1) + } +} diff --git a/internal/services/catalogwriteservice/config/app_options.go b/internal/services/catalogwriteservice/config/app_options.go new file mode 100644 index 00000000..1003cb65 --- /dev/null +++ b/internal/services/catalogwriteservice/config/app_options.go @@ -0,0 +1,34 @@ +package config + +import ( + "strings" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + + "github.com/iancoleman/strcase" +) + +type AppOptions struct { + DeliveryType string `mapstructure:"deliveryType" env:"DeliveryType"` + ServiceName string `mapstructure:"serviceName" env:"serviceName"` +} + +func NewAppOptions(environment environment.Environment) (*AppOptions, error) { + optionName := strcase.ToLowerCamel(typeMapper.GetGenericTypeNameByT[AppOptions]()) + cfg, err := config.BindConfigKey[*AppOptions](optionName, environment) + if err != nil { + return nil, err + } + + return cfg, nil +} + +func (cfg *AppOptions) GetMicroserviceNameUpper() string { + return strings.ToUpper(cfg.ServiceName) +} + +func (cfg *AppOptions) GetMicroserviceName() string { + return cfg.ServiceName +} diff --git a/internal/services/catalogwriteservice/config/config.development.json b/internal/services/catalogwriteservice/config/config.development.json new file mode 100644 index 00000000..bcdc2233 --- /dev/null +++ b/internal/services/catalogwriteservice/config/config.development.json @@ -0,0 +1,113 @@ +{ + "appOptions": { + "serviceName": "catalogwriteservice", + "deliveryType": "http" + }, + "grpcOptions": { + "name": "catalogwriteservice", + "port": ":6003", + "host": "localhost", + "development": true + }, + "echoHttpOptions": { + "name": "catalogwriteservice", + "port": ":7000", + "development": true, + "timeout": 30, + "basePath": "/api/v1", + "host": "http://localhost", + "productsPath": "products", + "debugHeaders": true, + "httpClientDebug": true, + "debugErrorsResponse": true, + "ignoreLogUrls": [ + "metrics" + ] + }, + "logOptions": { + "level": "debug", + "logType": 0, + "callerEnabled": false + }, + "gormOptions": { + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "postgres", + "dbName": "catalogs_service", + "sslMode": false + }, + "rabbitmqOptions": { + "autoStart": true, + "reconnecting": true, + "rabbitmqHostOptions": { + "userName": "guest", + "password": "guest", + "hostName": "localhost", + "port": 5672, + "httpPort": 15672 + } + }, + "tracingOptions": { + "enable": true, + "serviceName": "catalogs-write-service", + "instrumentationName": "io.opentelemetry.traces.catalogs-write-service", + "id": 1, + "useStdout": false, + "alwaysOnSampler": true, + "jaegerExporterOptions": { + "otlpEndpoint": "localhost:4320", + "enabled": true + }, + "tempoExporterOptions": { + "otlpEndpoint": "localhost:4322", + "enabled": true + }, + "zipkinExporterOptions": { + "url": "http://localhost:9411/api/v2/spans" + }, + "otlpProviders": [ + { + "name": "uptrace", + "enabled": false, + "otlpEndpoint": "otlp.uptrace.dev:4317", + "otlpHeaders": { + "uptrace-dsn": "https://@uptrace.dev/" + } + }, + { + "name": "elastic-apm", + "enabled": false, + "otlpEndpoint": "host.docker.internal:4317", + "otlpHeaders": { + "Authorization": "Bearer ${ELASTIC_APM_SECRET_TOKEN}" + } + }, + { + "name": "signoz", + "enabled": false, + "otlpEndpoint": "localhost:4317" + }, + { + "name": "grafana-tempo", + "enabled": false, + "otlpEndpoint": "localhost:4322" + } + ] + }, + "metricsOptions": { + "metricsRoutePath": "metrics", + "serviceName": "catalogs-write-service", + "instrumentationName": "io.opentelemetry.metrics.catalogs-write-service" + }, + "migrationOptions": { + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "postgres", + "dbName": "catalogs_service", + "sslMode": false, + "migrationsDir": "db/migrations/goose-migrate", + "skipMigration": false + } +} diff --git a/internal/services/catalogwriteservice/config/config.test.json b/internal/services/catalogwriteservice/config/config.test.json new file mode 100644 index 00000000..8de7c47b --- /dev/null +++ b/internal/services/catalogwriteservice/config/config.test.json @@ -0,0 +1,89 @@ +{ + "appOptions": { + "serviceName": "catalogwriteservice", + "deliveryType": "http" + }, + "grpcOptions": { + "name": "catalogwriteservice", + "port": ":3301", + "host": "localhost", + "development": true + }, + "echoHttpOptions": { + "name": "catalogwriteservice", + "port": ":6001", + "development": true, + "timeout": 30, + "basePath": "/api/v1", + "host": "http://localhost", + "productsPath": "products", + "debugHeaders": true, + "httpClientDebug": true, + "debugErrorsResponse": true, + "ignoreLogUrls": [ + "metrics" + ] + }, + "logOptions": { + "level": "debug", + "logType": 0, + "callerEnabled": false + }, + "gormOptions": { + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "postgres", + "dbName": "catalogs_service", + "sslMode": false + }, + "rabbitmqOptions": { + "autoStart": false, + "reconnecting": false, + "rabbitmqHostOptions": { + "userName": "guest", + "password": "guest", + "hostName": "localhost", + "port": 5672, + "httpPort": 15672 + } + }, + "tracingOptions": { + "enable": true, + "serviceName": "catalogs-write-service", + "instrumentationName": "io.opentelemetry.traces.catalogs-write-service", + "id": 1, + "useStdout": false, + "alwaysOnSampler": true, + "jaegerExporterOptions": { + "otlpEndpoint": "localhost:4320", + "enabled": true + }, + "zipkinExporterOptions": { + "url": "http://localhost:9411/api/v2/spans" + }, + "otlpProviders": [ + { + "name": "uptrace", + "enabled": false, + "otlpEndpoint": "otlp.uptrace.dev:4317", + "otlpHeaders": {} + } + ] + }, + "metricsOptions": { + "metricsRoutePath": "metrics", + "serviceName": "catalogs-write-service", + "instrumentationName": "io.opentelemetry.metrics.catalogs-write-service" + }, + "migrationOptions": { + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "postgres", + "dbName": "catalogs_service", + "sslMode": false, + "migrationsDir": "db/migrations/goose-migrate", + "skipMigration": false + } +} diff --git a/internal/services/catalogwriteservice/config/config_fx.go b/internal/services/catalogwriteservice/config/config_fx.go new file mode 100644 index 00000000..d60caf6a --- /dev/null +++ b/internal/services/catalogwriteservice/config/config_fx.go @@ -0,0 +1,15 @@ +package config + +import ( + "go.uber.org/fx" +) + +// https://uber-go.github.io/fx/modules.html +var Module = fx.Module("appconfigfx", + // - order is not important in provide + // - provide can have parameter and will resolve if registered + // - execute its func only if it requested + fx.Provide( + NewAppOptions, + ), +) diff --git a/internal/services/catalogwriteservice/db/fixtures/products/products.yaml b/internal/services/catalogwriteservice/db/fixtures/products/products.yaml new file mode 100644 index 00000000..6c7b6831 --- /dev/null +++ b/internal/services/catalogwriteservice/db/fixtures/products/products.yaml @@ -0,0 +1,8 @@ +{{range $Product := $.Products}} +- product_id: {{$Product.ProductId}} + name: {{$Product.Name}} + price: 100 + description: {{$Product.Description}} + created_at: 2022-01-01 + updated_at: 2022-01-01 +{{end}} \ No newline at end of file diff --git a/internal/services/catalogwriteservice/db/migrations/atlas/20230919170700.sql b/internal/services/catalogwriteservice/db/migrations/atlas/20230919170700.sql new file mode 100644 index 00000000..09475fba --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/atlas/20230919170700.sql @@ -0,0 +1,10 @@ +-- Create "products" table +CREATE TABLE "public"."products" ( + "product_id" text NOT NULL, + "name" text NULL, + "description" text NULL, + "price" numeric NULL, + "created_at" timestamptz NULL, + "updated_at" timestamptz NULL, + PRIMARY KEY ("product_id") +); diff --git a/internal/services/catalogwriteservice/db/migrations/atlas/atlas.sum b/internal/services/catalogwriteservice/db/migrations/atlas/atlas.sum new file mode 100644 index 00000000..7ef3c671 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/atlas/atlas.sum @@ -0,0 +1,2 @@ +h1:STL9Gt6US7g2EZozDIQ2fkT0eGROAhTsnDSZhY6rNRI= +20230919170700.sql h1:Z2PYRNbeYrwt9xmOI44evGjjj/jmpShqWYVexvUXWI8= diff --git a/internal/services/catalogwriteservice/db/migrations/atlas/goose/00001_enable_uuid_extension.sql b/internal/services/catalogwriteservice/db/migrations/atlas/goose/00001_enable_uuid_extension.sql new file mode 100644 index 00000000..c941cb36 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/atlas/goose/00001_enable_uuid_extension.sql @@ -0,0 +1 @@ +CREATE EXTENSION "uuid-ossp"; diff --git a/internal/services/catalogwriteservice/db/migrations/atlas/goose/00002_create_products_table.sql b/internal/services/catalogwriteservice/db/migrations/atlas/goose/00002_create_products_table.sql new file mode 100644 index 00000000..3cd15971 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/atlas/goose/00002_create_products_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS products +( + product_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + name text, + description text, + price numeric, + created_at timestamp with time zone, + updated_at timestamp with time zone +); diff --git a/internal/services/catalogwriteservice/db/migrations/atlas/goose/atlas.sum b/internal/services/catalogwriteservice/db/migrations/atlas/goose/atlas.sum new file mode 100644 index 00000000..d462e2a2 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/atlas/goose/atlas.sum @@ -0,0 +1,3 @@ +h1:tTCE9gdplT14q9qkT5Awu+3A/Y+Xjl8AoJhkH6e4tpE= +00001_enable_uuid_extension.sql h1:8nvgTOQQ91UoPUqGRpVIc0TFZ225YmCOblX7yEObv2I= +00002_create_products_table.sql h1:u5k/Xihi+GiziFZALx3cJa+v7x2b450ycezB3lVweBc= diff --git a/internal/services/catalogwriteservice/db/migrations/atlas/readme.md b/internal/services/catalogwriteservice/db/migrations/atlas/readme.md new file mode 100644 index 00000000..e49933b8 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/atlas/readme.md @@ -0,0 +1,6 @@ +# Migration Using Atlas +- [Automatic migration planning for GORM](https://atlasgo.io/guides/orms/gorm) +- [Automatic migration planning for golang-migrate](https://atlasgo.io/guides/migration-tools/golang-migrate) +- [Importing a Goose project to Atlas](https://atlasgo.io/guides/migration-tools/goose-import) + +# Sync Grom with Atlas Here diff --git a/internal/services/catalogwriteservice/db/migrations/go-migrate/000001_enable_uuid_extension.down.sql b/internal/services/catalogwriteservice/db/migrations/go-migrate/000001_enable_uuid_extension.down.sql new file mode 100644 index 00000000..8ca954c0 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/go-migrate/000001_enable_uuid_extension.down.sql @@ -0,0 +1 @@ +DROP EXTENSION IF EXISTS "uuid-ossp"; diff --git a/internal/services/catalogwriteservice/db/migrations/go-migrate/000001_enable_uuid_extension.up.sql b/internal/services/catalogwriteservice/db/migrations/go-migrate/000001_enable_uuid_extension.up.sql new file mode 100644 index 00000000..c941cb36 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/go-migrate/000001_enable_uuid_extension.up.sql @@ -0,0 +1 @@ +CREATE EXTENSION "uuid-ossp"; diff --git a/internal/services/catalogwriteservice/db/migrations/go-migrate/000002_create_products_table.down.sql b/internal/services/catalogwriteservice/db/migrations/go-migrate/000002_create_products_table.down.sql new file mode 100644 index 00000000..39a3c0ed --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/go-migrate/000002_create_products_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS products; diff --git a/internal/services/catalogwriteservice/db/migrations/go-migrate/000002_create_products_table.up.sql b/internal/services/catalogwriteservice/db/migrations/go-migrate/000002_create_products_table.up.sql new file mode 100644 index 00000000..3cd15971 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/go-migrate/000002_create_products_table.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS products +( + product_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + name text, + description text, + price numeric, + created_at timestamp with time zone, + updated_at timestamp with time zone +); diff --git a/internal/services/catalogwriteservice/db/migrations/go-migrate/atlas.sum b/internal/services/catalogwriteservice/db/migrations/go-migrate/atlas.sum new file mode 100644 index 00000000..49961eab --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/go-migrate/atlas.sum @@ -0,0 +1,6 @@ +h1:tpPUeCB+6hZZFR/Q7kF3UyQU0jggMPFUrcHVSeXxD6M= +000001_enable_uuid_extension.down.sql h1:gtXVYVcdHUgztryvvV/3OSCpegzalBV2afyVKJD2Umw= +000001_enable_uuid_extension.up.sql h1:AwRwKu3SfgU4x2WRaGwuVp9B+NZ0xFzH4/q3TCqwMbU= +000002_create_products_table.down.sql h1:BxLX2d7QPf2y7uuw7O401p6Bg2mBNQVdEyWIfkEEo4U= +000002_create_products_table.up.sql h1:dqSmpveIQ7ez6EgR5oq2jGjbhKWpQ+IU5StOz/hfbPw= +schema.sql h1:sYZljxAdKwrdvhSf9OppA2UPUI0VaDZxyiiJ8ifJhlI= diff --git a/internal/services/catalogwriteservice/db/migrations/go-migrate/readme.md b/internal/services/catalogwriteservice/db/migrations/go-migrate/readme.md new file mode 100644 index 00000000..0adcf4d3 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/go-migrate/readme.md @@ -0,0 +1,16 @@ +# Migration Using Go-Migrate +- [migrate CLI](https://github.com/golang-migrate/migrate/tree/master/cmd/migrate) +- [Getting started](https://github.com/golang-migrate/migrate/blob/856ea12df9d230b0145e23d951b7dbd6b86621cb/GETTING_STARTED.md) +- [Migration Filename Format](https://github.com/golang-migrate/migrate/blob/856ea12df9d230b0145e23d951b7dbd6b86621cb/MIGRATIONS.md) +- [PostgreSQL tutorial for beginners](https://github.com/golang-migrate/migrate/blob/856ea12df9d230b0145e23d951b7dbd6b86621cb/database/postgres/TUTORIAL.md) + +# Atlas Go-Migrate +[https://atlasgo.io/guides/migration-tools/golang-migrate](https://atlasgo.io/guides/migration-tools/golang-migrate) + +```bash +atlas migrate hash --env go-migrate +``` + +```bash +atlas schema inspect --env go-migrate --url "file://db/migrations/go-migrate" --format "{{ sql . \" \" }}" > ./db/migrations/go-migrate/schema.sql +``` diff --git a/internal/services/catalogwriteservice/db/migrations/go-migrate/schema.sql b/internal/services/catalogwriteservice/db/migrations/go-migrate/schema.sql new file mode 100644 index 00000000..879b57f2 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/go-migrate/schema.sql @@ -0,0 +1,14 @@ +-- Add new schema named "public" +CREATE SCHEMA IF NOT EXISTS "public"; +-- Set comment to schema: "public" +COMMENT ON SCHEMA "public" IS 'standard public schema'; +-- Create "products" table +CREATE TABLE "public"."products" ( + "product_id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "name" text NULL, + "description" text NULL, + "price" numeric NULL, + "created_at" timestamptz NULL, + "updated_at" timestamptz NULL, + PRIMARY KEY ("product_id") +); diff --git a/internal/services/catalogwriteservice/db/migrations/goose-migrate/00001_enable_uuid_extension.sql b/internal/services/catalogwriteservice/db/migrations/goose-migrate/00001_enable_uuid_extension.sql new file mode 100644 index 00000000..30826f0f --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/goose-migrate/00001_enable_uuid_extension.sql @@ -0,0 +1,9 @@ +-- +goose Up +-- +goose StatementBegin +CREATE EXTENSION "uuid-ossp"; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP EXTENSION "uuid-ossp"; +-- +goose StatementEnd diff --git a/internal/services/catalogwriteservice/db/migrations/goose-migrate/00002_create_products_table.sql b/internal/services/catalogwriteservice/db/migrations/goose-migrate/00002_create_products_table.sql new file mode 100644 index 00000000..f7ed379b --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/goose-migrate/00002_create_products_table.sql @@ -0,0 +1,17 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS products +( + product_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + name text, + description text, + price numeric, + created_at timestamp with time zone, + updated_at timestamp with time zone +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE products; +-- +goose StatementEnd diff --git a/internal/services/catalogwriteservice/db/migrations/goose-migrate/readme.md b/internal/services/catalogwriteservice/db/migrations/goose-migrate/readme.md new file mode 100644 index 00000000..ac7d5e07 --- /dev/null +++ b/internal/services/catalogwriteservice/db/migrations/goose-migrate/readme.md @@ -0,0 +1,9 @@ +# Migration Using Goose +- [Goose Usage](https://github.com/pressly/goose#usage) + +# Atlas Goose +- [Importing a Goose project to Atlas](https://atlasgo.io/guides/migration-tools/goose-import) + +```bash +atlas migrate import --from file://db/migrations/goose-migrate?format=goose --to file://db/migrations/atlas/goose +``` diff --git a/internal/services/catalogwriteservice/docs/docs.go b/internal/services/catalogwriteservice/docs/docs.go new file mode 100644 index 00000000..59cd573e --- /dev/null +++ b/internal/services/catalogwriteservice/docs/docs.go @@ -0,0 +1,380 @@ +// Code generated by swaggo/swag. DO NOT EDIT. + +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/products": { + "get": { + "description": "Get all products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get all product", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto" + } + } + } + }, + "post": { + "description": "Create new product item", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Create product", + "parameters": [ + { + "description": "Product data", + "name": "CreateProductRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto" + } + } + } + } + }, + "/api/v1/products/search": { + "get": { + "description": "Search products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Search products", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "description": "Get product by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get product by id", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto" + } + } + } + }, + "put": { + "description": "Update existing product", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Update product", + "parameters": [ + { + "description": "Product data", + "name": "UpdateProductRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto" + } + }, + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "description": "Delete existing product", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Delete product", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "productId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto": { + "type": "object", + "properties": { + "productId": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto": { + "type": "object", + "properties": { + "product": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListQuery": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FilterModel" + } + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Catalogs Write-Service Api", + Description: "Catalogs Write-Service Api.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/internal/services/catalogwriteservice/docs/swagger.json b/internal/services/catalogwriteservice/docs/swagger.json new file mode 100644 index 00000000..96d7f8e0 --- /dev/null +++ b/internal/services/catalogwriteservice/docs/swagger.json @@ -0,0 +1,353 @@ +{ + "swagger": "2.0", + "info": { + "description": "Catalogs Write-Service Api.", + "title": "Catalogs Write-Service Api", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "1.0" + }, + "paths": { + "/api/v1/products": { + "get": { + "description": "Get all products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get all product", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto" + } + } + } + }, + "post": { + "description": "Create new product item", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Create product", + "parameters": [ + { + "description": "Product data", + "name": "CreateProductRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto" + } + } + } + } + }, + "/api/v1/products/search": { + "get": { + "description": "Search products", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Search products", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto" + } + } + } + } + }, + "/api/v1/products/{id}": { + "get": { + "description": "Get product by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Get product by id", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto" + } + } + } + }, + "put": { + "description": "Update existing product", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Update product", + "parameters": [ + { + "description": "Product data", + "name": "UpdateProductRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto" + } + }, + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + }, + "delete": { + "description": "Delete existing product", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Products" + ], + "summary": "Delete product", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "productId": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto": { + "type": "object", + "properties": { + "productId": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto": { + "type": "object", + "properties": { + "product": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto": { + "type": "object", + "properties": { + "products": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListQuery": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FilterModel" + } + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/internal/services/catalogwriteservice/docs/swagger.yaml b/internal/services/catalogwriteservice/docs/swagger.yaml new file mode 100644 index 00000000..14a05ec0 --- /dev/null +++ b/internal/services/catalogwriteservice/docs/swagger.yaml @@ -0,0 +1,228 @@ +definitions: + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto: + properties: + createdAt: + type: string + description: + type: string + name: + type: string + price: + type: number + productId: + type: string + updatedAt: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto + : properties: + description: + type: string + name: + type: string + price: + type: number + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto + : properties: + productId: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto + : properties: + product: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto + : properties: + products: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto + : properties: + products: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto + : properties: + description: + type: string + name: + type: string + price: + type: number + type: object + utils.FilterModel: + properties: + comparison: + type: string + field: + type: string + value: + type: string + type: object + utils.ListQuery: + properties: + filters: + items: + $ref: '#/definitions/utils.FilterModel' + type: array + orderBy: + type: string + page: + type: integer + size: + type: integer + type: object + ? utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1_ProductDto + : properties: + items: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_dto_v1.ProductDto' + type: array + page: + type: integer + size: + type: integer + totalItems: + type: integer + totalPage: + type: integer + type: object +info: + contact: + name: Mehdi Hadeli + url: https://github.com/mehdihadeli + description: Catalogs Write-Service Api. + title: Catalogs Write-Service Api + version: "1.0" +paths: + /api/v1/products: + get: + consumes: + - application/json + description: Get all products + parameters: + - in: query + name: orderBy + type: string + - in: query + name: page + type: integer + - in: query + name: size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_products_v1_dtos.GetProductsResponseDto' + summary: Get all product + tags: + - Products + post: + consumes: + - application/json + description: Create new product item + parameters: + - description: Product data + in: body + name: CreateProductRequestDto + required: true + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductRequestDto' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_creating_product_v1_dtos.CreateProductResponseDto' + summary: Create product + tags: + - Products + /api/v1/products/{id}: + delete: + consumes: + - application/json + description: Delete existing product + parameters: + - description: Product ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + summary: Delete product + tags: + - Products + get: + consumes: + - application/json + description: Get product by id + parameters: + - description: Product ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_getting_product_by_id_v1_dtos.GetProductByIdResponseDto' + summary: Get product by id + tags: + - Products + put: + consumes: + - application/json + description: Update existing product + parameters: + - description: Product data + in: body + name: UpdateProductRequestDto + required: true + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_updating_product_v1_dtos.UpdateProductRequestDto' + - description: Product ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + summary: Update product + tags: + - Products + /api/v1/products/search: + get: + consumes: + - application/json + description: Search products + parameters: + - in: query + name: search + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_catalogwriteservice_internal_products_features_searching_product_v1_dtos.SearchProductsResponseDto' + summary: Search products + tags: + - Products +swagger: "2.0" diff --git a/internal/services/catalogwriteservice/go.mod b/internal/services/catalogwriteservice/go.mod new file mode 100644 index 00000000..feea5c54 --- /dev/null +++ b/internal/services/catalogwriteservice/go.mod @@ -0,0 +1,255 @@ +module github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice + +go 1.22 + +// https://go.dev/doc/tutorial/call-module-code +replace github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg => ../../pkg/ + +require ( + emperror.dev/errors v0.8.1 + github.com/brianvoe/gofakeit/v6 v6.25.0 + github.com/gavv/httpexpect/v2 v2.3.1 + github.com/glebarez/sqlite v1.10.0 + github.com/go-ozzo/ozzo-validation v3.6.0+incompatible + github.com/go-playground/validator v9.31.0+incompatible + github.com/goccy/go-json v0.10.2 + github.com/iancoleman/strcase v0.3.0 + github.com/labstack/echo/v4 v4.11.1 + github.com/lib/pq v1.10.9 + github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg v0.0.0-20230831075934-be8df319f588 + github.com/mehdihadeli/go-mediatr v1.3.0 + github.com/michaelklishin/rabbit-hole v1.5.0 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.28.0 + github.com/pterm/pterm v0.12.69 + github.com/satori/go.uuid v1.2.0 + github.com/spf13/cobra v1.7.0 + github.com/stretchr/testify v1.8.4 + github.com/swaggo/echo-swagger v1.4.1 + github.com/swaggo/swag v1.16.2 + go.opentelemetry.io/otel v1.19.0 + go.opentelemetry.io/otel/metric v1.19.0 + go.opentelemetry.io/otel/trace v1.19.0 + go.uber.org/fx v1.20.0 + google.golang.org/grpc v1.58.2 + gopkg.in/khaiql/dbcleaner.v2 v2.3.0 + gorm.io/gorm v1.25.5 +) + +require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.1.0 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/ClickHouse/ch-go v0.58.2 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.14.1 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/hcsshim v0.11.1 // indirect + github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect + github.com/ahmetb/go-linq/v3 v3.2.0 // indirect + github.com/ajg/form v1.5.1 // indirect + github.com/alexflint/go-filemutex v1.2.0 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/avast/retry-go v3.0.0+incompatible // indirect + github.com/caarlos0/env/v8 v8.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/containerd/containerd v1.7.6 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v24.0.6+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/doug-martin/goqu/v9 v9.18.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fatih/structs v1.0.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.6.1 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-testfixtures/testfixtures/v3 v3.9.0 // indirect + github.com/goccy/go-reflect v1.2.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang-migrate/migrate/v4 v4.16.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/gookit/color v1.5.4 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect + github.com/imkira/go-interpol v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.1 // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jackc/puddle v1.3.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/kamva/mgm/v3 v3.5.0 // indirect + github.com/khaiql/dbcleaner v2.3.0+incompatible // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect + github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/mcuadros/go-defaults v1.2.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/nolleh/caption_json_formatter v0.2.2 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/opencontainers/runc v1.1.9 // indirect + github.com/openzipkin/zipkin-go v0.4.2 // indirect + github.com/paulmach/orb v0.10.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pierrec/lz4/v4 v4.1.18 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/pressly/goose/v3 v3.15.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/rabbitmq/amqp091-go v1.8.1 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect + github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect + github.com/redis/go-redis/v9 v9.2.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/segmentio/asm v1.2.0 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/shirou/gopsutil/v3 v3.23.9 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/viper v1.16.0 // indirect + github.com/stretchr/objx v0.5.1 // indirect + github.com/swaggo/files/v2 v2.0.0 // indirect + github.com/testcontainers/testcontainers-go v0.25.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/ulule/limiter/v3 v3.11.2 // indirect + github.com/uptrace/bun v1.1.16 // indirect + github.com/uptrace/bun/driver/pgdriver v1.1.16 // indirect + github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3 // indirect + github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3 // indirect + github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.47.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.0 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.mongodb.org/mongo-driver v1.12.1 // indirect + go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.45.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.19.0 // indirect + go.opentelemetry.io/otel/sdk v1.19.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.19.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.12.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gorm.io/driver/postgres v1.5.2 // indirect + gorm.io/plugin/opentelemetry v0.1.4 // indirect + mellium.im/sasl v0.3.1 // indirect + modernc.org/libc v1.24.1 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.6.0 // indirect + modernc.org/sqlite v1.25.0 // indirect + moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e // indirect +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/protobuf v1.31.0 + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/internal/services/catalogwriteservice/go.sum b/internal/services/catalogwriteservice/go.sum new file mode 100644 index 00000000..1f0d24e9 --- /dev/null +++ b/internal/services/catalogwriteservice/go.sum @@ -0,0 +1,1293 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= +cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0= +emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= +github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= +github.com/ClickHouse/clickhouse-go/v2 v2.14.1 h1:5C2hhmZEGUVdy8CPpY3iPpfBv2kRbx5iOcflU49Rzws= +github.com/ClickHouse/clickhouse-go/v2 v2.14.1/go.mod h1:PHqbMvJTQ0EI4a1vJhmbmL/Ajr+Cin2O+WJjnYctJvg= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.1 h1:hJ3s7GbWlGK4YVV92sO88BQSyF4ZLVy7/awqOlPxFbA= +github.com/Microsoft/hcsshim v0.11.1/go.mod h1:nFJmaO4Zr5Y7eADdFOpYswDDlNVbvcIJJNJLECr5JQg= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= +github.com/ahmetb/go-linq/v3 v3.2.0 h1:BEuMfp+b59io8g5wYzNoFe9pWPalRklhlhbiU3hYZDE= +github.com/ahmetb/go-linq/v3 v3.2.0/go.mod h1:haQ3JfOeWK8HpVxMtHHEMPVgBKiYyQ+f1/kLZh/cj9U= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alexflint/go-filemutex v1.2.0 h1:1v0TJPDtlhgpW4nJ+GvxCLSlUDC3+gW0CQQvlmfDR/s= +github.com/alexflint/go-filemutex v1.2.0/go.mod h1:mYyQSWvw9Tx2/H2n9qXPb52tTYfE0pZAWcBq5mK025c= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +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/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/brianvoe/gofakeit/v6 v6.25.0 h1:ZpFjktOpLZUeF8q223o0rUuXtA+m5qW5srjvVi+JkXk= +github.com/brianvoe/gofakeit/v6 v6.25.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= +github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.7.6 h1:oNAVsnhPoy4BTPQivLgTzI9Oleml9l/+eYIDYXRCYo8= +github.com/containerd/containerd v1.7.6/go.mod h1:SY6lrkkuJT40BVNO37tlYTSnKJnP5AXBc0fhx0q+TJ4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +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/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= +github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= +github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY= +github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/fasthttp/websocket v1.4.3-rc.6 h1:omHqsl8j+KXpmzRjF8bmzOSYJ8GnS0E3efi1wYT+niY= +github.com/fasthttp/websocket v1.4.3-rc.6/go.mod h1:43W9OM2T8FeXpCWMsBd9Cb7nE2CACNqNvCqQCoty/Lc= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/httpexpect/v2 v2.3.1 h1:sGLlKMn8AuHS9ztK9Sb7AJ7OxIL8v2PcLdyxfKt1Fo4= +github.com/gavv/httpexpect/v2 v2.3.1/go.mod h1:yOE8m/aqFYQDNrgprMeXgq4YynfN9h1NgcE1+1suV64= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= +github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= +github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/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/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY= +github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms= +github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= +github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= +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/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.0.0 h1:HrmLyvOLJyjR0YofMw8QGdCIuYOs4TJUBDNU5sJC09E= +github.com/imkira/go-interpol v1.0.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= +github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kamva/mgm/v3 v3.5.0 h1:/2mNshpqwAC9spdzJZ0VR/UZ/SY/PsNTrMjT111KQjM= +github.com/kamva/mgm/v3 v3.5.0/go.mod h1:F4J1hZnXQMkqL3DZgR7Z7BOuiTqQG/JTic3YzliG4jk= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/khaiql/dbcleaner v2.3.0+incompatible h1:VU/ZnMcs0Dx6s4XELYfZMMyib2hyrnJ0Xk1/2aZQIqg= +github.com/khaiql/dbcleaner v2.3.0+incompatible/go.mod h1:NUURNSEp3cHXCm37Ljb/IWAdp2/qYv/HAW+1BdnEbps= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= +github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc= +github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY= +github.com/mehdihadeli/go-mediatr v1.3.0 h1:hrb5Scp/nsiR3Y62mjZ0Tc5UX/dRJl4nDFkINBEIESA= +github.com/mehdihadeli/go-mediatr v1.3.0/go.mod h1:lsG+hyH+pEOhmZiZl0KPO72BcZiEReF03CBk4GVJB0k= +github.com/michaelklishin/rabbit-hole v1.5.0 h1:Bex27BiFDsijCM9D0ezSHqyy0kehpYHuNKaPqq/a4RM= +github.com/michaelklishin/rabbit-hole v1.5.0/go.mod h1:vvI1uOitYZi0O5HEGXhaWC1XT80Gy+HvFheJ+5Krlhk= +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/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nolleh/caption_json_formatter v0.2.2 h1:EKsOr/fCllNQF2ZoajfbSDlV73BNV1bDu1aTTSRrlN0= +github.com/nolleh/caption_json_formatter v0.2.2/go.mod h1:5FYofZA8NAej/eFxa12FvyQKosU1LfyKizZPlY0JojU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA= +github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM= +github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= +github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= +github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= +github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/pressly/goose/v3 v3.15.0 h1:6tY5aDqFknY6VZkorFGgZtWygodZQxfmmEF4rqyJW9k= +github.com/pressly/goose/v3 v3.15.0/go.mod h1:LlIo3zGccjb/YUgG+Svdb9Er14vefRdlDI7URCDrwYo= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.69 h1:fBCKnB8dSLAl8FlYRQAWYGp2WTI/Xm/tKJ21Hyo9USw= +github.com/pterm/pterm v0.12.69/go.mod h1:wl06ko9MHnqxz4oDV++IORDpjCzw6+mfrvf0MPj6fdk= +github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= +github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873 h1:N3Af8f13ooDKcIhsmFT7Z05CStZWu4C7Md0uDEy4q6o= +github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= +github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= +github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= +github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= +github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= +github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= +github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/testcontainers/testcontainers-go v0.25.0 h1:erH6cQjsaJrH+rJDU9qIf89KFdhK0Bft0aEZHlYC3Vs= +github.com/testcontainers/testcontainers-go v0.25.0/go.mod h1:4sC9SiJyzD1XFi59q8umTQYWxnkweEc5OjVtTUlJzqQ= +github.com/testcontainers/testcontainers-go/modules/postgres v0.25.0 h1:8WNK1Edo9ohRYPrDCXWdoVY2cbg/oFh9y5uWZGSBESo= +github.com/testcontainers/testcontainers-go/modules/postgres v0.25.0/go.mod h1:XpwOhyUXheL31hz73L8be8maW1rQq8H48x5qZeHtYr0= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA= +github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI= +github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A= +github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8= +github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7QiNOCPLqPFJjK4= +github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= +github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3 h1:m5eNyOhch/7tyK6aN6eRRpNoD1vM8PNh64dA05X22Js= +github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3/go.mod h1:APPUXm9BbpH7NFkfpbw04raZSitzl19/3NOCu0rbI4E= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3 h1:LyGS9cIZV0YVhE81zwfMhIE2l2flcj3wn5IoK4VkbWA= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3/go.mod h1:RvCYhPchLhvQ9l9C9goblbgO7BaKt597kBMf5mgKyo0= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3 h1:2na5W81H38Z4qXCQCuzlcdSMiTWgPJ6XeZIArq6VIJE= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3/go.mod h1:9IVEh9mPv3NwFf99dVLX15FqVgdpZJ8RMDo/Cr0vK74= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= +github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= +github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vmihailenco/msgpack/v5 v5.4.0 h1:hRM0digJwyR6vll33NNAwCFguy5JuBD6jxDmQP3l608= +github.com/vmihailenco/msgpack/v5 v5.4.0/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= +go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= +go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0 h1:bldpPC7XAv7f7LKTwNfRkNdzRhjtXaWybZFFa16dAb8= +go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0/go.mod h1:xhkNpJG3D+kmuaciNTco7cdK27Fb77J9Iqcq5CMe4Y8= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= +go.opentelemetry.io/contrib/instrumentation/host v0.45.0 h1:1uzNKJDqZ6y6F5J6aKWgJjRREpKiGhBvKHlWon/bqB4= +go.opentelemetry.io/contrib/instrumentation/host v0.45.0/go.mod h1:vlqPvzDsmB4+jlERxBRXsdLCD6Q0LoBzxHqNXp3qvG4= +go.opentelemetry.io/contrib/propagators/ot v1.20.0 h1:duH7mgL6VGQH7e7QEAVOFkCQXWpCb4PjTtrhdrYrJRQ= +go.opentelemetry.io/contrib/propagators/ot v1.20.0/go.mod h1:gijQzxOq0JLj9lyZhTvqjDddGV/zaNagpPIn+2r8CEI= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 h1:ZtfnDL+tUrs1F0Pzfwbg2d59Gru9NCH3bgSHBM6LDwU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 h1:NmnYCiR0qNufkldjVvyQfZTHSdzeHoZ41zggMsdMcLM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0/go.mod h1:UVAO61+umUsHLtYb8KXXRoHtxUkdOPkYidzW3gipRLQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +go.opentelemetry.io/otel/exporters/prometheus v0.42.0 h1:jwV9iQdvp38fxXi8ZC+lNpxjK16MRcZlpDYvbuO1FiA= +go.opentelemetry.io/otel/exporters/prometheus v0.42.0/go.mod h1:f3bYiqNqhoPxkvI2LrXqQVC546K7BuRDL/kKuxkujhA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0 h1:4jJuoeOo9W6hZnz+r046fyoH5kykZPRvKfUXJVfMpB0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0/go.mod h1:/MtYTE1SfC2QIcE0bDot6fIX+h+WvXjgTqgn9P0LNPE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0 h1:EGY0h5mGliP9o/nIkVuLI0vRiQqmsYOcbwCuotksO1o= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0/go.mod h1:JQgTGJP11yi3o4GHzIWYodhPisxANdqxF1eHwDSnJrI= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= +go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= +go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/khaiql/dbcleaner.v2 v2.3.0 h1:9tRNEo5tn7MhpHySz5Rstjt7iuTl2oIMRLYlLUf1BOA= +gopkg.in/khaiql/dbcleaner.v2 v2.3.0/go.mod h1:kzKBqwVHZ7o00FtbCfDKRDNfSkPGAgDJMQ8bd6ruy30= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c= +gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/plugin/opentelemetry v0.1.4 h1:7p0ocWELjSSRI7NCKPW2mVe6h43YPini99sNJcbsTuc= +gorm.io/plugin/opentelemetry v0.1.4/go.mod h1:tndJHOdvPT0pyGhOb8E2209eXJCUxhC5UpKw7bGVWeI= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= +mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= +modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= +modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= +modernc.org/ccgo/v3 v3.16.14 h1:af6KNtFgsVmnDYrWk3PQCS9XT6BXe7o3ZFJKkIKvXNQ= +modernc.org/ccgo/v3 v3.16.14/go.mod h1:mPDSujUIaTNWQSG4eqKw+atqLOEbma6Ncsa94WbC9zo= +modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= +modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= +modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= +modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e h1:C7q+e9M5nggAvWfVg9Nl66kebKeuJlP3FD58V4RR5wo= +moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e/go.mod h1:nejbQVfXh96n9dSF6cH3Jsk/QI1Z2oEL7sSI2ifXFNA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/services/catalogwriteservice/internal/products/configurations/endpoints/endpoints.go b/internal/services/catalogwriteservice/internal/products/configurations/endpoints/endpoints.go new file mode 100644 index 00000000..940d20f5 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/configurations/endpoints/endpoints.go @@ -0,0 +1,13 @@ +package endpoints + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" +) + +func RegisterEndpoints(endpoints []route.Endpoint) error { + for _, endpoint := range endpoints { + endpoint.MapEndpoint() + } + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/configurations/mappings/mapping_profile.go b/internal/services/catalogwriteservice/internal/products/configurations/mappings/mapping_profile.go new file mode 100644 index 00000000..1eaa5ec4 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/configurations/mappings/mapping_profile.go @@ -0,0 +1,67 @@ +package mappings + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + datamodel "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + dtoV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + productsService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc/genproto" + + "google.golang.org/protobuf/types/known/timestamppb" +) + +func ConfigureProductsMappings() error { + err := mapper.CreateMap[*models.Product, *dtoV1.ProductDto]() + if err != nil { + return err + } + + err = mapper.CreateMap[*dtoV1.ProductDto, *models.Product]() + if err != nil { + return err + } + + err = mapper.CreateMap[*datamodel.ProductDataModel, *models.Product]() + if err != nil { + return err + } + + err = mapper.CreateMap[*models.Product, *datamodel.ProductDataModel]() + if err != nil { + return err + } + + err = mapper.CreateCustomMap[*dtoV1.ProductDto, *productsService.Product]( + func(product *dtoV1.ProductDto) *productsService.Product { + if product == nil { + return nil + } + return &productsService.Product{ + ProductId: product.Id.String(), + Name: product.Name, + Description: product.Description, + Price: product.Price, + CreatedAt: timestamppb.New(product.CreatedAt), + UpdatedAt: timestamppb.New(product.UpdatedAt), + } + }, + ) + if err != nil { + return err + } + + err = mapper.CreateCustomMap( + func(product *models.Product) *productsService.Product { + return &productsService.Product{ + ProductId: product.Id.String(), + Name: product.Name, + Description: product.Description, + Price: product.Price, + CreatedAt: timestamppb.New(product.CreatedAt), + UpdatedAt: timestamppb.New(product.UpdatedAt), + } + }, + ) + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/configurations/mediator/mediator.go b/internal/services/catalogwriteservice/internal/products/configurations/mediator/mediator.go new file mode 100644 index 00000000..93c3bd7c --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/configurations/mediator/mediator.go @@ -0,0 +1,14 @@ +package mediator + +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + +func RegisterMediatorHandlers(handlers []cqrs.HandlerRegisterer) error { + for _, handler := range handlers { + err := handler.RegisterHandler() + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/configurations/products_module_configurator.go b/internal/services/catalogwriteservice/internal/products/configurations/products_module_configurator.go new file mode 100644 index 00000000..7ef475a6 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/configurations/products_module_configurator.go @@ -0,0 +1,66 @@ +package configurations + +import ( + fxcontracts "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + grpcServer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/configurations/endpoints" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/configurations/mappings" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/configurations/mediator" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc" + productsservice "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc/genproto" + + googleGrpc "google.golang.org/grpc" +) + +type ProductsModuleConfigurator struct { + fxcontracts.Application +} + +func NewProductsModuleConfigurator( + fxapp fxcontracts.Application, +) *ProductsModuleConfigurator { + return &ProductsModuleConfigurator{ + Application: fxapp, + } +} + +func (c *ProductsModuleConfigurator) ConfigureProductsModule() error { + // config products mappings + err := mappings.ConfigureProductsMappings() + if err != nil { + return err + } + + // register products request handler on mediator + c.ResolveFuncWithParamTag( + mediator.RegisterMediatorHandlers, + `group:"product-handlers"`, + ) + + return nil +} + +func (c *ProductsModuleConfigurator) MapProductsEndpoints() error { + // config endpoints + c.ResolveFuncWithParamTag( + endpoints.RegisterEndpoints, + `group:"product-routes"`, + ) + + // config Products Grpc Endpoints + c.ResolveFunc( + func(catalogsGrpcServer grpcServer.GrpcServer, grpcService *grpc.ProductGrpcServiceServer) error { + catalogsGrpcServer.GrpcServiceBuilder(). + RegisterRoutes(func(server *googleGrpc.Server) { + productsservice.RegisterProductsServiceServer( + server, + grpcService, + ) + }) + + return nil + }, + ) + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/configurations/rabbitmq/rabbitmq_configurations.go b/internal/services/catalogwriteservice/internal/products/configurations/rabbitmq/rabbitmq_configurations.go new file mode 100644 index 00000000..9cffc524 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/configurations/rabbitmq/rabbitmq_configurations.go @@ -0,0 +1,17 @@ +package rabbitmq + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + producerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/events/integrationevents" +) + +func ConfigProductsRabbitMQ( + builder configurations.RabbitMQConfigurationBuilder, +) { + builder.AddProducer( + integrationevents.ProductCreatedV1{}, + func(builder producerConfigurations.RabbitMQProducerConfigurationBuilder) { + }, + ) +} diff --git a/internal/services/catalogwriteservice/internal/products/contracts/product_repository.go b/internal/services/catalogwriteservice/internal/products/contracts/product_repository.go new file mode 100644 index 00000000..d32ff762 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/contracts/product_repository.go @@ -0,0 +1,26 @@ +package contracts + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + uuid "github.com/satori/go.uuid" +) + +type ProductRepository interface { + GetAllProducts( + ctx context.Context, + listQuery *utils.ListQuery, + ) (*utils.ListResult[*models.Product], error) + SearchProducts( + ctx context.Context, + searchText string, + listQuery *utils.ListQuery, + ) (*utils.ListResult[*models.Product], error) + GetProductById(ctx context.Context, uuid uuid.UUID) (*models.Product, error) + CreateProduct(ctx context.Context, product *models.Product) (*models.Product, error) + UpdateProduct(ctx context.Context, product *models.Product) (*models.Product, error) + DeleteProductByID(ctx context.Context, uuid uuid.UUID) error +} diff --git a/internal/services/catalogwriteservice/internal/products/data/datamodels/product_data_model.go b/internal/services/catalogwriteservice/internal/products/data/datamodels/product_data_model.go new file mode 100644 index 00000000..8242d06e --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/data/datamodels/product_data_model.go @@ -0,0 +1,35 @@ +package datamodels + +import ( + "time" + + "github.com/goccy/go-json" + uuid "github.com/satori/go.uuid" + "gorm.io/gorm" +) + +// https://gorm.io/docs/conventions.html +// https://gorm.io/docs/models.html#gorm-Model + +// ProductDataModel data model +type ProductDataModel struct { + Id uuid.UUID `gorm:"primaryKey"` + Name string + Description string + Price float64 + CreatedAt time.Time `gorm:"default:current_timestamp"` + UpdatedAt time.Time + // for soft delete - https://gorm.io/docs/delete.html#Soft-Delete + gorm.DeletedAt +} + +// TableName overrides the table name used by ProductDataModel to `products` - https://gorm.io/docs/conventions.html#TableName +func (p *ProductDataModel) TableName() string { + return "products" +} + +func (p *ProductDataModel) String() string { + j, _ := json.Marshal(p) + + return string(j) +} diff --git a/internal/services/catalogwriteservice/internal/products/data/repositories/pg_product_repository.go b/internal/services/catalogwriteservice/internal/products/data/repositories/pg_product_repository.go new file mode 100644 index 00000000..f4fb675a --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/data/repositories/pg_product_repository.go @@ -0,0 +1,237 @@ +package repositories + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/data" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + utils2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/repository" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + data2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + "emperror.dev/errors" + uuid "github.com/satori/go.uuid" + attribute2 "go.opentelemetry.io/otel/attribute" + "gorm.io/gorm" +) + +type postgresProductRepository struct { + log logger.Logger + gormGenericRepository data.GenericRepository[*models.Product] + tracer tracing.AppTracer +} + +func NewPostgresProductRepository( + log logger.Logger, + db *gorm.DB, + tracer tracing.AppTracer, +) data2.ProductRepository { + gormRepository := repository.NewGenericGormRepository[*models.Product](db) + return &postgresProductRepository{ + log: log, + gormGenericRepository: gormRepository, + tracer: tracer, + } +} + +func (p *postgresProductRepository) GetAllProducts( + ctx context.Context, + listQuery *utils.ListQuery, +) (*utils.ListResult[*models.Product], error) { + ctx, span := p.tracer.Start(ctx, "postgresProductRepository.GetAllProducts") + defer span.End() + + result, err := p.gormGenericRepository.GetAll(ctx, listQuery) + err = utils2.TraceStatusFromContext( + ctx, + errors.WrapIf( + err, + "error in the paginate", + ), + ) + + if err != nil { + return nil, err + } + + p.log.Infow( + "products loaded", + logger.Fields{"ProductsResult": result}, + ) + + span.SetAttributes(attribute.Object("ProductsResult", result)) + + return result, nil +} + +func (p *postgresProductRepository) SearchProducts( + ctx context.Context, + searchText string, + listQuery *utils.ListQuery, +) (*utils.ListResult[*models.Product], error) { + ctx, span := p.tracer.Start(ctx, "postgresProductRepository.SearchProducts") + span.SetAttributes(attribute2.String("SearchText", searchText)) + defer span.End() + + result, err := p.gormGenericRepository.Search(ctx, searchText, listQuery) + err = utils2.TraceStatusFromContext( + ctx, + errors.WrapIf( + err, + "error in the paginate", + ), + ) + if err != nil { + return nil, err + } + + p.log.Infow( + fmt.Sprintf( + "products loaded for search term '%s'", + searchText, + ), + logger.Fields{"ProductsResult": result}, + ) + span.SetAttributes(attribute.Object("ProductsResult", result)) + + return result, nil +} + +func (p *postgresProductRepository) GetProductById( + ctx context.Context, + uuid uuid.UUID, +) (*models.Product, error) { + ctx, span := p.tracer.Start(ctx, "postgresProductRepository.GetProductById") + span.SetAttributes(attribute2.String("Id", uuid.String())) + defer span.End() + + product, err := p.gormGenericRepository.GetById(ctx, uuid) + err = utils2.TraceStatusFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "can't find the product with id %s into the database.", + uuid, + ), + ), + ) + + if err != nil { + return nil, err + } + + span.SetAttributes(attribute.Object("Product", product)) + p.log.Infow( + fmt.Sprintf( + "product with id %s laoded", + uuid.String(), + ), + logger.Fields{"Product": product, "Id": uuid}, + ) + + return product, nil +} + +func (p *postgresProductRepository) CreateProduct( + ctx context.Context, + product *models.Product, +) (*models.Product, error) { + ctx, span := p.tracer.Start(ctx, "postgresProductRepository.CreateProduct") + defer span.End() + + err := p.gormGenericRepository.Add(ctx, product) + err = utils2.TraceStatusFromSpan( + span, + errors.WrapIf( + err, + "error in the inserting product into the database.", + ), + ) + if err != nil { + return nil, err + } + + span.SetAttributes(attribute.Object("Product", product)) + p.log.Infow( + fmt.Sprintf( + "product with id '%s' created", + product.Id, + ), + logger.Fields{"Product": product, "Id": product.Id}, + ) + + return product, nil +} + +func (p *postgresProductRepository) UpdateProduct( + ctx context.Context, + updateProduct *models.Product, +) (*models.Product, error) { + ctx, span := p.tracer.Start(ctx, "postgresProductRepository.UpdateProduct") + defer span.End() + + err := p.gormGenericRepository.Update(ctx, updateProduct) + err = utils2.TraceStatusFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "error in updating product with id %s into the database.", + updateProduct.Id, + ), + ), + ) + + if err != nil { + return nil, err + } + + span.SetAttributes(attribute.Object("Product", updateProduct)) + p.log.Infow( + fmt.Sprintf( + "product with id '%s' updated", + updateProduct.Id, + ), + logger.Fields{ + "Product": updateProduct, + "Id": updateProduct.Id, + }, + ) + + return updateProduct, nil +} + +func (p *postgresProductRepository) DeleteProductByID( + ctx context.Context, + uuid uuid.UUID, +) error { + ctx, span := p.tracer.Start(ctx, "postgresProductRepository.UpdateProduct") + span.SetAttributes(attribute2.String("Id", uuid.String())) + defer span.End() + + err := p.gormGenericRepository.Delete(ctx, uuid) + err = utils2.TraceStatusFromSpan(span, errors.WrapIf(err, fmt.Sprintf( + "error in the deleting product with id %s into the database.", + uuid, + ))) + + if err != nil { + return err + } + + p.log.Infow( + fmt.Sprintf( + "product with id %s deleted", + uuid, + ), + logger.Fields{"Product": uuid}, + ) + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams/product_handler_params.go b/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams/product_handler_params.go new file mode 100644 index 00000000..d3eb62b8 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams/product_handler_params.go @@ -0,0 +1,19 @@ +package fxparams + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/producer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/data/dbcontext" + + "go.uber.org/fx" +) + +type ProductHandlerParams struct { + fx.In + + Log logger.Logger + CatalogsDBContext *dbcontext.CatalogsGormDBContext + RabbitmqProducer producer.Producer + Tracer tracing.AppTracer +} diff --git a/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams/product_route_params.go b/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams/product_route_params.go new file mode 100644 index 00000000..becc5954 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams/product_route_params.go @@ -0,0 +1,19 @@ +package fxparams + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/contracts" + + "github.com/go-playground/validator" + "github.com/labstack/echo/v4" + "go.uber.org/fx" +) + +type ProductRouteParams struct { + fx.In + + CatalogsMetrics *contracts.CatalogsMetrics + Logger logger.Logger + ProductsGroup *echo.Group `name:"product-echo-group"` + Validator *validator.Validate +} diff --git a/internal/services/catalogwriteservice/internal/products/dtos/v1/product_dto.go b/internal/services/catalogwriteservice/internal/products/dtos/v1/product_dto.go new file mode 100644 index 00000000..9a3a386d --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/dtos/v1/product_dto.go @@ -0,0 +1,16 @@ +package v1 + +import ( + "time" + + uuid "github.com/satori/go.uuid" +) + +type ProductDto struct { + Id uuid.UUID `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/create_product.go b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/create_product.go new file mode 100644 index 00000000..2f107ec0 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/create_product.go @@ -0,0 +1,84 @@ +package v1 + +import ( + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + + validation "github.com/go-ozzo/ozzo-validation" + uuid "github.com/satori/go.uuid" +) + +// https://echo.labstack.com/guide/request/ +// https://github.com/go-playground/validator + +type CreateProduct struct { + cqrs.Command + ProductID uuid.UUID + Name string + Description string + Price float64 + CreatedAt time.Time +} + +// NewCreateProduct Create a new product +func NewCreateProduct( + name string, + description string, + price float64, +) *CreateProduct { + command := &CreateProduct{ + Command: cqrs.NewCommandByT[CreateProduct](), + ProductID: uuid.NewV4(), + Name: name, + Description: description, + Price: price, + CreatedAt: time.Now(), + } + + return command +} + +// NewCreateProductWithValidation Create a new product with inline validation - for defensive programming and ensuring validation even without using middleware +func NewCreateProductWithValidation( + name string, + description string, + price float64, +) (*CreateProduct, error) { + command := NewCreateProduct(name, description, price) + err := command.Validate() + + return command, err +} + +func (c *CreateProduct) isTxRequest(){ +} + +func (c *CreateProduct) Validate() error { + err := validation.ValidateStruct( + c, + validation.Field(&c.ProductID, validation.Required), + validation.Field( + &c.Name, + validation.Required, + validation.Length(0, 255), + ), + validation.Field( + &c.Description, + validation.Required, + validation.Length(0, 5000), + ), + validation.Field( + &c.Price, + validation.Required, + validation.Min(0.0).Exclusive(), + ), + validation.Field(&c.CreatedAt, validation.Required), + ) + if err != nil { + return customErrors.NewValidationErrorWrap(err, "validation error") + } + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/create_product_endpoint.go b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/create_product_endpoint.go new file mode 100644 index 00000000..61d8ac78 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/create_product_endpoint.go @@ -0,0 +1,75 @@ +package v1 + +import ( + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type createProductEndpoint struct { + fxparams.ProductRouteParams +} + +func NewCreteProductEndpoint( + params fxparams.ProductRouteParams, +) route.Endpoint { + return &createProductEndpoint{ProductRouteParams: params} +} + +func (ep *createProductEndpoint) MapEndpoint() { + ep.ProductsGroup.POST("", ep.handler()) +} + +// CreateProduct +// @Tags Products +// @Summary Create product +// @Description Create new product item +// @Accept json +// @Produce json +// @Param CreateProductRequestDto body dtos.CreateProductRequestDto true "Product data" +// @Success 201 {object} dtos.CreateProductResponseDto +// @Router /api/v1/products [post] +func (ep *createProductEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + + request := &dtos.CreateProductRequestDto{} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the binding request", + ) + + return badRequestErr + } + + command, err := NewCreateProductWithValidation( + request.Name, + request.Description, + request.Price, + ) + if err != nil { + return err + } + + result, err := mediatr.Send[*CreateProduct, *dtos.CreateProductResponseDto]( + ctx, + command, + ) + if err != nil { + return errors.WithMessage( + err, + "error in sending CreateProduct", + ) + } + + return c.JSON(http.StatusCreated, result) + } +} diff --git a/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/create_product_handler.go b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/create_product_handler.go new file mode 100644 index 00000000..391efe3d --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/create_product_handler.go @@ -0,0 +1,107 @@ +package v1 + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + datamodel "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + dtosv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/events/integrationevents" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + "github.com/mehdihadeli/go-mediatr" +) + +type createProductHandler struct { + fxparams.ProductHandlerParams +} + +func NewCreateProductHandler( + params fxparams.ProductHandlerParams, +) cqrs.RequestHandlerWithRegisterer[*CreateProduct, *dtos.CreateProductResponseDto] { + return &createProductHandler{ + ProductHandlerParams: params, + } +} + +func (c *createProductHandler) RegisterHandler() error { + return mediatr.RegisterRequestHandler[*CreateProduct, *dtos.CreateProductResponseDto]( + c, + ) +} + +func (c *createProductHandler) Handle( + ctx context.Context, + command *CreateProduct, +) (*dtos.CreateProductResponseDto, error) { + product := &models.Product{ + Id: command.ProductID, + Name: command.Name, + Description: command.Description, + Price: command.Price, + CreatedAt: command.CreatedAt, + } + + var createProductResult *dtos.CreateProductResponseDto + + result, err := gormdbcontext.AddModel[*datamodel.ProductDataModel, *models.Product]( + ctx, + c.CatalogsDBContext, + product, + ) + if err != nil { + return nil, err + } + + productDto, err := mapper.Map[*dtosv1.ProductDto](result) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in the mapping ProductDto", + ) + } + + productCreated := integrationevents.NewProductCreatedV1( + productDto, + ) + + err = c.RabbitmqProducer.PublishMessage(ctx, productCreated, nil) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in publishing ProductCreated integration_events event", + ) + } + + c.Log.Infow( + fmt.Sprintf( + "ProductCreated message with messageId `%s` published to the rabbitmq broker", + productCreated.MessageId, + ), + logger.Fields{"MessageId": productCreated.MessageId}, + ) + + createProductResult = &dtos.CreateProductResponseDto{ + ProductID: product.Id, + } + + c.Log.Infow( + fmt.Sprintf( + "product with id '%s' created", + command.ProductID, + ), + logger.Fields{ + "Id": command.ProductID, + "MessageId": productCreated.MessageId, + }, + ) + + return createProductResult, err +} diff --git a/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos/create_product_request_dto.go b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos/create_product_request_dto.go new file mode 100644 index 00000000..c9ed5bb6 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos/create_product_request_dto.go @@ -0,0 +1,12 @@ +package dtos + +// https://echo.labstack.com/guide/binding/ +// https://echo.labstack.com/guide/request/ +// https://github.com/go-playground/validator + +// CreateProductRequestDto validation will handle in command level +type CreateProductRequestDto struct { + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` +} diff --git a/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos/create_product_response_dto.go b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos/create_product_response_dto.go new file mode 100644 index 00000000..7bf1f0c8 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos/create_product_response_dto.go @@ -0,0 +1,16 @@ +package dtos + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/serializer/json" + + uuid "github.com/satori/go.uuid" +) + +// https://echo.labstack.com/guide/response/ +type CreateProductResponseDto struct { + ProductID uuid.UUID `json:"productId"` +} + +func (c *CreateProductResponseDto) String() string { + return json.PrettyPrint(c) +} diff --git a/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/events/domainevents/.gitkeep b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/events/domainevents/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/events/integrationevents/product_created.go b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/events/integrationevents/product_created.go new file mode 100644 index 00000000..7461f32c --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/events/integrationevents/product_created.go @@ -0,0 +1,20 @@ +package integrationevents + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + dtoV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + + uuid "github.com/satori/go.uuid" +) + +type ProductCreatedV1 struct { + *types.Message + *dtoV1.ProductDto +} + +func NewProductCreatedV1(productDto *dtoV1.ProductDto) *ProductCreatedV1 { + return &ProductCreatedV1{ + ProductDto: productDto, + Message: types.NewMessage(uuid.NewV4().String()), + } +} diff --git a/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/delete_product.go b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/delete_product.go new file mode 100644 index 00000000..d1f36ff6 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/delete_product.go @@ -0,0 +1,45 @@ +package v1 + +import ( + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + + validation "github.com/go-ozzo/ozzo-validation" + "github.com/go-ozzo/ozzo-validation/is" + uuid "github.com/satori/go.uuid" +) + +type DeleteProduct struct { + ProductID uuid.UUID +} + +// NewDeleteProduct delete a product +func NewDeleteProduct(productID uuid.UUID) *DeleteProduct { + command := &DeleteProduct{ProductID: productID} + + return command +} + +// NewDeleteProductWithValidation delete a product with inline validation - for defensive programming and ensuring validation even without using middleware +func NewDeleteProductWithValidation(productID uuid.UUID) (*DeleteProduct, error) { + command := NewDeleteProduct(productID) + err := command.Validate() + + return command, err +} + +// IsTxRequest for enabling transactions on the mediatr pipeline +func (c *DeleteProduct) isTxRequest() { +} + +func (c *DeleteProduct) Validate() error { + err := validation.ValidateStruct( + c, + validation.Field(&c.ProductID, validation.Required), + validation.Field(&c.ProductID, is.UUIDv4), + ) + if err != nil { + return customErrors.NewValidationErrorWrap(err, "validation error") + } + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/delete_product_endpoint.go b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/delete_product_endpoint.go new file mode 100644 index 00000000..41807470 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/delete_product_endpoint.go @@ -0,0 +1,72 @@ +package v1 + +import ( + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/dtos" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type deleteProductEndpoint struct { + fxparams.ProductRouteParams +} + +func NewDeleteProductEndpoint( + params fxparams.ProductRouteParams, +) route.Endpoint { + return &deleteProductEndpoint{ProductRouteParams: params} +} + +func (ep *deleteProductEndpoint) MapEndpoint() { + ep.ProductsGroup.DELETE("/:id", ep.handler()) +} + +// DeleteProduct +// @Tags Products +// @Summary Delete product +// @Description Delete existing product +// @Accept json +// @Produce json +// @Success 204 +// @Param id path string true "Product ID" +// @Router /api/v1/products/{id} [delete] +func (ep *deleteProductEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + + request := &dtos.DeleteProductRequestDto{} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the binding request", + ) + + return badRequestErr + } + + command, err := NewDeleteProductWithValidation(request.ProductID) + if err != nil { + return err + } + + _, err = mediatr.Send[*DeleteProduct, *mediatr.Unit]( + ctx, + command, + ) + + if err != nil { + return errors.WithMessage( + err, + "error in sending DeleteProduct", + ) + } + + return c.NoContent(http.StatusNoContent) + } +} diff --git a/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/delete_product_handler.go b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/delete_product_handler.go new file mode 100644 index 00000000..09fcf2ae --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/delete_product_handler.go @@ -0,0 +1,77 @@ +package v1 + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + integrationEvents "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/events/integrationevents" + + "github.com/mehdihadeli/go-mediatr" +) + +type deleteProductHandler struct { + fxparams.ProductHandlerParams +} + +func NewDeleteProductHandler( + params fxparams.ProductHandlerParams, +) cqrs.RequestHandlerWithRegisterer[*DeleteProduct, *mediatr.Unit] { + return &deleteProductHandler{ + ProductHandlerParams: params, + } +} + +func (c *deleteProductHandler) RegisterHandler() error { + return mediatr.RegisterRequestHandler[*DeleteProduct, *mediatr.Unit]( + c, + ) +} + +// IsTxRequest for enabling transactions on the mediatr pipeline +func (c *deleteProductHandler) isTxRequest() { +} + +func (c *deleteProductHandler) Handle( + ctx context.Context, + command *DeleteProduct, +) (*mediatr.Unit, error) { + err := gormdbcontext.DeleteDataModelByID[*datamodels.ProductDataModel](ctx, c.CatalogsDBContext, command.ProductID) + if err != nil { + return nil, err + } + + productDeleted := integrationEvents.NewProductDeletedV1( + command.ProductID.String(), + ) + + if err = c.RabbitmqProducer.PublishMessage(ctx, productDeleted, nil); err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in publishing 'ProductDeleted' message", + ) + } + + c.Log.Infow( + fmt.Sprintf( + "ProductDeleted message with messageId '%s' published to the rabbitmq broker", + productDeleted.MessageId, + ), + logger.Fields{"MessageId": productDeleted.MessageId}, + ) + + c.Log.Infow( + fmt.Sprintf( + "product with id '%s' deleted", + command.ProductID, + ), + logger.Fields{"Id": command.ProductID}, + ) + + return &mediatr.Unit{}, err +} diff --git a/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/dtos/delete_product_request_dto.go b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/dtos/delete_product_request_dto.go new file mode 100644 index 00000000..5f2ecfbd --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/dtos/delete_product_request_dto.go @@ -0,0 +1,7 @@ +package dtos + +import uuid "github.com/satori/go.uuid" + +type DeleteProductRequestDto struct { + ProductID uuid.UUID `param:"id" json:"-"` +} diff --git a/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/events/integrationevents/product_deleted.go b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/events/integrationevents/product_deleted.go new file mode 100644 index 00000000..0f9d8205 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/events/integrationevents/product_deleted.go @@ -0,0 +1,16 @@ +package integrationEvents + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + + uuid "github.com/satori/go.uuid" +) + +type ProductDeletedV1 struct { + *types.Message + ProductId string `json:"productId,omitempty"` +} + +func NewProductDeletedV1(productId string) *ProductDeletedV1 { + return &ProductDeletedV1{ProductId: productId, Message: types.NewMessage(uuid.NewV4().String())} +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos/get_product_by_id_request_dto.go b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos/get_product_by_id_request_dto.go new file mode 100644 index 00000000..60e1a788 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos/get_product_by_id_request_dto.go @@ -0,0 +1,12 @@ +package dtos + +import uuid "github.com/satori/go.uuid" + +// https://echo.labstack.com/guide/binding/ +// https://echo.labstack.com/guide/request/ +// https://github.com/go-playground/validator + +// GetProductByIdRequestDto validation will handle in query level +type GetProductByIdRequestDto struct { + ProductId uuid.UUID `param:"id" json:"-"` +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos/get_product_by_id_response_dto.go b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos/get_product_by_id_response_dto.go new file mode 100644 index 00000000..ed08aac7 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos/get_product_by_id_response_dto.go @@ -0,0 +1,8 @@ +package dtos + +import dtoV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + +// https://echo.labstack.com/guide/response/ +type GetProductByIdResponseDto struct { + Product *dtoV1.ProductDto `json:"product"` +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/get_product_by_id.go b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/get_product_by_id.go new file mode 100644 index 00000000..8786510f --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/get_product_by_id.go @@ -0,0 +1,46 @@ +package v1 + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + + validation "github.com/go-ozzo/ozzo-validation" + "github.com/go-ozzo/ozzo-validation/is" + uuid "github.com/satori/go.uuid" +) + +// https://echo.labstack.com/guide/request/ +// https://github.com/go-playground/validator + +type GetProductById struct { + cqrs.Query + ProductID uuid.UUID +} + +func NewGetProductById(productId uuid.UUID) *GetProductById { + query := &GetProductById{ + Query: cqrs.NewQueryByT[GetProductById](), + ProductID: productId, + } + + return query +} + +func NewGetProductByIdWithValidation(productId uuid.UUID) (*GetProductById, error) { + query := NewGetProductById(productId) + err := query.Validate() + + return query, err +} + +func (p *GetProductById) Validate() error { + err := validation.ValidateStruct( + p, + validation.Field(&p.ProductID, validation.Required, is.UUIDv4), + ) + if err != nil { + return customErrors.NewValidationErrorWrap(err, "validation error") + } + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/get_product_by_id_endpoint.go b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/get_product_by_id_endpoint.go new file mode 100644 index 00000000..0786402a --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/get_product_by_id_endpoint.go @@ -0,0 +1,71 @@ +package v1 + +import ( + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type getProductByIdEndpoint struct { + fxparams.ProductRouteParams +} + +func NewGetProductByIdEndpoint( + params fxparams.ProductRouteParams, +) route.Endpoint { + return &getProductByIdEndpoint{ProductRouteParams: params} +} + +func (ep *getProductByIdEndpoint) MapEndpoint() { + ep.ProductsGroup.GET("/:id", ep.handler()) +} + +// GetProductByID +// @Tags Products +// @Summary Get product by id +// @Description Get product by id +// @Accept json +// @Produce json +// @Param id path string true "Product ID" +// @Success 200 {object} dtos.GetProductByIdResponseDto +// @Router /api/v1/products/{id} [get] +func (ep *getProductByIdEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + + request := &dtos.GetProductByIdRequestDto{} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the binding request", + ) + + return badRequestErr + } + + query, err := NewGetProductByIdWithValidation(request.ProductId) + if err != nil { + return err + } + + queryResult, err := mediatr.Send[*GetProductById, *dtos.GetProductByIdResponseDto]( + ctx, + query, + ) + if err != nil { + return errors.WithMessage( + err, + "error in sending GetProductById", + ) + } + + return c.JSON(http.StatusOK, queryResult) + } +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/get_product_by_id_handler.go b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/get_product_by_id_handler.go new file mode 100644 index 00000000..42fbde19 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/get_product_by_id_handler.go @@ -0,0 +1,69 @@ +package v1 + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + dtoV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + "github.com/mehdihadeli/go-mediatr" +) + +type GetProductByIDHandler struct { + fxparams.ProductHandlerParams +} + +func NewGetProductByIDHandler( + params fxparams.ProductHandlerParams, +) cqrs.RequestHandlerWithRegisterer[*GetProductById, *dtos.GetProductByIdResponseDto] { + return &GetProductByIDHandler{ + ProductHandlerParams: params, + } +} + +func (c *GetProductByIDHandler) RegisterHandler() error { + return mediatr.RegisterRequestHandler[*GetProductById, *dtos.GetProductByIdResponseDto]( + c, + ) +} + +func (c *GetProductByIDHandler) Handle( + ctx context.Context, + query *GetProductById, +) (*dtos.GetProductByIdResponseDto, error) { + product, err := gormdbcontext.FindModelByID[*datamodels.ProductDataModel, *models.Product]( + ctx, + c.CatalogsDBContext, + query.ProductID, + ) + if err != nil { + return nil, err + } + + productDto, err := mapper.Map[*dtoV1.ProductDto](product) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in the mapping product", + ) + } + + c.Log.Infow( + fmt.Sprintf( + "product with id: {%s} fetched", + query.ProductID, + ), + logger.Fields{"Id": query.ProductID.String()}, + ) + + return &dtos.GetProductByIdResponseDto{Product: productDto}, nil +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos/get_products_request_dto.go b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos/get_products_request_dto.go new file mode 100644 index 00000000..0fa267ba --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos/get_products_request_dto.go @@ -0,0 +1,12 @@ +package dtos + +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + +// https://echo.labstack.com/guide/binding/ +// https://echo.labstack.com/guide/request/ +// https://github.com/go-playground/validator + +// GetProductsRequestDto validation will handle in command level +type GetProductsRequestDto struct { + *utils.ListQuery +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos/get_products_response_dto.go b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos/get_products_response_dto.go new file mode 100644 index 00000000..154160e8 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos/get_products_response_dto.go @@ -0,0 +1,11 @@ +package dtos + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + dtoV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" +) + +// https://echo.labstack.com/guide/response/ +type GetProductsResponseDto struct { + Products *utils.ListResult[*dtoV1.ProductDto] +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/get_products.go b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/get_products.go new file mode 100644 index 00000000..16e9fd66 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/get_products.go @@ -0,0 +1,15 @@ +package v1 + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" +) + +// Ref: https://golangbot.com/inheritance/ + +type GetProducts struct { + *utils.ListQuery +} + +func NewGetProducts(query *utils.ListQuery) (*GetProducts, error) { + return &GetProducts{ListQuery: query}, nil +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/get_products_endpoint.go b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/get_products_endpoint.go new file mode 100644 index 00000000..9b9a70d5 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/get_products_endpoint.go @@ -0,0 +1,82 @@ +package v1 + +import ( + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type getProductsEndpoint struct { + fxparams.ProductRouteParams +} + +func NewGetProductsEndpoint( + params fxparams.ProductRouteParams, +) route.Endpoint { + return &getProductsEndpoint{ProductRouteParams: params} +} + +func (ep *getProductsEndpoint) MapEndpoint() { + ep.ProductsGroup.GET("", ep.handler()) +} + +// GetAllProducts +// @Tags Products +// @Summary Get all product +// @Description Get all products +// @Accept json +// @Produce json +// @Param getProductsRequestDto query dtos.GetProductsRequestDto false "GetProductsRequestDto" +// @Success 200 {object} dtos.GetProductsResponseDto +// @Router /api/v1/products [get] +func (ep *getProductsEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + + listQuery, err := utils.GetListQueryFromCtx(c) + if err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in getting data from query string", + ) + + return badRequestErr + } + + request := &dtos.GetProductsRequestDto{ListQuery: listQuery} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the binding request", + ) + + return badRequestErr + } + + query, err := NewGetProducts(request.ListQuery) + if err != nil { + return err + } + + queryResult, err := mediatr.Send[*GetProducts, *dtos.GetProductsResponseDto]( + ctx, + query, + ) + if err != nil { + return errors.WithMessage( + err, + "error in sending GetProducts", + ) + } + + return c.JSON(http.StatusOK, queryResult) + } +} diff --git a/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/get_products_handler.go b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/get_products_handler.go new file mode 100644 index 00000000..f91ef871 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/get_products_handler.go @@ -0,0 +1,66 @@ +package v1 + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/helpers/gormextensions" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + datamodel "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + dtosv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + "github.com/mehdihadeli/go-mediatr" +) + +type getProductsHandler struct { + fxparams.ProductHandlerParams +} + +func NewGetProductsHandler( + params fxparams.ProductHandlerParams, +) cqrs.RequestHandlerWithRegisterer[*GetProducts, *dtos.GetProductsResponseDto] { + return &getProductsHandler{ + ProductHandlerParams: params, + } +} + +func (c *getProductsHandler) RegisterHandler() error { + return mediatr.RegisterRequestHandler[*GetProducts, *dtos.GetProductsResponseDto]( + c, + ) +} + +func (c *getProductsHandler) Handle( + ctx context.Context, + query *GetProducts, +) (*dtos.GetProductsResponseDto, error) { + products, err := gormextensions.Paginate[*datamodel.ProductDataModel, *models.Product]( + ctx, + query.ListQuery, + c.CatalogsDBContext.DB(), + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in the fetching products", + ) + } + + listResultDto, err := utils.ListResultToListResultDto[*dtosv1.ProductDto]( + products, + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in the mapping", + ) + } + + c.Log.Info("products fetched") + + return &dtos.GetProductsResponseDto{Products: listResultDto}, nil +} diff --git a/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/dtos/search_products_request_dto.go b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/dtos/search_products_request_dto.go new file mode 100644 index 00000000..d7920bdc --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/dtos/search_products_request_dto.go @@ -0,0 +1,10 @@ +package dtos + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" +) + +type SearchProductsRequestDto struct { + SearchText string `query:"search" json:"search"` + *utils.ListQuery ` json:"listQuery"` +} diff --git a/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/dtos/search_products_response_dto.go b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/dtos/search_products_response_dto.go new file mode 100644 index 00000000..3ca3115a --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/dtos/search_products_response_dto.go @@ -0,0 +1,10 @@ +package dtos + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + dtoV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" +) + +type SearchProductsResponseDto struct { + Products *utils.ListResult[*dtoV1.ProductDto] +} diff --git a/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/search_products.go b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/search_products.go new file mode 100644 index 00000000..030ca1ca --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/search_products.go @@ -0,0 +1,39 @@ +package v1 + +import ( + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + + validation "github.com/go-ozzo/ozzo-validation" +) + +type SearchProducts struct { + SearchText string + *utils.ListQuery +} + +func NewSearchProducts(searchText string, query *utils.ListQuery) *SearchProducts { + searchProductQuery := &SearchProducts{ + SearchText: searchText, + ListQuery: query, + } + + return searchProductQuery +} + +func NewSearchProductsWithValidation(searchText string, query *utils.ListQuery) (*SearchProducts, error) { + searchProductQuery := NewSearchProducts(searchText, query) + + err := searchProductQuery.Validate() + + return searchProductQuery, err +} + +func (p *SearchProducts) Validate() error { + err := validation.ValidateStruct(p, validation.Field(&p.SearchText, validation.Required)) + if err != nil { + return customErrors.NewValidationErrorWrap(err, "validation error") + } + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/search_products_endpoint.go b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/search_products_endpoint.go new file mode 100644 index 00000000..c9106c18 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/search_products_endpoint.go @@ -0,0 +1,85 @@ +package v1 + +import ( + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/dtos" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type searchProductsEndpoint struct { + fxparams.ProductRouteParams +} + +func NewSearchProductsEndpoint( + params fxparams.ProductRouteParams, +) route.Endpoint { + return &searchProductsEndpoint{ProductRouteParams: params} +} + +func (ep *searchProductsEndpoint) MapEndpoint() { + ep.ProductsGroup.GET("/search", ep.handler()) +} + +// SearchProducts +// @Tags Products +// @Summary Search products +// @Description Search products +// @Accept json +// @Produce json +// @Param searchProductsRequestDto query dtos.SearchProductsRequestDto false "SearchProductsRequestDto" +// @Success 200 {object} dtos.SearchProductsResponseDto +// @Router /api/v1/products/search [get] +func (ep *searchProductsEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + + listQuery, err := utils.GetListQueryFromCtx(c) + if err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in getting data from query string", + ) + + return badRequestErr + } + + request := &dtos.SearchProductsRequestDto{ListQuery: listQuery} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the binding request", + ) + + return badRequestErr + } + + query, err := NewSearchProductsWithValidation( + request.SearchText, + request.ListQuery, + ) + if err != nil { + return err + } + + queryResult, err := mediatr.Send[*SearchProducts, *dtos.SearchProductsResponseDto]( + ctx, + query, + ) + if err != nil { + return errors.WithMessage( + err, + "error in sending SearchProducts", + ) + } + + return c.JSON(http.StatusOK, queryResult) + } +} diff --git a/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/search_products_handler.go b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/search_products_handler.go new file mode 100644 index 00000000..4cc3da27 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/search_products_handler.go @@ -0,0 +1,98 @@ +package v1 + +import ( + "context" + "fmt" + "reflect" + "strings" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/helpers/gormextensions" + reflectionHelper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/reflectionhelper" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + datamodel "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + dto "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + "github.com/iancoleman/strcase" + "github.com/mehdihadeli/go-mediatr" + "gorm.io/gorm" +) + +type searchProductsHandler struct { + fxparams.ProductHandlerParams +} + +func NewSearchProductsHandler( + params fxparams.ProductHandlerParams, +) cqrs.RequestHandlerWithRegisterer[*SearchProducts, *dtos.SearchProductsResponseDto] { + return &searchProductsHandler{ + ProductHandlerParams: params, + } +} + +func (c *searchProductsHandler) RegisterHandler() error { + return mediatr.RegisterRequestHandler[*SearchProducts, *dtos.SearchProductsResponseDto]( + c, + ) +} + +func (c *searchProductsHandler) Handle( + ctx context.Context, + query *SearchProducts, +) (*dtos.SearchProductsResponseDto, error) { + dbQuery := c.prepareSearchDBQuery(query) + + products, err := gormPostgres.Paginate[*datamodel.ProductDataModel, *models.Product]( + ctx, + query.ListQuery, + dbQuery, + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in searching products in the repository", + ) + } + + listResultDto, err := utils.ListResultToListResultDto[*dto.ProductDto]( + products, + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in the mapping ListResultToListResultDto", + ) + } + + c.Log.Info("products fetched") + + return &dtos.SearchProductsResponseDto{Products: listResultDto}, nil +} + +func (c *searchProductsHandler) prepareSearchDBQuery( + query *SearchProducts, +) *gorm.DB { + fields := reflectionHelper.GetAllFields( + typeMapper.GetGenericTypeByT[*datamodel.ProductDataModel](), + ) + + dbQuery := c.CatalogsDBContext.DB() + + for _, field := range fields { + if field.Type.Kind() != reflect.String { + continue + } + + dbQuery = dbQuery.Or( + fmt.Sprintf("%s LIKE ?", strcase.ToSnake(field.Name)), + "%"+strings.ToLower(query.SearchText)+"%", + ) + } + + return dbQuery +} diff --git a/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/dtos/update_product_request_dto.go b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/dtos/update_product_request_dto.go new file mode 100644 index 00000000..106cdff1 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/dtos/update_product_request_dto.go @@ -0,0 +1,12 @@ +package dtos + +import uuid "github.com/satori/go.uuid" + +// https://echo.labstack.com/guide/binding/ + +type UpdateProductRequestDto struct { + ProductID uuid.UUID `json:"-" param:"id"` + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` +} diff --git a/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/events/integrationevents/product_updated.go b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/events/integrationevents/product_updated.go new file mode 100644 index 00000000..e7d84774 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/events/integrationevents/product_updated.go @@ -0,0 +1,20 @@ +package integrationevents + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + dto "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + + uuid "github.com/satori/go.uuid" +) + +type ProductUpdatedV1 struct { + *types.Message + *dto.ProductDto +} + +func NewProductUpdatedV1(productDto *dto.ProductDto) *ProductUpdatedV1 { + return &ProductUpdatedV1{ + Message: types.NewMessage(uuid.NewV4().String()), + ProductDto: productDto, + } +} diff --git a/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/update_product.go b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/update_product.go new file mode 100644 index 00000000..fa56be08 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/update_product.go @@ -0,0 +1,75 @@ +package v1 + +import ( + "time" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + + validation "github.com/go-ozzo/ozzo-validation" + uuid "github.com/satori/go.uuid" +) + +type UpdateProduct struct { + ProductID uuid.UUID + Name string + Description string + Price float64 + UpdatedAt time.Time +} + +func NewUpdateProduct( + productID uuid.UUID, + name string, + description string, + price float64, +) *UpdateProduct { + command := &UpdateProduct{ + ProductID: productID, + Name: name, + Description: description, + Price: price, + UpdatedAt: time.Now(), + } + + return command +} + +func NewUpdateProductWithValidation( + productID uuid.UUID, + name string, + description string, + price float64, +) (*UpdateProduct, error) { + command := NewUpdateProduct(productID, name, description, price) + err := command.Validate() + + return command, err +} + +// IsTxRequest for enabling transactions on the mediatr pipeline +func (c *UpdateProduct) isTxRequest() { +} + +func (c *UpdateProduct) Validate() error { + err := validation.ValidateStruct( + c, + validation.Field(&c.ProductID, validation.Required), + validation.Field( + &c.Name, + validation.Required, + validation.Length(0, 255), + ), + validation.Field( + &c.Description, + validation.Required, + validation.Length(0, 5000), + ), + validation.Field(&c.Price, validation.Required, validation.Min(0.0)), + validation.Field(&c.UpdatedAt, validation.Required), + ) + if err != nil { + return customErrors.NewValidationErrorWrap(err, "validation error") + } + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/update_product_endpoint.go b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/update_product_endpoint.go new file mode 100644 index 00000000..18c20aa7 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/update_product_endpoint.go @@ -0,0 +1,77 @@ +package v1 + +import ( + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/dtos" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type updateProductEndpoint struct { + fxparams.ProductRouteParams +} + +func NewUpdateProductEndpoint( + params fxparams.ProductRouteParams, +) route.Endpoint { + return &updateProductEndpoint{ProductRouteParams: params} +} + +func (ep *updateProductEndpoint) MapEndpoint() { + ep.ProductsGroup.PUT("/:id", ep.handler()) +} + +// UpdateProduct +// @Tags Products +// @Summary Update product +// @Description Update existing product +// @Accept json +// @Produce json +// @Param UpdateProductRequestDto body dtos.UpdateProductRequestDto true "Product data" +// @Param id path string true "Product ID" +// @Success 204 +// @Router /api/v1/products/{id} [put] +func (ep *updateProductEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + + request := &dtos.UpdateProductRequestDto{} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "error in the binding request", + ) + + return badRequestErr + } + + command, err := NewUpdateProductWithValidation( + request.ProductID, + request.Name, + request.Description, + request.Price, + ) + if err != nil { + return err + } + + _, err = mediatr.Send[*UpdateProduct, *mediatr.Unit]( + ctx, + command, + ) + if err != nil { + return errors.WithMessage( + err, + "error in sending UpdateProduct", + ) + } + + return c.NoContent(http.StatusNoContent) + } +} diff --git a/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/update_product_handler.go b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/update_product_handler.go new file mode 100644 index 00000000..cc51dcd0 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/update_product_handler.go @@ -0,0 +1,117 @@ +package v1 + +import ( + "context" + "fmt" + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + dto "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/events/integrationevents" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + "github.com/mehdihadeli/go-mediatr" +) + +type updateProductHandler struct { + fxparams.ProductHandlerParams + cqrs.HandlerRegisterer +} + +func NewUpdateProductHandler( + params fxparams.ProductHandlerParams, +) cqrs.RequestHandlerWithRegisterer[*UpdateProduct, *mediatr.Unit] { + return &updateProductHandler{ + ProductHandlerParams: params, + } +} + +func (c *updateProductHandler) RegisterHandler() error { + return mediatr.RegisterRequestHandler[*UpdateProduct, *mediatr.Unit]( + c, + ) +} + +// IsTxRequest for enabling transactions on the mediatr pipeline +func (c *updateProductHandler) isTxRequest() { +} + +func (c *updateProductHandler) Handle( + ctx context.Context, + command *UpdateProduct, +) (*mediatr.Unit, error) { + product, err := gormdbcontext.FindModelByID[*datamodels.ProductDataModel, *models.Product]( + ctx, + c.CatalogsDBContext, + command.ProductID, + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrapWithCode( + err, + http.StatusNotFound, + fmt.Sprintf( + "product with id `%s` not found", + command.ProductID, + ), + ) + } + + product.Name = command.Name + product.Price = command.Price + product.Description = command.Description + product.UpdatedAt = command.UpdatedAt + + updatedProduct, err := gormdbcontext.UpdateModel[*datamodels.ProductDataModel, *models.Product]( + ctx, + c.CatalogsDBContext, + product, + ) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in updating product in the repository", + ) + } + + productDto, err := mapper.Map[*dto.ProductDto](updatedProduct) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in the mapping ProductDto", + ) + } + + productUpdated := integrationevents.NewProductUpdatedV1(productDto) + + err = c.RabbitmqProducer.PublishMessage(ctx, productUpdated, nil) + if err != nil { + return nil, customErrors.NewApplicationErrorWrap( + err, + "error in publishing 'ProductUpdated' message", + ) + } + + c.Log.Infow( + fmt.Sprintf( + "product with id '%s' updated", + command.ProductID, + ), + logger.Fields{"Id": command.ProductID}, + ) + + c.Log.Infow( + fmt.Sprintf( + "ProductUpdated message with messageId `%s` published to the rabbitmq broker", + productUpdated.MessageId, + ), + logger.Fields{"MessageId": productUpdated.MessageId}, + ) + + return &mediatr.Unit{}, err +} diff --git a/internal/services/catalogwriteservice/internal/products/models/product.go b/internal/services/catalogwriteservice/internal/products/models/product.go new file mode 100644 index 00000000..913c5a7a --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/models/product.go @@ -0,0 +1,17 @@ +package models + +import ( + "time" + + uuid "github.com/satori/go.uuid" +) + +// Product model +type Product struct { + Id uuid.UUID + Name string + Description string + Price float64 + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/services/catalogwriteservice/internal/products/products_fx.go b/internal/services/catalogwriteservice/internal/products/products_fx.go new file mode 100644 index 00000000..0a36bd58 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/products/products_fx.go @@ -0,0 +1,95 @@ +package products + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/repositories" + creatingproductv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1" + deletingproductv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1" + gettingproductbyidv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1" + gettingproductsv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1" + searchingproductsv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1" + updatingoroductsv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc" + + "github.com/labstack/echo/v4" + "go.uber.org/fx" +) + +var Module = fx.Module( + "productsfx", + + // Other provides + fx.Provide(repositories.NewPostgresProductRepository), + fx.Provide(grpc.NewProductGrpcService), + + fx.Provide( + fx.Annotate(func(catalogsServer contracts.EchoHttpServer) *echo.Group { + var g *echo.Group + catalogsServer.RouteBuilder(). + RegisterGroupFunc("/api/v1", func(v1 *echo.Group) { + group := v1.Group("/products") + g = group + }) + + return g + }, fx.ResultTags(`name:"product-echo-group"`)), + ), + + // add cqrs handlers to DI + fx.Provide( + cqrs.AsHandler( + creatingproductv1.NewCreateProductHandler, + "product-handlers", + ), + cqrs.AsHandler( + gettingproductsv1.NewGetProductsHandler, + "product-handlers", + ), + cqrs.AsHandler( + deletingproductv1.NewDeleteProductHandler, + "product-handlers", + ), + cqrs.AsHandler( + gettingproductbyidv1.NewGetProductByIDHandler, + "product-handlers", + ), + cqrs.AsHandler( + searchingproductsv1.NewSearchProductsHandler, + "product-handlers", + ), + cqrs.AsHandler( + updatingoroductsv1.NewUpdateProductHandler, + "product-handlers", + ), + ), + + // add endpoints to DI + fx.Provide( + route.AsRoute( + creatingproductv1.NewCreteProductEndpoint, + "product-routes", + ), + route.AsRoute( + updatingoroductsv1.NewUpdateProductEndpoint, + "product-routes", + ), + route.AsRoute( + gettingproductsv1.NewGetProductsEndpoint, + "product-routes", + ), + route.AsRoute( + searchingproductsv1.NewSearchProductsEndpoint, + "product-routes", + ), + route.AsRoute( + gettingproductbyidv1.NewGetProductByIdEndpoint, + "product-routes", + ), + route.AsRoute( + deletingproductv1.NewDeleteProductEndpoint, + "product-routes", + ), + ), +) diff --git a/internal/services/catalogwriteservice/internal/shared/app/app.go b/internal/services/catalogwriteservice/internal/shared/app/app.go new file mode 100644 index 00000000..1a612242 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/app/app.go @@ -0,0 +1,41 @@ +package app + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/configurations/catalogs" +) + +type App struct{} + +func NewApp() *App { + return &App{} +} + +func (a *App) Run() { + // configure dependencies + appBuilder := NewCatalogsWriteApplicationBuilder() + appBuilder.ProvideModule(catalogs.CatalogsServiceModule) + + app := appBuilder.Build() + + // configure application + err := app.ConfigureCatalogs() + if err != nil { + app.Logger().Fatalf("Error in ConfigureCatalogs", err) + } + + err = app.MapCatalogsEndpoints() + if err != nil { + app.Logger().Fatalf("Error in MapCatalogsEndpoints", err) + } + + app.Logger().Info("Starting catalog_service application") + app.ResolveFunc(func(tracer tracing.AppTracer) { + _, span := tracer.Start(context.Background(), "Application started") + span.End() + }) + + app.Run() +} diff --git a/internal/services/catalogwriteservice/internal/shared/app/application.go b/internal/services/catalogwriteservice/internal/shared/app/application.go new file mode 100644 index 00000000..5c7c4185 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/app/application.go @@ -0,0 +1,27 @@ +package app + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/configurations/catalogs" + + "go.uber.org/fx" +) + +type CatalogsWriteApplication struct { + *catalogs.CatalogsServiceConfigurator +} + +func NewCatalogsWriteApplication( + providers []interface{}, + decorates []interface{}, + options []fx.Option, + logger logger.Logger, + environment environment.Environment, +) *CatalogsWriteApplication { + app := fxapp.NewApplication(providers, decorates, options, logger, environment) + return &CatalogsWriteApplication{ + CatalogsServiceConfigurator: catalogs.NewCatalogsServiceConfigurator(app), + } +} diff --git a/internal/services/catalogwriteservice/internal/shared/app/application_builder.go b/internal/services/catalogwriteservice/internal/shared/app/application_builder.go new file mode 100644 index 00000000..83f68f68 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/app/application_builder.go @@ -0,0 +1,26 @@ +package app + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" +) + +type CatalogsWriteApplicationBuilder struct { + contracts.ApplicationBuilder +} + +func NewCatalogsWriteApplicationBuilder() *CatalogsWriteApplicationBuilder { + builder := &CatalogsWriteApplicationBuilder{fxapp.NewApplicationBuilder()} + + return builder +} + +func (a *CatalogsWriteApplicationBuilder) Build() *CatalogsWriteApplication { + return NewCatalogsWriteApplication( + a.GetProvides(), + a.GetDecorates(), + a.Options(), + a.Logger(), + a.Environment(), + ) +} diff --git a/internal/services/catalogwriteservice/internal/shared/app/test/test_app.go b/internal/services/catalogwriteservice/internal/shared/app/test/test_app.go new file mode 100644 index 00000000..18f97ec3 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/app/test/test_app.go @@ -0,0 +1,132 @@ +package test + +import ( + "context" + "os" + "testing" + "time" + + fxcontracts "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc" + config3 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + contracts2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration/contracts" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/bus" + config2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/gorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/configurations/catalogs" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/data/dbcontext" + productsService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc/genproto" + + "github.com/stretchr/testify/require" + gorm2 "gorm.io/gorm" +) + +type TestApp struct{} + +type TestAppResult struct { + Cfg *config.AppOptions + Bus bus.RabbitmqBus + Container fxcontracts.Container + Logger logger.Logger + RabbitmqOptions *config2.RabbitmqOptions + EchoHttpOptions *config3.EchoHttpOptions + GormOptions *gormPostgres.GormOptions + Gorm *gorm2.DB + ProductServiceClient productsService.ProductsServiceClient + GrpcClient grpc.GrpcClient + PostgresMigrationRunner contracts2.PostgresMigrationRunner + CatalogsDBContext *dbcontext.CatalogsGormDBContext +} + +func NewTestApp() *TestApp { + return &TestApp{} +} + +func (a *TestApp) Run(t *testing.T) (result *TestAppResult) { + lifetimeCtx := context.Background() + + // ref: https://github.com/uber-go/fx/blob/master/app_test.go + appBuilder := NewCatalogsWriteTestApplicationBuilder(t) + appBuilder.ProvideModule(catalogs.CatalogsServiceModule) + + appBuilder.Decorate( + rabbitmq.RabbitmqContainerOptionsDecorator(t, lifetimeCtx), + ) + appBuilder.Decorate(gorm.GormContainerOptionsDecorator(t, lifetimeCtx)) + + testApp := appBuilder.Build() + + err := testApp.ConfigureCatalogs() + if err != nil { + testApp.Logger().Fatalf("Error in ConfigureCatalogs, %s", err) + } + + err = testApp.MapCatalogsEndpoints() + if err != nil { + testApp.Logger().Fatalf("Error in MapCatalogsEndpoints, %s", err) + } + + testApp.ResolveFunc( + func(cfg *config.AppOptions, + bus bus.RabbitmqBus, + logger logger.Logger, + rabbitmqOptions *config2.RabbitmqOptions, + gormOptions *gormPostgres.GormOptions, + gorm *gorm2.DB, + catalogsDBContext *dbcontext.CatalogsGormDBContext, + echoOptions *config3.EchoHttpOptions, + grpcClient grpc.GrpcClient, + postgresMigrationRunner contracts2.PostgresMigrationRunner, + ) { + grpcConnection := grpcClient.GetGrpcConnection() + + result = &TestAppResult{ + Bus: bus, + Cfg: cfg, + Container: testApp, + Logger: logger, + RabbitmqOptions: rabbitmqOptions, + GormOptions: gormOptions, + Gorm: gorm, + CatalogsDBContext: catalogsDBContext, + EchoHttpOptions: echoOptions, + PostgresMigrationRunner: postgresMigrationRunner, + ProductServiceClient: productsService.NewProductsServiceClient( + grpcConnection, + ), + GrpcClient: grpcClient, + } + }, + ) + // we need a longer timout for up and running our testcontainers + duration := time.Second * 300 + + // short timeout for handling start hooks and setup dependencies + startCtx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + err = testApp.Start(startCtx) + if err != nil { + t.Errorf("Error starting, err: %v", err) + os.Exit(1) + } + + // waiting for grpc endpoint becomes ready in the given timeout + err = result.GrpcClient.WaitForAvailableConnection() + require.NoError(t, err) + + t.Cleanup(func() { + // short timeout for handling stop hooks + stopCtx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + err = testApp.Stop(stopCtx) + require.NoError(t, err) + }) + + return +} diff --git a/internal/services/catalogwriteservice/internal/shared/app/test/test_application.go b/internal/services/catalogwriteservice/internal/shared/app/test/test_application.go new file mode 100644 index 00000000..dfaf0444 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/app/test/test_application.go @@ -0,0 +1,44 @@ +package test + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/test" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/app" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/configurations/catalogs" + + "go.uber.org/fx" + "go.uber.org/fx/fxtest" +) + +type CatalogsWriteTestApplication struct { + *app.CatalogsWriteApplication + tb fxtest.TB +} + +func NewCatalogsWriteTestApplication( + tb fxtest.TB, + providers []interface{}, + decorates []interface{}, + options []fx.Option, + logger logger.Logger, + environment environment.Environment, +) *CatalogsWriteTestApplication { + testApp := test.NewTestApplication( + tb, + providers, + decorates, + options, + logger, + environment, + ) + + catalogApplication := &app.CatalogsWriteApplication{ + CatalogsServiceConfigurator: catalogs.NewCatalogsServiceConfigurator(testApp), + } + + return &CatalogsWriteTestApplication{ + CatalogsWriteApplication: catalogApplication, + tb: tb, + } +} diff --git a/internal/services/catalogwriteservice/internal/shared/app/test/test_application_builder.go b/internal/services/catalogwriteservice/internal/shared/app/test/test_application_builder.go new file mode 100644 index 00000000..e050d5a2 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/app/test/test_application_builder.go @@ -0,0 +1,31 @@ +package test + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/test" + + "go.uber.org/fx/fxtest" +) + +type CatalogsWriteTestApplicationBuilder struct { + contracts.ApplicationBuilder + tb fxtest.TB +} + +func NewCatalogsWriteTestApplicationBuilder(tb fxtest.TB) *CatalogsWriteTestApplicationBuilder { + return &CatalogsWriteTestApplicationBuilder{ + ApplicationBuilder: test.NewTestApplicationBuilder(tb), + tb: tb, + } +} + +func (a *CatalogsWriteTestApplicationBuilder) Build() *CatalogsWriteTestApplication { + return NewCatalogsWriteTestApplication( + a.tb, + a.GetProvides(), + a.GetDecorates(), + a.Options(), + a.Logger(), + a.Environment(), + ) +} diff --git a/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator.go b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator.go new file mode 100644 index 00000000..6e452e48 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator.go @@ -0,0 +1,104 @@ +package catalogs + +import ( + "fmt" + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + echocontracts "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" + migrationcontracts "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/infrastructure" + + "github.com/labstack/echo/v4" + "gorm.io/gorm" +) + +type CatalogsServiceConfigurator struct { + contracts.Application + infrastructureConfigurator *infrastructure.InfrastructureConfigurator + productsModuleConfigurator *configurations.ProductsModuleConfigurator +} + +func NewCatalogsServiceConfigurator( + app contracts.Application, +) *CatalogsServiceConfigurator { + infraConfigurator := infrastructure.NewInfrastructureConfigurator(app) + productModuleConfigurator := configurations.NewProductsModuleConfigurator( + app, + ) + + return &CatalogsServiceConfigurator{ + Application: app, + infrastructureConfigurator: infraConfigurator, + productsModuleConfigurator: productModuleConfigurator, + } +} + +func (ic *CatalogsServiceConfigurator) ConfigureCatalogs() error { + // Shared + // Infrastructure + ic.infrastructureConfigurator.ConfigInfrastructures() + + // Shared + // Catalogs configurations + ic.ResolveFunc( + func(db *gorm.DB, postgresMigrationRunner migrationcontracts.PostgresMigrationRunner) error { + err := ic.migrateCatalogs(postgresMigrationRunner) + if err != nil { + return err + } + + if ic.Environment() != environment.Test { + err = ic.seedCatalogs(db) + if err != nil { + return err + } + } + + return nil + }, + ) + + // Modules + // Product module + err := ic.productsModuleConfigurator.ConfigureProductsModule() + + return err +} + +func (ic *CatalogsServiceConfigurator) MapCatalogsEndpoints() error { + // Shared + ic.ResolveFunc( + func(catalogsServer echocontracts.EchoHttpServer, options *config.AppOptions) error { + catalogsServer.SetupDefaultMiddlewares() + + // config catalogs root endpoint + catalogsServer.RouteBuilder(). + RegisterRoutes(func(e *echo.Echo) { + e.GET("", func(ec echo.Context) error { + return ec.String( + http.StatusOK, + fmt.Sprintf( + "%s is running...", + options.GetMicroserviceNameUpper(), + ), + ) + }) + }) + + // config catalogs swagger + ic.configSwagger(catalogsServer.RouteBuilder()) + + return nil + }, + ) + + // Modules + // Products CatalogsServiceModule endpoints + err := ic.productsModuleConfigurator.MapProductsEndpoints() + + return err +} diff --git a/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator_migration.go b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator_migration.go new file mode 100644 index 00000000..08c6884f --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator_migration.go @@ -0,0 +1,40 @@ +package catalogs + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + "gorm.io/gorm" +) + +func (ic *CatalogsServiceConfigurator) migrateCatalogs( + runner contracts.PostgresMigrationRunner, +) error { + // - for complex migration and ability to back-track to specific migration revision it is better we use `goose`, but if we want to use built-in gorm migration we can also sync gorm with `atlas` integration migration versioning for getting migration history from grom changes + // - here I used goose for migration, with using cmd/migration file + + // migration with Goorse + return migrateGoose(runner) +} + +func migrateGoose( + runner contracts.PostgresMigrationRunner, +) error { + err := runner.Up(context.Background(), 0) + + return err +} + +func migrateGorm( + db *gorm.DB, +) error { + // https://atlasgo.io/guides/orms/gorm + err := db.AutoMigrate(&models.Product{}) + if err != nil { + return err + } + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator_seed.go b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator_seed.go new file mode 100644 index 00000000..17e46f82 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator_seed.go @@ -0,0 +1,104 @@ +package catalogs + +import ( + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/testfixture" + datamodel "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + + "emperror.dev/errors" + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + "gorm.io/gorm" +) + +func (ic *CatalogsServiceConfigurator) seedCatalogs( + db *gorm.DB, +) error { + err := seedDataManually(db) + if err != nil { + return err + } + + return nil +} + +func seedDataManually(gormDB *gorm.DB) error { + var count int64 + + // https://gorm.io/docs/advanced_query.html#Count + gormDB.Model(&datamodel.ProductDataModel{}).Count(&count) + if count > 0 { + return nil + } + + products := []*datamodel.ProductDataModel{ + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + } + + err := gormDB.CreateInBatches(products, len(products)).Error + if err != nil { + return errors.Wrap(err, "error in seed database") + } + + return nil +} + +func seedDataWithFixture(gormDB *gorm.DB) error { + var count int64 + + // https://gorm.io/docs/advanced_query.html#Count + gormDB.Model(&datamodel.ProductDataModel{}).Count(&count) + if count > 0 { + return nil + } + + db, err := gormDB.DB() + if err != nil { + return errors.WrapIf(err, "error in seed database") + } + + // https://github.com/go-testfixtures/testfixtures#templating + // seed data + var data []struct { + Name string + ProductId uuid.UUID + Description string + } + + f := []struct { + Name string + ProductId uuid.UUID + Description string + }{ + {gofakeit.Name(), uuid.NewV4(), gofakeit.AdjectiveDescriptive()}, + {gofakeit.Name(), uuid.NewV4(), gofakeit.AdjectiveDescriptive()}, + } + + data = append(data, f...) + + err = testfixture.RunPostgresFixture( + db, + []string{"db/fixtures/products"}, + map[string]interface{}{ + "Products": data, + }) + if err != nil { + return errors.WrapIf(err, "error in seed database") + } + + return nil +} diff --git a/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator_swagger.go b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator_swagger.go new file mode 100644 index 00000000..9d5fd8e4 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_configurator_swagger.go @@ -0,0 +1,20 @@ +package catalogs + +import ( + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/docs" + + "github.com/labstack/echo/v4" + echoSwagger "github.com/swaggo/echo-swagger" +) + +func (ic *CatalogsServiceConfigurator) configSwagger(routeBuilder *customEcho.RouteBuilder) { + // https://github.com/swaggo/swag#how-to-use-it-with-gin + docs.SwaggerInfo.Version = "1.0" + docs.SwaggerInfo.Title = "Catalogs Write-Service Api" + docs.SwaggerInfo.Description = "Catalogs Write-Service Api." + + routeBuilder.RegisterRoutes(func(e *echo.Echo) { + e.GET("/swagger/*", echoSwagger.WrapHandler) + }) +} diff --git a/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_fx.go b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_fx.go new file mode 100644 index 00000000..c92c925f --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/catalogs_fx.go @@ -0,0 +1,163 @@ +package catalogs + +import ( + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/infrastructure" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/data" + + "go.opentelemetry.io/otel/metric" + api "go.opentelemetry.io/otel/metric" + "go.uber.org/fx" +) + +// https://pmihaylov.com/shared-components-go-microservices/ +var CatalogsServiceModule = fx.Module( + "catalogsfx", + // Shared Modules + config.Module, + infrastructure.Module, + data.Module, + + // Features Modules + products.Module, + + // Other provides + fx.Provide(provideCatalogsMetrics), +) + +// ref: https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go +func provideCatalogsMetrics( + cfg *config.AppOptions, + meter metric.Meter, +) (*contracts.CatalogsMetrics, error) { + if meter == nil { + return nil, nil + } + + createProductGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_create_product_grpc_requests_total", cfg.ServiceName), + api.WithDescription("The total number of create product grpc requests"), + ) + if err != nil { + return nil, err + } + + updateProductGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_update_product_grpc_requests_total", cfg.ServiceName), + api.WithDescription("The total number of update product grpc requests"), + ) + if err != nil { + return nil, err + } + + deleteProductGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_delete_product_grpc_requests_total", cfg.ServiceName), + api.WithDescription("The total number of delete product grpc requests"), + ) + if err != nil { + return nil, err + } + + getProductByIdGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf( + "%s_get_product_by_id_grpc_requests_total", + cfg.ServiceName, + ), + api.WithDescription( + "The total number of get product by id grpc requests", + ), + ) + if err != nil { + return nil, err + } + + searchProductGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_search_product_grpc_requests_total", cfg.ServiceName), + api.WithDescription("The total number of search product grpc requests"), + ) + if err != nil { + return nil, err + } + + createProductRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf( + "%s_create_product_rabbitmq_messages_total", + cfg.ServiceName, + ), + api.WithDescription( + "The total number of create product rabbirmq messages", + ), + ) + if err != nil { + return nil, err + } + + updateProductRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf( + "%s_update_product_rabbitmq_messages_total", + cfg.ServiceName, + ), + api.WithDescription( + "The total number of update product rabbirmq messages", + ), + ) + if err != nil { + return nil, err + } + + deleteProductRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf( + "%s_delete_product_rabbitmq_messages_total", + cfg.ServiceName, + ), + api.WithDescription( + "The total number of delete product rabbirmq messages", + ), + ) + if err != nil { + return nil, err + } + + successRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf( + "%s_search_product_rabbitmq_messages_total", + cfg.ServiceName, + ), + api.WithDescription( + "The total number of success rabbitmq processed messages", + ), + ) + if err != nil { + return nil, err + } + + errorRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf( + "%s_error_rabbitmq_processed_messages_total", + cfg.ServiceName, + ), + api.WithDescription( + "The total number of error rabbitmq processed messages", + ), + ) + if err != nil { + return nil, err + } + + return &contracts.CatalogsMetrics{ + CreateProductRabbitMQMessages: createProductRabbitMQMessages, + GetProductByIdGrpcRequests: getProductByIdGrpcRequests, + CreateProductGrpcRequests: createProductGrpcRequests, + DeleteProductRabbitMQMessages: deleteProductRabbitMQMessages, + DeleteProductGrpcRequests: deleteProductGrpcRequests, + ErrorRabbitMQMessages: errorRabbitMQMessages, + SearchProductGrpcRequests: searchProductGrpcRequests, + SuccessRabbitMQMessages: successRabbitMQMessages, + UpdateProductRabbitMQMessages: updateProductRabbitMQMessages, + UpdateProductGrpcRequests: updateProductGrpcRequests, + }, nil +} diff --git a/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_configurator.go b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_configurator.go new file mode 100644 index 00000000..e3ab1013 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_configurator.go @@ -0,0 +1,50 @@ +package infrastructure + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + loggingpipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/pipelines" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics" + metricspipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics/mediatr/pipelines" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + tracingpipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/mediatr/pipelines" + postgrespipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/pipelines" + validationpieline "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/validation/pipeline" + + "github.com/mehdihadeli/go-mediatr" + "gorm.io/gorm" +) + +type InfrastructureConfigurator struct { + contracts.Application +} + +func NewInfrastructureConfigurator( + fxapp contracts.Application, +) *InfrastructureConfigurator { + return &InfrastructureConfigurator{ + Application: fxapp, + } +} + +func (ic *InfrastructureConfigurator) ConfigInfrastructures() { + ic.ResolveFunc( + func(l logger.Logger, tracer tracing.AppTracer, metrics metrics.AppMetrics, db *gorm.DB) error { + err := mediatr.RegisterRequestPipelineBehaviors( + loggingpipelines.NewMediatorLoggingPipeline(l), + validationpieline.NewMediatorValidationPipeline(l), + tracingpipelines.NewMediatorTracingPipeline( + tracer, + tracingpipelines.WithLogger(l), + ), + metricspipelines.NewMediatorMetricsPipeline( + metrics, + metricspipelines.WithLogger(l), + ), + postgrespipelines.NewMediatorTransactionPipeline(l, db), + ) + + return err + }, + ) +} diff --git a/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_fx.go b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_fx.go new file mode 100644 index 00000000..63774d0e --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/configurations/catalogs/infrastructure/infrastructure_fx.go @@ -0,0 +1,45 @@ +package infrastructure + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/migration/goose" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresmessaging" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + rabbitmq2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/configurations/rabbitmq" + + "github.com/go-playground/validator" + "go.uber.org/fx" +) + +// https://pmihaylov.com/shared-components-go-microservices/ + +var Module = fx.Module( + "infrastructurefx", + // Modules + core.Module, + customEcho.Module, + grpc.Module, + postgresgorm.Module, + postgresmessaging.Module, + goose.Module, + rabbitmq.ModuleFunc( + func() configurations.RabbitMQConfigurationBuilderFuc { + return func(builder configurations.RabbitMQConfigurationBuilder) { + rabbitmq2.ConfigProductsRabbitMQ(builder) + } + }, + ), + health.Module, + tracing.Module, + metrics.Module, + + // Other provides + fx.Provide(validator.New), +) diff --git a/internal/services/catalogwriteservice/internal/shared/contracts/catalogs_metrics.go b/internal/services/catalogwriteservice/internal/shared/contracts/catalogs_metrics.go new file mode 100644 index 00000000..7db30b63 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/contracts/catalogs_metrics.go @@ -0,0 +1,18 @@ +package contracts + +import ( + "go.opentelemetry.io/otel/metric" +) + +type CatalogsMetrics struct { + CreateProductGrpcRequests metric.Float64Counter + UpdateProductGrpcRequests metric.Float64Counter + DeleteProductGrpcRequests metric.Float64Counter + GetProductByIdGrpcRequests metric.Float64Counter + SearchProductGrpcRequests metric.Float64Counter + SuccessRabbitMQMessages metric.Float64Counter + ErrorRabbitMQMessages metric.Float64Counter + CreateProductRabbitMQMessages metric.Float64Counter + UpdateProductRabbitMQMessages metric.Float64Counter + DeleteProductRabbitMQMessages metric.Float64Counter +} diff --git a/internal/services/catalogwriteservice/internal/shared/data/data_fx.go b/internal/services/catalogwriteservice/internal/shared/data/data_fx.go new file mode 100644 index 00000000..c2eb21c5 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/data/data_fx.go @@ -0,0 +1,18 @@ +package data + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/data/dbcontext" + + "go.uber.org/fx" +) + +// https://uber-go.github.io/fx/modules.html +var Module = fx.Module( + "datafx", + // - order is not important in provide + // - provide can have parameter and will resolve if registered + // - execute its func only if it requested + fx.Provide( + dbcontext.NewCatalogsDBContext, + ), +) diff --git a/internal/services/catalogwriteservice/internal/shared/data/dbcontext/catalogs_dbcontext.go b/internal/services/catalogwriteservice/internal/shared/data/dbcontext/catalogs_dbcontext.go new file mode 100644 index 00000000..c0edb8e7 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/data/dbcontext/catalogs_dbcontext.go @@ -0,0 +1,20 @@ +package dbcontext + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + + "gorm.io/gorm" +) + +type CatalogsGormDBContext struct { + // our dbcontext base + contracts.GormDBContext +} + +func NewCatalogsDBContext(db *gorm.DB) *CatalogsGormDBContext { + // initialize base GormContext + c := &CatalogsGormDBContext{GormDBContext: gormdbcontext.NewGormDBContext(db)} + + return c +} diff --git a/internal/services/catalogwriteservice/internal/shared/data/dbcontext/catalogs_dbcontext_test.go b/internal/services/catalogwriteservice/internal/shared/data/dbcontext/catalogs_dbcontext_test.go new file mode 100644 index 00000000..494670a0 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/data/dbcontext/catalogs_dbcontext_test.go @@ -0,0 +1,289 @@ +//go:build unit +// +build unit + +package dbcontext + +import ( + "context" + "os" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/fxlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/zap" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/scopes" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/configurations/mappings" + datamodel "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + "emperror.dev/errors" + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/suite" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" + "gorm.io/gorm" +) + +// Define the suite +type DBContextTestSuite struct { + suite.Suite + items []*datamodel.ProductDataModel + dbContext *CatalogsGormDBContext + app *fxtest.App + dbFilePath string +} + +// In order for 'go test' to run this suite, we need to create +// a normal test function and pass our suite to suite.Run +func TestDBContextTestSuite(t *testing.T) { + suite.Run(t, new(DBContextTestSuite)) +} + +func (s *DBContextTestSuite) Test_FindProductByID() { + s.Require().NotNil(s.dbContext) + + id := s.items[0].Id + + p, err := gormdbcontext.FindModelByID[*datamodel.ProductDataModel, *models.Product]( + context.Background(), + s.dbContext, + id, + ) + s.Require().NoError(err) + s.Require().NotNil(p) + + s.Assert().Equal(p.Id, id) +} + +func (s *DBContextTestSuite) Test_ExistsProductByID() { + s.Require().NotNil(s.dbContext) + + id := s.items[0].Id + + exist := gormdbcontext.Exists[*datamodel.ProductDataModel]( + context.Background(), + s.dbContext, + id, + ) + s.Require().True(exist) +} + +func (s *DBContextTestSuite) Test_NoneExistsProductByID() { + s.Require().NotNil(s.dbContext) + + id := uuid.NewV4() + + exist := gormdbcontext.Exists[*datamodel.ProductDataModel]( + context.Background(), + s.dbContext, + id, + ) + + s.Require().False(exist) +} + +func (s *DBContextTestSuite) Test_DeleteProductByID() { + s.Require().NotNil(s.dbContext) + + id := s.items[0].Id + + err := gormdbcontext.DeleteDataModelByID[*datamodel.ProductDataModel]( + context.Background(), + s.dbContext, + id, + ) + s.Require().NoError(err) + + p, err := gormdbcontext.FindModelByID[*datamodel.ProductDataModel, *models.Product]( + context.Background(), + s.dbContext, + id, + ) + s.Require().Error(err) + s.Require().Nil(p) + + // https://gorm.io/docs/delete.html#Find-soft-deleted-records + var softDeletedProduct *datamodel.ProductDataModel + s.dbContext.DB().Scopes(scopes.FilterAllItemsWithSoftDeleted).First(&softDeletedProduct, id) + s.Require().NotNil(softDeletedProduct) + + var deletedCount int64 + var allCount int64 + + // https://gorm.io/docs/advanced_query.html#Count + s.dbContext.DB().Model(&datamodel.ProductDataModel{}).Scopes(scopes.FilterAllItemsWithSoftDeleted).Count(&allCount) + s.Equal(allCount, int64(2)) + + s.dbContext.DB().Model(&datamodel.ProductDataModel{}).Scopes(scopes.SoftDeleted).Count(&deletedCount) + s.Equal(deletedCount, int64(1)) +} + +func (s *DBContextTestSuite) Test_CreateProduct() { + s.Require().NotNil(s.dbContext) + + item := &models.Product{ + Id: uuid.NewV4(), + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + } + + res, err := gormdbcontext.AddModel[*datamodel.ProductDataModel, *models.Product]( + context.Background(), + s.dbContext, + item, + ) + s.Require().NoError(err) + + p, err := gormdbcontext.FindModelByID[*datamodel.ProductDataModel, *models.Product]( + context.Background(), + s.dbContext, + item.Id, + ) + s.Require().NoError(err) + s.Require().NotNil(p) + + s.Assert().Equal(p.Id, item.Id) + s.Assert().Equal(p.Id, res.Id) +} + +func (s *DBContextTestSuite) Test_UpdateProduct() { + s.Require().NotNil(s.dbContext) + + id := s.items[0].Id + + p, err := gormdbcontext.FindModelByID[*datamodel.ProductDataModel, *models.Product]( + context.Background(), + s.dbContext, + id, + ) + s.Require().NoError(err) + + newName := gofakeit.Name() + item := p + item.Name = newName + + res, err := gormdbcontext.UpdateModel[*datamodel.ProductDataModel, *models.Product]( + context.Background(), + s.dbContext, + item, + ) + s.Require().NoError(err) + + p2, err := gormdbcontext.FindModelByID[*datamodel.ProductDataModel, *models.Product]( + context.Background(), + s.dbContext, + id, + ) + s.Require().NoError(err) + + s.Assert().Equal(item.Name, p2.Name) + s.Assert().Equal(res.Name, p2.Name) +} + +// TestSuite Hooks + +func (s *DBContextTestSuite) SetupTest() { + err := mappings.ConfigureProductsMappings() + s.Require().NoError(err) + + var gormDBContext *CatalogsGormDBContext + var gormOptions *gormPostgres.GormOptions + + app := fxtest.New( + s.T(), + config.ModuleFunc(environment.Test), + zap.Module, + fxlog.FxLogger, + gormPostgres.Module, + fx.Decorate( + func(cfg *gormPostgres.GormOptions) (*gormPostgres.GormOptions, error) { + // using sql-lite with a database file + cfg.UseSQLLite = true + + return cfg, nil + }, + ), + fx.Provide(NewCatalogsDBContext), + fx.Populate(&gormDBContext), + fx.Populate(&gormOptions), + ).RequireStart() + + s.app = app + s.dbContext = gormDBContext + s.dbFilePath = gormOptions.Dns() + + s.initDB() +} + +func (s *DBContextTestSuite) TearDownTest() { + err := s.cleanupDB() + s.Require().NoError(err) + + mapper.ClearMappings() + + s.app.RequireStop() +} + +func (s *DBContextTestSuite) initDB() { + err := migrateGorm(s.dbContext.DB()) + s.Require().NoError(err) + + products, err := seedData(s.dbContext.DB()) + s.Require().NoError(err) + + s.items = products +} + +func (s *DBContextTestSuite) cleanupDB() error { + sqldb, _ := s.dbContext.DB().DB() + e := sqldb.Close() + s.Require().NoError(e) + + // removing sql-lite file + err := os.Remove(s.dbFilePath) + + return err +} + +func migrateGorm(db *gorm.DB) error { + err := db.AutoMigrate(&datamodel.ProductDataModel{}) + if err != nil { + return err + } + + return nil +} + +func seedData(gormDB *gorm.DB) ([]*datamodel.ProductDataModel, error) { + products := []*datamodel.ProductDataModel{ + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + } + + // seed data + err := gormDB.CreateInBatches(products, len(products)).Error + if err != nil { + return nil, errors.Wrap(err, "error in seed database") + } + + return products, nil +} diff --git a/internal/services/catalogwriteservice/internal/shared/data/scopes/scopes.go b/internal/services/catalogwriteservice/internal/shared/data/scopes/scopes.go new file mode 100644 index 00000000..76877700 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/data/scopes/scopes.go @@ -0,0 +1,3 @@ +package scopes + +// After scopes, we should have a runner function like Find, Update, Delete diff --git a/internal/services/catalogwriteservice/internal/shared/grpc/genproto/products.pb.go b/internal/services/catalogwriteservice/internal/shared/grpc/genproto/products.pb.go new file mode 100644 index 00000000..579f3c31 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/grpc/genproto/products.pb.go @@ -0,0 +1,649 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v4.23.4 +// source: catalogwriteservice/products.proto + +package products_service + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Product struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=Id,proto3" json:"Id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=ShortTypeName,proto3" json:"ShortTypeName,omitempty"` + Description string `protobuf:"bytes,3,opt,name=Description,proto3" json:"Description,omitempty"` + Price float64 `protobuf:"fixed64,4,opt,name=Price,proto3" json:"Price,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` +} + +func (x *Product) Reset() { + *x = Product{} + if protoimpl.UnsafeEnabled { + mi := &file_catalog_write_service_products_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Product) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Product) ProtoMessage() {} + +func (x *Product) ProtoReflect() protoreflect.Message { + mi := &file_catalog_write_service_products_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Product.ProtoReflect.Descriptor instead. +func (*Product) Descriptor() ([]byte, []int) { + return file_catalog_write_service_products_proto_rawDescGZIP(), []int{0} +} + +func (x *Product) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *Product) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Product) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Product) GetPrice() float64 { + if x != nil { + return x.Price + } + return 0 +} + +func (x *Product) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Product) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +type CreateProductReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=ShortTypeName,proto3" json:"ShortTypeName,omitempty"` + Description string `protobuf:"bytes,2,opt,name=Description,proto3" json:"Description,omitempty"` + Price float64 `protobuf:"fixed64,3,opt,name=Price,proto3" json:"Price,omitempty"` +} + +func (x *CreateProductReq) Reset() { + *x = CreateProductReq{} + if protoimpl.UnsafeEnabled { + mi := &file_catalog_write_service_products_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateProductReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateProductReq) ProtoMessage() {} + +func (x *CreateProductReq) ProtoReflect() protoreflect.Message { + mi := &file_catalog_write_service_products_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateProductReq.ProtoReflect.Descriptor instead. +func (*CreateProductReq) Descriptor() ([]byte, []int) { + return file_catalog_write_service_products_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateProductReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateProductReq) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *CreateProductReq) GetPrice() float64 { + if x != nil { + return x.Price + } + return 0 +} + +type CreateProductRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=Id,proto3" json:"Id,omitempty"` +} + +func (x *CreateProductRes) Reset() { + *x = CreateProductRes{} + if protoimpl.UnsafeEnabled { + mi := &file_catalog_write_service_products_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateProductRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateProductRes) ProtoMessage() {} + +func (x *CreateProductRes) ProtoReflect() protoreflect.Message { + mi := &file_catalog_write_service_products_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateProductRes.ProtoReflect.Descriptor instead. +func (*CreateProductRes) Descriptor() ([]byte, []int) { + return file_catalog_write_service_products_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateProductRes) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +type UpdateProductReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=Id,proto3" json:"Id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=ShortTypeName,proto3" json:"ShortTypeName,omitempty"` + Description string `protobuf:"bytes,3,opt,name=Description,proto3" json:"Description,omitempty"` + Price float64 `protobuf:"fixed64,4,opt,name=Price,proto3" json:"Price,omitempty"` +} + +func (x *UpdateProductReq) Reset() { + *x = UpdateProductReq{} + if protoimpl.UnsafeEnabled { + mi := &file_catalog_write_service_products_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateProductReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateProductReq) ProtoMessage() {} + +func (x *UpdateProductReq) ProtoReflect() protoreflect.Message { + mi := &file_catalog_write_service_products_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateProductReq.ProtoReflect.Descriptor instead. +func (*UpdateProductReq) Descriptor() ([]byte, []int) { + return file_catalog_write_service_products_proto_rawDescGZIP(), []int{3} +} + +func (x *UpdateProductReq) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *UpdateProductReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateProductReq) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *UpdateProductReq) GetPrice() float64 { + if x != nil { + return x.Price + } + return 0 +} + +type UpdateProductRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UpdateProductRes) Reset() { + *x = UpdateProductRes{} + if protoimpl.UnsafeEnabled { + mi := &file_catalog_write_service_products_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateProductRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateProductRes) ProtoMessage() {} + +func (x *UpdateProductRes) ProtoReflect() protoreflect.Message { + mi := &file_catalog_write_service_products_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateProductRes.ProtoReflect.Descriptor instead. +func (*UpdateProductRes) Descriptor() ([]byte, []int) { + return file_catalog_write_service_products_proto_rawDescGZIP(), []int{4} +} + +type GetProductByIdReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=Id,proto3" json:"Id,omitempty"` +} + +func (x *GetProductByIdReq) Reset() { + *x = GetProductByIdReq{} + if protoimpl.UnsafeEnabled { + mi := &file_catalog_write_service_products_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetProductByIdReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProductByIdReq) ProtoMessage() {} + +func (x *GetProductByIdReq) ProtoReflect() protoreflect.Message { + mi := &file_catalog_write_service_products_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProductByIdReq.ProtoReflect.Descriptor instead. +func (*GetProductByIdReq) Descriptor() ([]byte, []int) { + return file_catalog_write_service_products_proto_rawDescGZIP(), []int{5} +} + +func (x *GetProductByIdReq) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +type GetProductByIdRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Product *Product `protobuf:"bytes,1,opt,name=Product,proto3" json:"Product,omitempty"` +} + +func (x *GetProductByIdRes) Reset() { + *x = GetProductByIdRes{} + if protoimpl.UnsafeEnabled { + mi := &file_catalog_write_service_products_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetProductByIdRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProductByIdRes) ProtoMessage() {} + +func (x *GetProductByIdRes) ProtoReflect() protoreflect.Message { + mi := &file_catalog_write_service_products_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProductByIdRes.ProtoReflect.Descriptor instead. +func (*GetProductByIdRes) Descriptor() ([]byte, []int) { + return file_catalog_write_service_products_proto_rawDescGZIP(), []int{6} +} + +func (x *GetProductByIdRes) GetProduct() *Product { + if x != nil { + return x.Product + } + return nil +} + +var File_catalog_write_service_products_proto protoreflect.FileDescriptor + +var file_catalog_write_service_products_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, + 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe7, 0x01, 0x0a, 0x07, 0x50, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, + 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x22, 0x5e, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, + 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x50, 0x72, + 0x69, 0x63, 0x65, 0x22, 0x30, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x49, 0x64, 0x22, 0x7c, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, + 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x50, 0x72, + 0x69, 0x63, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x22, 0x31, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x12, 0x1c, 0x0a, 0x09, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x11, 0x47, 0x65, + 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x73, 0x12, + 0x33, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x07, 0x50, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x32, 0x9f, 0x02, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x57, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x22, 0x2e, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, + 0x73, 0x12, 0x57, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x5f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x73, 0x12, 0x5a, 0x0a, 0x0e, 0x47, 0x65, + 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x79, 0x49, 0x64, 0x12, 0x23, 0x2e, 0x70, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, + 0x71, 0x1a, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x5f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, + 0x79, 0x49, 0x64, 0x52, 0x65, 0x73, 0x42, 0x15, 0x5a, 0x13, 0x2e, 0x2f, 0x3b, 0x70, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_catalog_write_service_products_proto_rawDescOnce sync.Once + file_catalog_write_service_products_proto_rawDescData = file_catalog_write_service_products_proto_rawDesc +) + +func file_catalog_write_service_products_proto_rawDescGZIP() []byte { + file_catalog_write_service_products_proto_rawDescOnce.Do(func() { + file_catalog_write_service_products_proto_rawDescData = protoimpl.X.CompressGZIP(file_catalog_write_service_products_proto_rawDescData) + }) + return file_catalog_write_service_products_proto_rawDescData +} + +var ( + file_catalog_write_service_products_proto_msgTypes = make([]protoimpl.MessageInfo, 7) + file_catalog_write_service_products_proto_goTypes = []interface{}{ + (*Product)(nil), // 0: products_service.Product + (*CreateProductReq)(nil), // 1: products_service.CreateProductReq + (*CreateProductRes)(nil), // 2: products_service.CreateProductRes + (*UpdateProductReq)(nil), // 3: products_service.UpdateProductReq + (*UpdateProductRes)(nil), // 4: products_service.UpdateProductRes + (*GetProductByIdReq)(nil), // 5: products_service.GetProductByIdReq + (*GetProductByIdRes)(nil), // 6: products_service.GetProductByIdRes + (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp + } +) + +var file_catalog_write_service_products_proto_depIdxs = []int32{ + 7, // 0: products_service.Product.CreatedAt:type_name -> google.protobuf.Timestamp + 7, // 1: products_service.Product.UpdatedAt:type_name -> google.protobuf.Timestamp + 0, // 2: products_service.GetProductByIdRes.Product:type_name -> products_service.Product + 1, // 3: products_service.ProductsService.CreateProduct:input_type -> products_service.CreateProductReq + 3, // 4: products_service.ProductsService.UpdateProduct:input_type -> products_service.UpdateProductReq + 5, // 5: products_service.ProductsService.GetProductById:input_type -> products_service.GetProductByIdReq + 2, // 6: products_service.ProductsService.CreateProduct:output_type -> products_service.CreateProductRes + 4, // 7: products_service.ProductsService.UpdateProduct:output_type -> products_service.UpdateProductRes + 6, // 8: products_service.ProductsService.GetProductById:output_type -> products_service.GetProductByIdRes + 6, // [6:9] is the sub-list for method output_type + 3, // [3:6] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_catalog_write_service_products_proto_init() } +func file_catalog_write_service_products_proto_init() { + if File_catalog_write_service_products_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_catalog_write_service_products_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Product); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_catalog_write_service_products_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateProductReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_catalog_write_service_products_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateProductRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_catalog_write_service_products_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateProductReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_catalog_write_service_products_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateProductRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_catalog_write_service_products_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetProductByIdReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_catalog_write_service_products_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetProductByIdRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_catalog_write_service_products_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_catalog_write_service_products_proto_goTypes, + DependencyIndexes: file_catalog_write_service_products_proto_depIdxs, + MessageInfos: file_catalog_write_service_products_proto_msgTypes, + }.Build() + File_catalog_write_service_products_proto = out.File + file_catalog_write_service_products_proto_rawDesc = nil + file_catalog_write_service_products_proto_goTypes = nil + file_catalog_write_service_products_proto_depIdxs = nil +} diff --git a/internal/services/catalogwriteservice/internal/shared/grpc/genproto/products_grpc.pb.go b/internal/services/catalogwriteservice/internal/shared/grpc/genproto/products_grpc.pb.go new file mode 100644 index 00000000..e509ba2d --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/grpc/genproto/products_grpc.pb.go @@ -0,0 +1,181 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.4 +// source: catalogwriteservice/products.proto + +package products_service + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + ProductsService_CreateProduct_FullMethodName = "/products_service.ProductsService/CreateProduct" + ProductsService_UpdateProduct_FullMethodName = "/products_service.ProductsService/UpdateProduct" + ProductsService_GetProductById_FullMethodName = "/products_service.ProductsService/GetProductById" +) + +// ProductsServiceClient is the client API for ProductsService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ProductsServiceClient interface { + CreateProduct(ctx context.Context, in *CreateProductReq, opts ...grpc.CallOption) (*CreateProductRes, error) + UpdateProduct(ctx context.Context, in *UpdateProductReq, opts ...grpc.CallOption) (*UpdateProductRes, error) + GetProductById(ctx context.Context, in *GetProductByIdReq, opts ...grpc.CallOption) (*GetProductByIdRes, error) +} + +type productsServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewProductsServiceClient(cc grpc.ClientConnInterface) ProductsServiceClient { + return &productsServiceClient{cc} +} + +func (c *productsServiceClient) CreateProduct(ctx context.Context, in *CreateProductReq, opts ...grpc.CallOption) (*CreateProductRes, error) { + out := new(CreateProductRes) + err := c.cc.Invoke(ctx, ProductsService_CreateProduct_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productsServiceClient) UpdateProduct(ctx context.Context, in *UpdateProductReq, opts ...grpc.CallOption) (*UpdateProductRes, error) { + out := new(UpdateProductRes) + err := c.cc.Invoke(ctx, ProductsService_UpdateProduct_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *productsServiceClient) GetProductById(ctx context.Context, in *GetProductByIdReq, opts ...grpc.CallOption) (*GetProductByIdRes, error) { + out := new(GetProductByIdRes) + err := c.cc.Invoke(ctx, ProductsService_GetProductById_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ProductsServiceServer is the server API for ProductsService service. +// All implementations should embed UnimplementedProductsServiceServer +// for forward compatibility +type ProductsServiceServer interface { + CreateProduct(context.Context, *CreateProductReq) (*CreateProductRes, error) + UpdateProduct(context.Context, *UpdateProductReq) (*UpdateProductRes, error) + GetProductById(context.Context, *GetProductByIdReq) (*GetProductByIdRes, error) +} + +// UnimplementedProductsServiceServer should be embedded to have forward compatible implementations. +type UnimplementedProductsServiceServer struct { +} + +func (UnimplementedProductsServiceServer) CreateProduct(context.Context, *CreateProductReq) (*CreateProductRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateProduct not implemented") +} +func (UnimplementedProductsServiceServer) UpdateProduct(context.Context, *UpdateProductReq) (*UpdateProductRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateProduct not implemented") +} +func (UnimplementedProductsServiceServer) GetProductById(context.Context, *GetProductByIdReq) (*GetProductByIdRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetProductById not implemented") +} + +// UnsafeProductsServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ProductsServiceServer will +// result in compilation errors. +type UnsafeProductsServiceServer interface { + mustEmbedUnimplementedProductsServiceServer() +} + +func RegisterProductsServiceServer(s grpc.ServiceRegistrar, srv ProductsServiceServer) { + s.RegisterService(&ProductsService_ServiceDesc, srv) +} + +func _ProductsService_CreateProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateProductReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductsServiceServer).CreateProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductsService_CreateProduct_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductsServiceServer).CreateProduct(ctx, req.(*CreateProductReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductsService_UpdateProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateProductReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductsServiceServer).UpdateProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductsService_UpdateProduct_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductsServiceServer).UpdateProduct(ctx, req.(*UpdateProductReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProductsService_GetProductById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProductByIdReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProductsServiceServer).GetProductById(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ProductsService_GetProductById_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProductsServiceServer).GetProductById(ctx, req.(*GetProductByIdReq)) + } + return interceptor(ctx, in, info, handler) +} + +// ProductsService_ServiceDesc is the grpc.ServiceDesc for ProductsService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ProductsService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "products_service.ProductsService", + HandlerType: (*ProductsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateProduct", + Handler: _ProductsService_CreateProduct_Handler, + }, + { + MethodName: "UpdateProduct", + Handler: _ProductsService_UpdateProduct_Handler, + }, + { + MethodName: "GetProductById", + Handler: _ProductsService_GetProductById_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "catalogwriteservice/products.proto", +} diff --git a/internal/services/catalogwriteservice/internal/shared/grpc/product_grpc_service_server.go b/internal/services/catalogwriteservice/internal/shared/grpc/product_grpc_service_server.go new file mode 100644 index 00000000..72642196 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/grpc/product_grpc_service_server.go @@ -0,0 +1,234 @@ +package grpc + +import ( + "context" + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + createProductCommandV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1" + createProductDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos" + getProductByIdQueryV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1" + getProductByIdDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos" + updateProductCommandV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/contracts" + productsService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc/genproto" + + "emperror.dev/errors" + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + attribute2 "go.opentelemetry.io/otel/attribute" + api "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" +) + +var grpcMetricsAttr = api.WithAttributes( + attribute2.Key("MetricsType").String("Http"), +) + +type ProductGrpcServiceServer struct { + catalogsMetrics *contracts.CatalogsMetrics + logger logger.Logger + // Ref:https://github.com/grpc/grpc-go/issues/3794#issuecomment-720599532 + // product_service_client.UnimplementedProductsServiceServer +} + +func NewProductGrpcService( + catalogsMetrics *contracts.CatalogsMetrics, + logger logger.Logger, +) *ProductGrpcServiceServer { + return &ProductGrpcServiceServer{ + catalogsMetrics: catalogsMetrics, + logger: logger, + } +} + +func (s *ProductGrpcServiceServer) CreateProduct( + ctx context.Context, + req *productsService.CreateProductReq, +) (*productsService.CreateProductRes, error) { + span := trace.SpanFromContext(ctx) + span.SetAttributes(attribute.Object("Request", req)) + s.catalogsMetrics.CreateProductGrpcRequests.Add(ctx, 1, grpcMetricsAttr) + + command, err := createProductCommandV1.NewCreateProduct( + req.GetName(), + req.GetDescription(), + req.GetPrice(), + ) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "[ProductGrpcServiceServer_CreateProduct.StructCtx] command validation failed", + ) + s.logger.Errorf( + fmt.Sprintf( + "[ProductGrpcServiceServer_CreateProduct.StructCtx] err: %v", + validationErr, + ), + ) + return nil, validationErr + } + + result, err := mediatr.Send[*createProductCommandV1.CreateProduct, *createProductDtosV1.CreateProductResponseDto]( + ctx, + command, + ) + if err != nil { + err = errors.WithMessage( + err, + "[ProductGrpcServiceServer_CreateProduct.Send] error in sending CreateProduct", + ) + s.logger.Errorw( + fmt.Sprintf( + "[ProductGrpcServiceServer_CreateProduct.Send] id: {%s}, err: %v", + command.ProductID, + err, + ), + logger.Fields{"Id": command.ProductID}, + ) + return nil, err + } + + return &productsService.CreateProductRes{ + ProductId: result.ProductID.String(), + }, nil +} + +func (s *ProductGrpcServiceServer) UpdateProduct( + ctx context.Context, + req *productsService.UpdateProductReq, +) (*productsService.UpdateProductRes, error) { + s.catalogsMetrics.UpdateProductGrpcRequests.Add(ctx, 1, grpcMetricsAttr) + span := trace.SpanFromContext(ctx) + span.SetAttributes(attribute.Object("Request", req)) + + productUUID, err := uuid.FromString(req.GetProductId()) + if err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "[ProductGrpcServiceServer_UpdateProduct.uuid.FromString] error in converting uuid", + ) + s.logger.Errorf( + fmt.Sprintf( + "[ProductGrpcServiceServer_UpdateProduct.uuid.FromString] err: %v", + badRequestErr, + ), + ) + return nil, badRequestErr + } + + command, err := updateProductCommandV1.NewUpdateProduct( + productUUID, + req.GetName(), + req.GetDescription(), + req.GetPrice(), + ) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "[ProductGrpcServiceServer_UpdateProduct.StructCtx] command validation failed", + ) + s.logger.Errorf( + fmt.Sprintf( + "[ProductGrpcServiceServer_UpdateProduct.StructCtx] err: %v", + validationErr, + ), + ) + return nil, validationErr + } + + if _, err = mediatr.Send[*updateProductCommandV1.UpdateProduct, *mediatr.Unit](ctx, command); err != nil { + err = errors.WithMessage( + err, + "[ProductGrpcServiceServer_UpdateProduct.Send] error in sending CreateProduct", + ) + s.logger.Errorw( + fmt.Sprintf( + "[ProductGrpcServiceServer_UpdateProduct.Send] id: {%s}, err: %v", + command.ProductID, + err, + ), + logger.Fields{"Id": command.ProductID}, + ) + return nil, err + } + + return &productsService.UpdateProductRes{}, nil +} + +func (s *ProductGrpcServiceServer) GetProductById( + ctx context.Context, + req *productsService.GetProductByIdReq, +) (*productsService.GetProductByIdRes, error) { + //// we could use trace manually, but I used grpc middleware for doing this + //ctx, span, clean := grpcTracing.StartGrpcServerTracerSpan(ctx, "ProductGrpcServiceServer.GetProductById") + //defer clean() + + s.catalogsMetrics.GetProductByIdGrpcRequests.Add(ctx, 1, grpcMetricsAttr) + span := trace.SpanFromContext(ctx) + span.SetAttributes(attribute.Object("Request", req)) + + productUUID, err := uuid.FromString(req.GetProductId()) + if err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "[ProductGrpcServiceServer_GetProductById.uuid.FromString] error in converting uuid", + ) + s.logger.Errorf( + fmt.Sprintf( + "[ProductGrpcServiceServer_GetProductById.uuid.FromString] err: %v", + badRequestErr, + ), + ) + return nil, badRequestErr + } + + query, err := getProductByIdQueryV1.NewGetProductById(productUUID) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "[ProductGrpcServiceServer_GetProductById.StructCtx] query validation failed", + ) + s.logger.Errorf( + fmt.Sprintf( + "[ProductGrpcServiceServer_GetProductById.StructCtx] err: %v", + validationErr, + ), + ) + return nil, validationErr + } + + queryResult, err := mediatr.Send[*getProductByIdQueryV1.GetProductById, *getProductByIdDtosV1.GetProductByIdResponseDto]( + ctx, + query, + ) + if err != nil { + err = errors.WithMessage( + err, + "[ProductGrpcServiceServer_GetProductById.Send] error in sending GetProductById", + ) + s.logger.Errorw( + fmt.Sprintf( + "[ProductGrpcServiceServer_GetProductById.Send] id: {%s}, err: %v", + query.ProductID, + err, + ), + logger.Fields{"Id": query.ProductID}, + ) + return nil, err + } + + product, err := mapper.Map[*productsService.Product](queryResult.Product) + if err != nil { + err = errors.WithMessage( + err, + "[ProductGrpcServiceServer_GetProductById.Map] error in mapping product", + ) + return nil, err + } + + return &productsService.GetProductByIdRes{Product: product}, nil +} diff --git a/internal/services/catalogwriteservice/internal/shared/testfixtures/integration/integration_test_fixture.go b/internal/services/catalogwriteservice/internal/shared/testfixtures/integration/integration_test_fixture.go new file mode 100644 index 00000000..2686b154 --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/testfixtures/integration/integration_test_fixture.go @@ -0,0 +1,207 @@ +package integration + +import ( + "context" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/bus" + fxcontracts "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + gormPostgres "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/helpers/gormextensions" + config2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/testfixture" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/config" + datamodel "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/app/test" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/data/dbcontext" + productsService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc/genproto" + + "emperror.dev/errors" + "github.com/brianvoe/gofakeit/v6" + rabbithole "github.com/michaelklishin/rabbit-hole" + uuid "github.com/satori/go.uuid" + "gopkg.in/khaiql/dbcleaner.v2" + "gorm.io/gorm" + + _ "github.com/lib/pq" +) + +type IntegrationTestSharedFixture struct { + Cfg *config.AppOptions + Log logger.Logger + Bus bus.Bus + CatalogsDBContext *dbcontext.CatalogsGormDBContext + Container fxcontracts.Container + DbCleaner dbcleaner.DbCleaner + RabbitmqCleaner *rabbithole.Client + rabbitmqOptions *config2.RabbitmqOptions + Gorm *gorm.DB + BaseAddress string + Items []*datamodel.ProductDataModel + ProductServiceClient productsService.ProductsServiceClient +} + +func NewIntegrationTestSharedFixture( + t *testing.T, +) *IntegrationTestSharedFixture { + result := test.NewTestApp().Run(t) + + // https://github.com/michaelklishin/rabbit-hole + rmqc, err := rabbithole.NewClient( + result.RabbitmqOptions.RabbitmqHostOptions.HttpEndPoint(), + result.RabbitmqOptions.RabbitmqHostOptions.UserName, + result.RabbitmqOptions.RabbitmqHostOptions.Password) + if err != nil { + result.Logger.Error( + errors.WrapIf(err, "error in creating rabbithole client"), + ) + } + + shared := &IntegrationTestSharedFixture{ + Log: result.Logger, + Container: result.Container, + Cfg: result.Cfg, + RabbitmqCleaner: rmqc, + CatalogsDBContext: result.CatalogsDBContext, + Bus: result.Bus, + rabbitmqOptions: result.RabbitmqOptions, + Gorm: result.Gorm, + BaseAddress: result.EchoHttpOptions.BasePathAddress(), + ProductServiceClient: result.ProductServiceClient, + } + + return shared +} + +func (i *IntegrationTestSharedFixture) SetupTest() { + i.Log.Info("SetupTest started") + + // migration will do in app configuration + // seed data for our tests - app seed doesn't run in test environment + res, err := seedDataManually(i.Gorm) + if err != nil { + i.Log.Error(errors.WrapIf(err, "error in seeding data in postgres")) + } + + i.Items = res +} + +func (i *IntegrationTestSharedFixture) TearDownTest() { + i.Log.Info("TearDownTest started") + + // cleanup test containers with their hooks + if err := i.cleanupRabbitmqData(); err != nil { + i.Log.Error(errors.WrapIf(err, "error in cleanup rabbitmq data")) + } + + if err := i.cleanupPostgresData(); err != nil { + i.Log.Error(errors.WrapIf(err, "error in cleanup postgres data")) + } +} + +func (i *IntegrationTestSharedFixture) cleanupRabbitmqData() error { + // https://github.com/michaelklishin/rabbit-hole + // Get all queues + queues, err := i.RabbitmqCleaner.ListQueuesIn( + i.rabbitmqOptions.RabbitmqHostOptions.VirtualHost, + ) + if err != nil { + return err + } + + // clear each queue + for _, queue := range queues { + _, err = i.RabbitmqCleaner.PurgeQueue( + i.rabbitmqOptions.RabbitmqHostOptions.VirtualHost, + queue.Name, + ) + + return err + } + + return nil +} + +func (i *IntegrationTestSharedFixture) cleanupPostgresData() error { + tables := []string{"products"} + // Iterate over the tables and delete all records + for _, table := range tables { + err := i.Gorm.Exec("DELETE FROM " + table).Error + + return err + } + + return nil +} + +func seedDataManually(gormDB *gorm.DB) ([]*datamodel.ProductDataModel, error) { + products := []*datamodel.ProductDataModel{ + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + } + + err := gormDB.CreateInBatches(products, len(products)).Error + if err != nil { + return nil, errors.Wrap(err, "error in seed database") + } + + return products, nil +} + +func seedDataWithFixture(gormDB *gorm.DB) ([]*datamodel.ProductDataModel, error) { + db, err := gormDB.DB() + if err != nil { + return nil, errors.WrapIf(err, "error in seed database") + } + + // https://github.com/go-testfixtures/testfixtures#templating + // seed data + var data []struct { + Name string + ProductId uuid.UUID + Description string + } + + f := []struct { + Name string + ProductId uuid.UUID + Description string + }{ + {gofakeit.Name(), uuid.NewV4(), gofakeit.AdjectiveDescriptive()}, + {gofakeit.Name(), uuid.NewV4(), gofakeit.AdjectiveDescriptive()}, + } + + data = append(data, f...) + + err = testfixture.RunPostgresFixture( + db, + []string{"db/fixtures/products"}, + map[string]interface{}{ + "Products": data, + }) + if err != nil { + return nil, errors.WrapIf(err, "error in seed database") + } + + result, err := gormPostgres.Paginate[*datamodel.ProductDataModel, *datamodel.ProductDataModel]( + context.Background(), + utils.NewListQuery(10, 1), + gormDB, + ) + + return result.Items, nil +} diff --git a/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest/unit_test_fixture.go b/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest/unit_test_fixture.go new file mode 100644 index 00000000..1a17f8bb --- /dev/null +++ b/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest/unit_test_fixture.go @@ -0,0 +1,210 @@ +package unittest + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/mocks" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + defaultLogger "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/defaultlogger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger/external/gromlog" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/helpers/gormextensions" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/configurations/mappings" + datamodel "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/data/dbcontext" + + "emperror.dev/errors" + "github.com/brianvoe/gofakeit/v6" + "github.com/glebarez/sqlite" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "go.opentelemetry.io/otel/trace" + "gorm.io/gorm" +) + +type UnitTestSharedFixture struct { + Cfg *config.AppOptions + Log logger.Logger + suite.Suite + Products []*datamodel.ProductDataModel + Bus *mocks.Bus + Tracer trace.Tracer + CatalogDBContext *dbcontext.CatalogsGormDBContext + Ctx context.Context + dbFilePath string + dbFileName string +} + +func NewUnitTestSharedFixture(t *testing.T) *UnitTestSharedFixture { + // we could use EmptyLogger if we don't want to log anything + log := defaultLogger.GetLogger() + cfg := &config.AppOptions{} + + // empty tracer, just for testing + nopetracer := trace.NewNoopTracerProvider() + testTracer := nopetracer.Tracer("test_tracer") + + unit := &UnitTestSharedFixture{ + Cfg: cfg, + Log: log, + Tracer: testTracer, + dbFileName: "sqlite.db", + } + + return unit +} + +func (c *UnitTestSharedFixture) BeginTx() { + c.Log.Info("starting transaction") + // seems when we `Begin` a transaction on gorm.DB (with SQLLite in-memory) our previous gormDB before transaction will remove and the new gormDB with tx will go on the memory + tx := c.CatalogDBContext.DB().Begin() + gormContext := gormextensions.SetTxToContext(c.Ctx, tx) + c.Ctx = gormContext + + //// works on both transaction and none-transactional gormdbcontext + //var productData []*datamodel.ProductDataModel + //var productData2 []*datamodel.ProductDataModel + // + //s := c.CatalogDBContext.Find(&productData).Error + //s2 := tx.Find(&productData2).Error +} + +func (c *UnitTestSharedFixture) CommitTx() { + tx := gormextensions.GetTxFromContextIfExists(c.Ctx) + if tx != nil { + c.Log.Info("committing transaction") + tx.Commit() + } +} + +/// Shared Hooks + +func (c *UnitTestSharedFixture) SetupSuite() { + // this fix root working directory problem in our test environment inner our fixture + environment.FixProjectRootWorkingDirectoryPath() + projectRootDir := environment.GetProjectRootWorkingDirectory() + + c.dbFilePath = filepath.Join(projectRootDir, c.dbFileName) +} + +func (c *UnitTestSharedFixture) TearDownSuite() { +} + +func (c *UnitTestSharedFixture) SetupTest() { + ctx := context.Background() + c.Ctx = ctx + + c.setupBus() + + c.setupDB() + + err := mappings.ConfigureProductsMappings() + c.Require().NoError(err) +} + +func (c *UnitTestSharedFixture) TearDownTest() { + err := c.cleanupDB() + c.Require().NoError(err) + + mapper.ClearMappings() +} + +func (c *UnitTestSharedFixture) setupBus() { + // create new mocks + bus := &mocks.Bus{} + + bus.On("PublishMessage", mock.Anything, mock.Anything, mock.Anything). + Return(nil) + c.Bus = bus +} + +func (c *UnitTestSharedFixture) setupDB() { + dbContext := c.createSQLLiteDBContext() + c.CatalogDBContext = dbContext + + c.initDB(dbContext) +} + +func (c *UnitTestSharedFixture) createSQLLiteDBContext() *dbcontext.CatalogsGormDBContext { + // https://gorm.io/docs/connecting_to_the_database.html#SQLite + // https://github.com/glebarez/sqlite + // https://www.connectionstrings.com/sqlite/ + gormSQLLiteDB, err := gorm.Open( + sqlite.Open(c.dbFilePath), + &gorm.Config{ + Logger: gromlog.NewGormCustomLogger(defaultLogger.GetLogger()), + }) + c.Require().NoError(err) + + dbContext := dbcontext.NewCatalogsDBContext(gormSQLLiteDB) + + return dbContext +} + +func (c *UnitTestSharedFixture) initDB(dbContext *dbcontext.CatalogsGormDBContext) { + // migrations for our database + err := migrateGorm(dbContext) + c.Require().NoError(err) + + // seed data for our tests + items, err := seedDataManually(dbContext) + c.Require().NoError(err) + + c.Products = items +} + +func (c *UnitTestSharedFixture) cleanupDB() error { + sqldb, _ := c.CatalogDBContext.DB().DB() + e := sqldb.Close() + c.Require().NoError(e) + + // removing sql-lite file + err := os.Remove(c.dbFilePath) + + return err +} + +func migrateGorm(dbContext *dbcontext.CatalogsGormDBContext) error { + err := dbContext.DB().AutoMigrate(&datamodel.ProductDataModel{}) + if err != nil { + return err + } + + return nil +} + +func seedDataManually( + dbContext *dbcontext.CatalogsGormDBContext, +) ([]*datamodel.ProductDataModel, error) { + products := []*datamodel.ProductDataModel{ + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + { + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + }, + } + + // seed data + err := dbContext.DB().CreateInBatches(products, len(products)).Error + if err != nil { + return nil, errors.Wrap(err, "error in seed database") + } + + return products, nil +} diff --git a/internal/services/catalogwriteservice/mocks/CatalogContext.go b/internal/services/catalogwriteservice/mocks/CatalogContext.go new file mode 100644 index 00000000..5c463374 --- /dev/null +++ b/internal/services/catalogwriteservice/mocks/CatalogContext.go @@ -0,0 +1,79 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + data "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/contracts" + + mock "github.com/stretchr/testify/mock" +) + +// CatalogContext is an autogenerated mock type for the CatalogContext type +type CatalogContext struct { + mock.Mock +} + +type CatalogContext_Expecter struct { + mock *mock.Mock +} + +func (_m *CatalogContext) EXPECT() *CatalogContext_Expecter { + return &CatalogContext_Expecter{mock: &_m.Mock} +} + +// Products provides a mock function with given fields: +func (_m *CatalogContext) Products() data.ProductRepository { + ret := _m.Called() + + var r0 data.ProductRepository + if rf, ok := ret.Get(0).(func() data.ProductRepository); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(data.ProductRepository) + } + } + + return r0 +} + +// CatalogContext_Products_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Products' +type CatalogContext_Products_Call struct { + *mock.Call +} + +// Products is a helper method to define mock.On call +func (_e *CatalogContext_Expecter) Products() *CatalogContext_Products_Call { + return &CatalogContext_Products_Call{Call: _e.mock.On("Products")} +} + +func (_c *CatalogContext_Products_Call) Run(run func()) *CatalogContext_Products_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *CatalogContext_Products_Call) Return(_a0 data.ProductRepository) *CatalogContext_Products_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *CatalogContext_Products_Call) RunAndReturn(run func() data.ProductRepository) *CatalogContext_Products_Call { + _c.Call.Return(run) + return _c +} + +// NewCatalogContext creates a new instance of CatalogContext. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewCatalogContext(t interface { + mock.TestingT + Cleanup(func()) +}) *CatalogContext { + mock := &CatalogContext{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/catalogwriteservice/mocks/ProductRepository.go b/internal/services/catalogwriteservice/mocks/ProductRepository.go new file mode 100644 index 00000000..62732dce --- /dev/null +++ b/internal/services/catalogwriteservice/mocks/ProductRepository.go @@ -0,0 +1,359 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + utils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + models "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + + uuid "github.com/satori/go.uuid" + mock "github.com/stretchr/testify/mock" +) + +// ProductRepository is an autogenerated mock type for the ProductRepository type +type ProductRepository struct { + mock.Mock +} + +type ProductRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *ProductRepository) EXPECT() *ProductRepository_Expecter { + return &ProductRepository_Expecter{mock: &_m.Mock} +} + +// CreateProduct provides a mock function with given fields: ctx, product +func (_m *ProductRepository) CreateProduct(ctx context.Context, product *models.Product) (*models.Product, error) { + ret := _m.Called(ctx, product) + + var r0 *models.Product + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *models.Product) (*models.Product, error)); ok { + return rf(ctx, product) + } + if rf, ok := ret.Get(0).(func(context.Context, *models.Product) *models.Product); ok { + r0 = rf(ctx, product) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Product) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *models.Product) error); ok { + r1 = rf(ctx, product) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_CreateProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateProduct' +type ProductRepository_CreateProduct_Call struct { + *mock.Call +} + +// CreateProduct is a helper method to define mock.On call +// - ctx context.Context +// - product *models.Product +func (_e *ProductRepository_Expecter) CreateProduct(ctx interface{}, product interface{}) *ProductRepository_CreateProduct_Call { + return &ProductRepository_CreateProduct_Call{Call: _e.mock.On("CreateProduct", ctx, product)} +} + +func (_c *ProductRepository_CreateProduct_Call) Run(run func(ctx context.Context, product *models.Product)) *ProductRepository_CreateProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*models.Product)) + }) + return _c +} + +func (_c *ProductRepository_CreateProduct_Call) Return(_a0 *models.Product, _a1 error) *ProductRepository_CreateProduct_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_CreateProduct_Call) RunAndReturn(run func(context.Context, *models.Product) (*models.Product, error)) *ProductRepository_CreateProduct_Call { + _c.Call.Return(run) + return _c +} + +// DeleteProductByID provides a mock function with given fields: ctx, _a1 +func (_m *ProductRepository) DeleteProductByID(ctx context.Context, _a1 uuid.UUID) error { + ret := _m.Called(ctx, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok { + r0 = rf(ctx, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ProductRepository_DeleteProductByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteProductByID' +type ProductRepository_DeleteProductByID_Call struct { + *mock.Call +} + +// DeleteProductByID is a helper method to define mock.On call +// - ctx context.Context +// - _a1 uuid.UUID +func (_e *ProductRepository_Expecter) DeleteProductByID(ctx interface{}, _a1 interface{}) *ProductRepository_DeleteProductByID_Call { + return &ProductRepository_DeleteProductByID_Call{Call: _e.mock.On("DeleteProductByID", ctx, _a1)} +} + +func (_c *ProductRepository_DeleteProductByID_Call) Run(run func(ctx context.Context, _a1 uuid.UUID)) *ProductRepository_DeleteProductByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *ProductRepository_DeleteProductByID_Call) Return(_a0 error) *ProductRepository_DeleteProductByID_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *ProductRepository_DeleteProductByID_Call) RunAndReturn(run func(context.Context, uuid.UUID) error) *ProductRepository_DeleteProductByID_Call { + _c.Call.Return(run) + return _c +} + +// GetAllProducts provides a mock function with given fields: ctx, listQuery +func (_m *ProductRepository) GetAllProducts(ctx context.Context, listQuery *utils.ListQuery) (*utils.ListResult[*models.Product], error) { + ret := _m.Called(ctx, listQuery) + + var r0 *utils.ListResult[*models.Product] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) (*utils.ListResult[*models.Product], error)); ok { + return rf(ctx, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) *utils.ListResult[*models.Product]); ok { + r0 = rf(ctx, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*models.Product]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *utils.ListQuery) error); ok { + r1 = rf(ctx, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_GetAllProducts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllProducts' +type ProductRepository_GetAllProducts_Call struct { + *mock.Call +} + +// GetAllProducts is a helper method to define mock.On call +// - ctx context.Context +// - listQuery *utils.ListQuery +func (_e *ProductRepository_Expecter) GetAllProducts(ctx interface{}, listQuery interface{}) *ProductRepository_GetAllProducts_Call { + return &ProductRepository_GetAllProducts_Call{Call: _e.mock.On("GetAllProducts", ctx, listQuery)} +} + +func (_c *ProductRepository_GetAllProducts_Call) Run(run func(ctx context.Context, listQuery *utils.ListQuery)) *ProductRepository_GetAllProducts_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*utils.ListQuery)) + }) + return _c +} + +func (_c *ProductRepository_GetAllProducts_Call) Return(_a0 *utils.ListResult[*models.Product], _a1 error) *ProductRepository_GetAllProducts_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_GetAllProducts_Call) RunAndReturn(run func(context.Context, *utils.ListQuery) (*utils.ListResult[*models.Product], error)) *ProductRepository_GetAllProducts_Call { + _c.Call.Return(run) + return _c +} + +// GetProductById provides a mock function with given fields: ctx, _a1 +func (_m *ProductRepository) GetProductById(ctx context.Context, _a1 uuid.UUID) (*models.Product, error) { + ret := _m.Called(ctx, _a1) + + var r0 *models.Product + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*models.Product, error)); ok { + return rf(ctx, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) *models.Product); ok { + r0 = rf(ctx, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Product) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = rf(ctx, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_GetProductById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProductById' +type ProductRepository_GetProductById_Call struct { + *mock.Call +} + +// GetProductById is a helper method to define mock.On call +// - ctx context.Context +// - _a1 uuid.UUID +func (_e *ProductRepository_Expecter) GetProductById(ctx interface{}, _a1 interface{}) *ProductRepository_GetProductById_Call { + return &ProductRepository_GetProductById_Call{Call: _e.mock.On("GetProductById", ctx, _a1)} +} + +func (_c *ProductRepository_GetProductById_Call) Run(run func(ctx context.Context, _a1 uuid.UUID)) *ProductRepository_GetProductById_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *ProductRepository_GetProductById_Call) Return(_a0 *models.Product, _a1 error) *ProductRepository_GetProductById_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_GetProductById_Call) RunAndReturn(run func(context.Context, uuid.UUID) (*models.Product, error)) *ProductRepository_GetProductById_Call { + _c.Call.Return(run) + return _c +} + +// SearchProducts provides a mock function with given fields: ctx, searchText, listQuery +func (_m *ProductRepository) SearchProducts(ctx context.Context, searchText string, listQuery *utils.ListQuery) (*utils.ListResult[*models.Product], error) { + ret := _m.Called(ctx, searchText, listQuery) + + var r0 *utils.ListResult[*models.Product] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*models.Product], error)); ok { + return rf(ctx, searchText, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) *utils.ListResult[*models.Product]); ok { + r0 = rf(ctx, searchText, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*models.Product]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *utils.ListQuery) error); ok { + r1 = rf(ctx, searchText, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_SearchProducts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SearchProducts' +type ProductRepository_SearchProducts_Call struct { + *mock.Call +} + +// SearchProducts is a helper method to define mock.On call +// - ctx context.Context +// - searchText string +// - listQuery *utils.ListQuery +func (_e *ProductRepository_Expecter) SearchProducts(ctx interface{}, searchText interface{}, listQuery interface{}) *ProductRepository_SearchProducts_Call { + return &ProductRepository_SearchProducts_Call{Call: _e.mock.On("SearchProducts", ctx, searchText, listQuery)} +} + +func (_c *ProductRepository_SearchProducts_Call) Run(run func(ctx context.Context, searchText string, listQuery *utils.ListQuery)) *ProductRepository_SearchProducts_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*utils.ListQuery)) + }) + return _c +} + +func (_c *ProductRepository_SearchProducts_Call) Return(_a0 *utils.ListResult[*models.Product], _a1 error) *ProductRepository_SearchProducts_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_SearchProducts_Call) RunAndReturn(run func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*models.Product], error)) *ProductRepository_SearchProducts_Call { + _c.Call.Return(run) + return _c +} + +// UpdateProduct provides a mock function with given fields: ctx, product +func (_m *ProductRepository) UpdateProduct(ctx context.Context, product *models.Product) (*models.Product, error) { + ret := _m.Called(ctx, product) + + var r0 *models.Product + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *models.Product) (*models.Product, error)); ok { + return rf(ctx, product) + } + if rf, ok := ret.Get(0).(func(context.Context, *models.Product) *models.Product); ok { + r0 = rf(ctx, product) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Product) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *models.Product) error); ok { + r1 = rf(ctx, product) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductRepository_UpdateProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateProduct' +type ProductRepository_UpdateProduct_Call struct { + *mock.Call +} + +// UpdateProduct is a helper method to define mock.On call +// - ctx context.Context +// - product *models.Product +func (_e *ProductRepository_Expecter) UpdateProduct(ctx interface{}, product interface{}) *ProductRepository_UpdateProduct_Call { + return &ProductRepository_UpdateProduct_Call{Call: _e.mock.On("UpdateProduct", ctx, product)} +} + +func (_c *ProductRepository_UpdateProduct_Call) Run(run func(ctx context.Context, product *models.Product)) *ProductRepository_UpdateProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*models.Product)) + }) + return _c +} + +func (_c *ProductRepository_UpdateProduct_Call) Return(_a0 *models.Product, _a1 error) *ProductRepository_UpdateProduct_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductRepository_UpdateProduct_Call) RunAndReturn(run func(context.Context, *models.Product) (*models.Product, error)) *ProductRepository_UpdateProduct_Call { + _c.Call.Return(run) + return _c +} + +// NewProductRepository creates a new instance of ProductRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProductRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *ProductRepository { + mock := &ProductRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/catalogwriteservice/mocks/ProductsServiceClient.go b/internal/services/catalogwriteservice/mocks/ProductsServiceClient.go new file mode 100644 index 00000000..8053297e --- /dev/null +++ b/internal/services/catalogwriteservice/mocks/ProductsServiceClient.go @@ -0,0 +1,250 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" + + productsservice "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc/genproto" +) + +// ProductsServiceClient is an autogenerated mock type for the ProductsServiceClient type +type ProductsServiceClient struct { + mock.Mock +} + +type ProductsServiceClient_Expecter struct { + mock *mock.Mock +} + +func (_m *ProductsServiceClient) EXPECT() *ProductsServiceClient_Expecter { + return &ProductsServiceClient_Expecter{mock: &_m.Mock} +} + +// CreateProduct provides a mock function with given fields: ctx, in, opts +func (_m *ProductsServiceClient) CreateProduct(ctx context.Context, in *productsservice.CreateProductReq, opts ...grpc.CallOption) (*productsservice.CreateProductRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *productsservice.CreateProductRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.CreateProductReq, ...grpc.CallOption) (*productsservice.CreateProductRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.CreateProductReq, ...grpc.CallOption) *productsservice.CreateProductRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*productsservice.CreateProductRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *productsservice.CreateProductReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductsServiceClient_CreateProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateProduct' +type ProductsServiceClient_CreateProduct_Call struct { + *mock.Call +} + +// CreateProduct is a helper method to define mock.On call +// - ctx context.Context +// - in *products_service.CreateProductReq +// - opts ...grpc.CallOption +func (_e *ProductsServiceClient_Expecter) CreateProduct(ctx interface{}, in interface{}, opts ...interface{}) *ProductsServiceClient_CreateProduct_Call { + return &ProductsServiceClient_CreateProduct_Call{Call: _e.mock.On("CreateProduct", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ProductsServiceClient_CreateProduct_Call) Run(run func(ctx context.Context, in *productsservice.CreateProductReq, opts ...grpc.CallOption)) *ProductsServiceClient_CreateProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*productsservice.CreateProductReq), variadicArgs...) + }) + return _c +} + +func (_c *ProductsServiceClient_CreateProduct_Call) Return(_a0 *productsservice.CreateProductRes, _a1 error) *ProductsServiceClient_CreateProduct_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductsServiceClient_CreateProduct_Call) RunAndReturn(run func(context.Context, *productsservice.CreateProductReq, ...grpc.CallOption) (*productsservice.CreateProductRes, error)) *ProductsServiceClient_CreateProduct_Call { + _c.Call.Return(run) + return _c +} + +// GetProductById provides a mock function with given fields: ctx, in, opts +func (_m *ProductsServiceClient) GetProductById(ctx context.Context, in *productsservice.GetProductByIdReq, opts ...grpc.CallOption) (*productsservice.GetProductByIdRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *productsservice.GetProductByIdRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.GetProductByIdReq, ...grpc.CallOption) (*productsservice.GetProductByIdRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.GetProductByIdReq, ...grpc.CallOption) *productsservice.GetProductByIdRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*productsservice.GetProductByIdRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *productsservice.GetProductByIdReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductsServiceClient_GetProductById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProductById' +type ProductsServiceClient_GetProductById_Call struct { + *mock.Call +} + +// GetProductById is a helper method to define mock.On call +// - ctx context.Context +// - in *products_service.GetProductByIdReq +// - opts ...grpc.CallOption +func (_e *ProductsServiceClient_Expecter) GetProductById(ctx interface{}, in interface{}, opts ...interface{}) *ProductsServiceClient_GetProductById_Call { + return &ProductsServiceClient_GetProductById_Call{Call: _e.mock.On("GetProductById", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ProductsServiceClient_GetProductById_Call) Run(run func(ctx context.Context, in *productsservice.GetProductByIdReq, opts ...grpc.CallOption)) *ProductsServiceClient_GetProductById_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*productsservice.GetProductByIdReq), variadicArgs...) + }) + return _c +} + +func (_c *ProductsServiceClient_GetProductById_Call) Return(_a0 *productsservice.GetProductByIdRes, _a1 error) *ProductsServiceClient_GetProductById_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductsServiceClient_GetProductById_Call) RunAndReturn(run func(context.Context, *productsservice.GetProductByIdReq, ...grpc.CallOption) (*productsservice.GetProductByIdRes, error)) *ProductsServiceClient_GetProductById_Call { + _c.Call.Return(run) + return _c +} + +// UpdateProduct provides a mock function with given fields: ctx, in, opts +func (_m *ProductsServiceClient) UpdateProduct(ctx context.Context, in *productsservice.UpdateProductReq, opts ...grpc.CallOption) (*productsservice.UpdateProductRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *productsservice.UpdateProductRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.UpdateProductReq, ...grpc.CallOption) (*productsservice.UpdateProductRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.UpdateProductReq, ...grpc.CallOption) *productsservice.UpdateProductRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*productsservice.UpdateProductRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *productsservice.UpdateProductReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductsServiceClient_UpdateProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateProduct' +type ProductsServiceClient_UpdateProduct_Call struct { + *mock.Call +} + +// UpdateProduct is a helper method to define mock.On call +// - ctx context.Context +// - in *products_service.UpdateProductReq +// - opts ...grpc.CallOption +func (_e *ProductsServiceClient_Expecter) UpdateProduct(ctx interface{}, in interface{}, opts ...interface{}) *ProductsServiceClient_UpdateProduct_Call { + return &ProductsServiceClient_UpdateProduct_Call{Call: _e.mock.On("UpdateProduct", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *ProductsServiceClient_UpdateProduct_Call) Run(run func(ctx context.Context, in *productsservice.UpdateProductReq, opts ...grpc.CallOption)) *ProductsServiceClient_UpdateProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*productsservice.UpdateProductReq), variadicArgs...) + }) + return _c +} + +func (_c *ProductsServiceClient_UpdateProduct_Call) Return(_a0 *productsservice.UpdateProductRes, _a1 error) *ProductsServiceClient_UpdateProduct_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductsServiceClient_UpdateProduct_Call) RunAndReturn(run func(context.Context, *productsservice.UpdateProductReq, ...grpc.CallOption) (*productsservice.UpdateProductRes, error)) *ProductsServiceClient_UpdateProduct_Call { + _c.Call.Return(run) + return _c +} + +// NewProductsServiceClient creates a new instance of ProductsServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProductsServiceClient(t interface { + mock.TestingT + Cleanup(func()) +}) *ProductsServiceClient { + mock := &ProductsServiceClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/catalogwriteservice/mocks/ProductsServiceServer.go b/internal/services/catalogwriteservice/mocks/ProductsServiceServer.go new file mode 100644 index 00000000..0a4607a0 --- /dev/null +++ b/internal/services/catalogwriteservice/mocks/ProductsServiceServer.go @@ -0,0 +1,203 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + productsservice "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc/genproto" +) + +// ProductsServiceServer is an autogenerated mock type for the ProductsServiceServer type +type ProductsServiceServer struct { + mock.Mock +} + +type ProductsServiceServer_Expecter struct { + mock *mock.Mock +} + +func (_m *ProductsServiceServer) EXPECT() *ProductsServiceServer_Expecter { + return &ProductsServiceServer_Expecter{mock: &_m.Mock} +} + +// CreateProduct provides a mock function with given fields: _a0, _a1 +func (_m *ProductsServiceServer) CreateProduct(_a0 context.Context, _a1 *productsservice.CreateProductReq) (*productsservice.CreateProductRes, error) { + ret := _m.Called(_a0, _a1) + + var r0 *productsservice.CreateProductRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.CreateProductReq) (*productsservice.CreateProductRes, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.CreateProductReq) *productsservice.CreateProductRes); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*productsservice.CreateProductRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *productsservice.CreateProductReq) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductsServiceServer_CreateProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateProduct' +type ProductsServiceServer_CreateProduct_Call struct { + *mock.Call +} + +// CreateProduct is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *products_service.CreateProductReq +func (_e *ProductsServiceServer_Expecter) CreateProduct(_a0 interface{}, _a1 interface{}) *ProductsServiceServer_CreateProduct_Call { + return &ProductsServiceServer_CreateProduct_Call{Call: _e.mock.On("CreateProduct", _a0, _a1)} +} + +func (_c *ProductsServiceServer_CreateProduct_Call) Run(run func(_a0 context.Context, _a1 *productsservice.CreateProductReq)) *ProductsServiceServer_CreateProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*productsservice.CreateProductReq)) + }) + return _c +} + +func (_c *ProductsServiceServer_CreateProduct_Call) Return(_a0 *productsservice.CreateProductRes, _a1 error) *ProductsServiceServer_CreateProduct_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductsServiceServer_CreateProduct_Call) RunAndReturn(run func(context.Context, *productsservice.CreateProductReq) (*productsservice.CreateProductRes, error)) *ProductsServiceServer_CreateProduct_Call { + _c.Call.Return(run) + return _c +} + +// GetProductById provides a mock function with given fields: _a0, _a1 +func (_m *ProductsServiceServer) GetProductById(_a0 context.Context, _a1 *productsservice.GetProductByIdReq) (*productsservice.GetProductByIdRes, error) { + ret := _m.Called(_a0, _a1) + + var r0 *productsservice.GetProductByIdRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.GetProductByIdReq) (*productsservice.GetProductByIdRes, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.GetProductByIdReq) *productsservice.GetProductByIdRes); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*productsservice.GetProductByIdRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *productsservice.GetProductByIdReq) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductsServiceServer_GetProductById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProductById' +type ProductsServiceServer_GetProductById_Call struct { + *mock.Call +} + +// GetProductById is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *products_service.GetProductByIdReq +func (_e *ProductsServiceServer_Expecter) GetProductById(_a0 interface{}, _a1 interface{}) *ProductsServiceServer_GetProductById_Call { + return &ProductsServiceServer_GetProductById_Call{Call: _e.mock.On("GetProductById", _a0, _a1)} +} + +func (_c *ProductsServiceServer_GetProductById_Call) Run(run func(_a0 context.Context, _a1 *productsservice.GetProductByIdReq)) *ProductsServiceServer_GetProductById_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*productsservice.GetProductByIdReq)) + }) + return _c +} + +func (_c *ProductsServiceServer_GetProductById_Call) Return(_a0 *productsservice.GetProductByIdRes, _a1 error) *ProductsServiceServer_GetProductById_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductsServiceServer_GetProductById_Call) RunAndReturn(run func(context.Context, *productsservice.GetProductByIdReq) (*productsservice.GetProductByIdRes, error)) *ProductsServiceServer_GetProductById_Call { + _c.Call.Return(run) + return _c +} + +// UpdateProduct provides a mock function with given fields: _a0, _a1 +func (_m *ProductsServiceServer) UpdateProduct(_a0 context.Context, _a1 *productsservice.UpdateProductReq) (*productsservice.UpdateProductRes, error) { + ret := _m.Called(_a0, _a1) + + var r0 *productsservice.UpdateProductRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.UpdateProductReq) (*productsservice.UpdateProductRes, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *productsservice.UpdateProductReq) *productsservice.UpdateProductRes); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*productsservice.UpdateProductRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *productsservice.UpdateProductReq) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProductsServiceServer_UpdateProduct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateProduct' +type ProductsServiceServer_UpdateProduct_Call struct { + *mock.Call +} + +// UpdateProduct is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *products_service.UpdateProductReq +func (_e *ProductsServiceServer_Expecter) UpdateProduct(_a0 interface{}, _a1 interface{}) *ProductsServiceServer_UpdateProduct_Call { + return &ProductsServiceServer_UpdateProduct_Call{Call: _e.mock.On("UpdateProduct", _a0, _a1)} +} + +func (_c *ProductsServiceServer_UpdateProduct_Call) Run(run func(_a0 context.Context, _a1 *productsservice.UpdateProductReq)) *ProductsServiceServer_UpdateProduct_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*productsservice.UpdateProductReq)) + }) + return _c +} + +func (_c *ProductsServiceServer_UpdateProduct_Call) Return(_a0 *productsservice.UpdateProductRes, _a1 error) *ProductsServiceServer_UpdateProduct_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ProductsServiceServer_UpdateProduct_Call) RunAndReturn(run func(context.Context, *productsservice.UpdateProductReq) (*productsservice.UpdateProductRes, error)) *ProductsServiceServer_UpdateProduct_Call { + _c.Call.Return(run) + return _c +} + +// NewProductsServiceServer creates a new instance of ProductsServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProductsServiceServer(t interface { + mock.TestingT + Cleanup(func()) +}) *ProductsServiceServer { + mock := &ProductsServiceServer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/catalogwriteservice/mocks/UnsafeProductsServiceServer.go b/internal/services/catalogwriteservice/mocks/UnsafeProductsServiceServer.go new file mode 100644 index 00000000..33f9a17d --- /dev/null +++ b/internal/services/catalogwriteservice/mocks/UnsafeProductsServiceServer.go @@ -0,0 +1,64 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// UnsafeProductsServiceServer is an autogenerated mock type for the UnsafeProductsServiceServer type +type UnsafeProductsServiceServer struct { + mock.Mock +} + +type UnsafeProductsServiceServer_Expecter struct { + mock *mock.Mock +} + +func (_m *UnsafeProductsServiceServer) EXPECT() *UnsafeProductsServiceServer_Expecter { + return &UnsafeProductsServiceServer_Expecter{mock: &_m.Mock} +} + +// mustEmbedUnimplementedProductsServiceServer provides a mock function with given fields: +func (_m *UnsafeProductsServiceServer) mustEmbedUnimplementedProductsServiceServer() { + _m.Called() +} + +// UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'mustEmbedUnimplementedProductsServiceServer' +type UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call struct { + *mock.Call +} + +// mustEmbedUnimplementedProductsServiceServer is a helper method to define mock.On call +func (_e *UnsafeProductsServiceServer_Expecter) mustEmbedUnimplementedProductsServiceServer() *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call { + return &UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call{Call: _e.mock.On("mustEmbedUnimplementedProductsServiceServer")} +} + +func (_c *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call) Run(run func()) *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call) Return() *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call { + _c.Call.Return() + return _c +} + +func (_c *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call) RunAndReturn(run func()) *UnsafeProductsServiceServer_mustEmbedUnimplementedProductsServiceServer_Call { + _c.Call.Return(run) + return _c +} + +// NewUnsafeProductsServiceServer creates a new instance of UnsafeProductsServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewUnsafeProductsServiceServer(t interface { + mock.TestingT + Cleanup(func()) +}) *UnsafeProductsServiceServer { + mock := &UnsafeProductsServiceServer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/catalogwriteservice/readme.md b/internal/services/catalogwriteservice/readme.md new file mode 100644 index 00000000..c27efa08 --- /dev/null +++ b/internal/services/catalogwriteservice/readme.md @@ -0,0 +1,6 @@ +# Catalogs Write Service + +## Project Layout and Structure + +Based on these projects structure: +- [Standard Go Project Layout](https://github.com/golang-standards/project-layout) diff --git a/internal/services/catalogwriteservice/revive-config.toml b/internal/services/catalogwriteservice/revive-config.toml new file mode 100644 index 00000000..f9e2405d --- /dev/null +++ b/internal/services/catalogwriteservice/revive-config.toml @@ -0,0 +1,30 @@ +ignoreGeneratedHeader = false +severity = "warning" +confidence = 0.8 +errorCode = 0 +warningCode = 0 + +[rule.blank-imports] +[rule.context-as-argument] +[rule.context-keys-type] +[rule.dot-imports] +[rule.error-return] +[rule.error-strings] +[rule.error-naming] +[rule.exported] +[rule.if-return] +[rule.increment-decrement] +[rule.var-naming] +[rule.var-declaration] +[rule.package-comments] +[rule.range] +[rule.receiver-naming] +[rule.time-naming] +[rule.unexported-return] +[rule.indent-error-flow] +[rule.errorf] +[rule.empty-block] +[rule.superfluous-else] +[rule.unused-parameter] +[rule.unreachable-code] +[rule.redefines-builtin-id] diff --git a/internal/services/catalogwriteservice/taskfile.yml b/internal/services/catalogwriteservice/taskfile.yml new file mode 100644 index 00000000..ed72a03d --- /dev/null +++ b/internal/services/catalogwriteservice/taskfile.yml @@ -0,0 +1,3 @@ +#https://taskfile.dev/#/installation + +version: "3" diff --git a/internal/services/catalogwriteservice/taskfile_db.yml b/internal/services/catalogwriteservice/taskfile_db.yml new file mode 100644 index 00000000..ed72a03d --- /dev/null +++ b/internal/services/catalogwriteservice/taskfile_db.yml @@ -0,0 +1,3 @@ +#https://taskfile.dev/#/installation + +version: "3" diff --git a/internal/services/catalogwriteservice/taskfile_test.yml b/internal/services/catalogwriteservice/taskfile_test.yml new file mode 100644 index 00000000..89e7fc8e --- /dev/null +++ b/internal/services/catalogwriteservice/taskfile_test.yml @@ -0,0 +1,24 @@ +#https://taskfile.dev/#/installation + +version: "3" + +tasks: + mock: + desc: Generate interfaces mocks + cmds: + - mockery --output mocks --all + + integration: + desc: Run integration tests + cmds: + - go test -v -tags=integration ./... + + e2e: + desc: Run integration tests + cmds: + - go test -v -tags=e2e ./... + + unit: + desc: Run unit tests + cmds: + - go test -v -tags=unit ./... diff --git a/internal/services/catalogwriteservice/test/endtoend/products/features/creatingproduct/v1/create_product_test.go b/internal/services/catalogwriteservice/test/endtoend/products/features/creatingproduct/v1/create_product_test.go new file mode 100644 index 00000000..786eb570 --- /dev/null +++ b/internal/services/catalogwriteservice/test/endtoend/products/features/creatingproduct/v1/create_product_test.go @@ -0,0 +1,96 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/brianvoe/gofakeit/v6" + "github.com/gavv/httpexpect/v2" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestCreateProductEndpoint(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "CreateProduct Endpoint EndToEnd Tests") +} + +var _ = Describe("CreateProduct Feature", func() { + var ( + ctx context.Context + request *dtos.CreateProductRequestDto + ) + + _ = BeforeEach(func() { + ctx = context.Background() + + By("Seeding the required data") + integrationFixture.SetupTest() + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" step for testing the create product API with valid input + Describe("Create new product return created status with valid input", func() { + BeforeEach(func() { + // Generate a valid request + request = &dtos.CreateProductRequestDto{ + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + Name: gofakeit.Name(), + } + }) + // "When" step + When("A valid request is made to create a product", func() { + // "Then" step + It("Should returns a StatusCreated response", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.POST("products"). + WithContext(ctx). + WithJSON(request). + Expect(). + Status(http.StatusCreated) + }) + }) + }) + + // "Scenario" step for testing the create product API with invalid price input + Describe("Create product returns a BadRequest status with invalid price input", func() { + BeforeEach(func() { + // Generate an invalid request with zero price + request = &dtos.CreateProductRequestDto{ + Description: gofakeit.AdjectiveDescriptive(), + Price: 0.0, + Name: gofakeit.Name(), + } + }) + // "When" step + When("An invalid request is made with a zero price", func() { + // "Then" step + It("Should return a BadRequest status", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.POST("products"). + WithContext(ctx). + WithJSON(request). + Expect(). + Status(http.StatusBadRequest) + }) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/endtoend/products/features/deletingproduct/v1/delete_product_test.go b/internal/services/catalogwriteservice/test/endtoend/products/features/deletingproduct/v1/delete_product_test.go new file mode 100644 index 00000000..28b7ef5e --- /dev/null +++ b/internal/services/catalogwriteservice/test/endtoend/products/features/deletingproduct/v1/delete_product_test.go @@ -0,0 +1,86 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/gavv/httpexpect/v2" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestDeleteProductEndpoint(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "DeleteProduct Endpoint EndToEnd Tests") +} + +var _ = Describe("Delete Product Feature", func() { + var ( + ctx context.Context + id uuid.UUID + ) + + _ = BeforeEach(func() { + ctx = context.Background() + + By("Seeding the required data") + integrationFixture.SetupTest() + + id = integrationFixture.Items[0].Id + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" step for testing the delete product API with valid input + Describe("Delete product with valid input returns NoContent status", func() { + // "When" step + When("A valid request is made to delete a product", func() { + // "Then" step + It("Should return a NoContent status", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.DELETE("products/{id}"). + WithContext(ctx). + WithPath("id", id.String()). + Expect(). + Status(http.StatusNoContent) + }) + }) + }) + + // "Scenario" step for testing the delete product API with invalid ID + Describe("Delete product with with invalid ID returns NotFound status", func() { + BeforeEach(func() { + // Generate an invalid UUID + id = uuid.NewV4() + }) + + // "When" step + When("An invalid request is made with an invalid ID", func() { + // "Then" step + It("Should return a NotFound status", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.DELETE("products/{id}"). + WithContext(ctx). + WithPath("id", id.String()). + Expect(). + Status(http.StatusNotFound) + }) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/endtoend/products/features/gettingproductbyid/v1/get_product_by_id_test.go b/internal/services/catalogwriteservice/test/endtoend/products/features/gettingproductbyid/v1/get_product_by_id_test.go new file mode 100644 index 00000000..762f35af --- /dev/null +++ b/internal/services/catalogwriteservice/test/endtoend/products/features/gettingproductbyid/v1/get_product_by_id_test.go @@ -0,0 +1,83 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/gavv/httpexpect/v2" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestGetProductByIdEndpoint(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "GetProductById Endpoint EndToEnd Tests") +} + +var _ = Describe("Get Product By Id Feature", func() { + var ( + ctx context.Context + id uuid.UUID + ) + + _ = BeforeEach(func() { + ctx = context.Background() + + By("Seeding the required data") + integrationFixture.SetupTest() + + id = integrationFixture.Items[0].Id + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" step for testing the get product by ID API with a valid ID + Describe("Get product by ID with a valid ID returns ok status", func() { + // "When" step + When("A valid request is made with a valid ID", func() { + // "Then" step + It("Should return an OK status", func() { + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.GET("products/{id}"). + WithPath("id", id). + WithContext(ctx). + Expect(). + Status(http.StatusOK) + }) + }) + }) + + // "Scenario" step for testing the get product by ID API with a valid ID + Describe("Get product by ID with a invalid ID returns NotFound status", func() { + BeforeEach(func() { + // Generate an invalid UUID + id = uuid.NewV4() + }) + When("An invalid request is made with an invalid ID", func() { + // "Then" step + It("Should return a NotFound status", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.GET("products/{id}"). + WithPath("id", id.String()). + WithContext(ctx). + Expect(). + Status(http.StatusNotFound) + }) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/endtoend/products/features/gettingproducts/v1/get_products_test.go b/internal/services/catalogwriteservice/test/endtoend/products/features/gettingproducts/v1/get_products_test.go new file mode 100644 index 00000000..9b67b331 --- /dev/null +++ b/internal/services/catalogwriteservice/test/endtoend/products/features/gettingproducts/v1/get_products_test.go @@ -0,0 +1,57 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/gavv/httpexpect/v2" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestGetAllProductEndpoint(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "GetAllProducts Endpoint EndToEnd Tests") +} + +var _ = Describe("Get All Products Feature", func() { + var ctx context.Context + + _ = BeforeEach(func() { + ctx = context.Background() + + By("Seeding the required data") + integrationFixture.SetupTest() + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" step for testing the get all products API + Describe("Get all products returns ok status", func() { + // "When" step + When("A request is made to get all products", func() { + // "Then" step + It("Should return an OK status", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.GET("products"). + WithContext(ctx). + Expect(). + Status(http.StatusOK) + }) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/endtoend/products/features/searchingproduct/v1/search_products_test.go b/internal/services/catalogwriteservice/test/endtoend/products/features/searchingproduct/v1/search_products_test.go new file mode 100644 index 00000000..fe60eddc --- /dev/null +++ b/internal/services/catalogwriteservice/test/endtoend/products/features/searchingproduct/v1/search_products_test.go @@ -0,0 +1,58 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/gavv/httpexpect/v2" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestSearchProductsEndpoint(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "SearchProducts Endpoint EndToEnd Tests") +} + +var _ = Describe("Search Products Feature", func() { + var ctx context.Context + + _ = BeforeEach(func() { + ctx = context.Background() + + By("Seeding the required data") + integrationFixture.SetupTest() + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" step for testing the search products API + Describe("Search products return ok status", func() { + // "When" step + When("A request is made to search for products", func() { + // "Then" step + It("Should return an OK status", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.GET("products/search"). + WithContext(ctx). + WithQuery("search", integrationFixture.Items[0].Name). + Expect(). + Status(http.StatusOK) + }) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/endtoend/products/features/updatingproduct/v1/update_product_test.go b/internal/services/catalogwriteservice/test/endtoend/products/features/updatingproduct/v1/update_product_test.go new file mode 100644 index 00000000..613e161a --- /dev/null +++ b/internal/services/catalogwriteservice/test/endtoend/products/features/updatingproduct/v1/update_product_test.go @@ -0,0 +1,102 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/brianvoe/gofakeit/v6" + "github.com/gavv/httpexpect/v2" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestUpdateProductEndpoint(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "UpdateProduct Endpoint EndToEnd Tests") +} + +var _ = Describe("UpdateProductE2ETest Suite", func() { + var ( + ctx context.Context + id uuid.UUID + request *dtos.UpdateProductRequestDto + ) + + _ = BeforeEach(func() { + ctx = context.Background() + + By("Seeding the required data") + integrationFixture.SetupTest() + id = integrationFixture.Items[0].Id + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" step for testing the update product API with valid input + Describe("Update product with valid input returns NoContent status", func() { + BeforeEach(func() { + request = &dtos.UpdateProductRequestDto{ + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 1000), + Name: gofakeit.Name(), + } + }) + + // "When" step + When("A valid request is made to update a product", func() { + // "Then" step + It("Should return a NoContent status", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.PUT("products/{id}"). + WithPath("id", id.String()). + WithJSON(request). + WithContext(ctx). + Expect(). + Status(http.StatusNoContent) + }) + }) + }) + + // "Scenario" step for testing the update product API with invalid input + Describe("Update product returns BadRequest with invalid input", func() { + BeforeEach(func() { + // Get a valid product ID from your test data + id = uuid.NewV4() + request = &dtos.UpdateProductRequestDto{ + Description: gofakeit.AdjectiveDescriptive(), + Price: 0, + Name: gofakeit.Name(), + } + }) + // "When" step + When("An invalid request is made to update a product", func() { + // "Then" step + It("Should return a BadRequest status", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.New(GinkgoT(), integrationFixture.BaseAddress) + expect.PUT("products/{id}"). + WithPath("id", id.String()). + WithJSON(request). + WithContext(context.Background()). + Expect(). + Status(http.StatusBadRequest) + }) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/endtoend/products/grpc/product_grpc_service_server_test.go b/internal/services/catalogwriteservice/test/endtoend/products/grpc/product_grpc_service_server_test.go new file mode 100644 index 00000000..23f56439 --- /dev/null +++ b/internal/services/catalogwriteservice/test/endtoend/products/grpc/product_grpc_service_server_test.go @@ -0,0 +1,89 @@ +//go:build e2e +// +build e2e + +package grpc + +import ( + "context" + "testing" + + productService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/grpc/genproto" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestProductGrpcServiceEndToEnd(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "ProductGrpcService EndToEnd Tests") +} + +var _ = Describe("Product Grpc Service Feature", func() { + var ( + ctx context.Context + id uuid.UUID + ) + + _ = BeforeEach(func() { + ctx = context.Background() + + By("Seeding the required data") + integrationFixture.SetupTest() + + id = integrationFixture.Items[0].Id + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" step for testing the creation of a product with valid data in the database + Describe("Creation of a product with valid data in the database", func() { + // "When" step + When("A request is made to create a product with valid data", func() { + // "Then" step + It("Should return a non-empty Id", func() { + // Create a gRPC request with valid data + request := &productService.CreateProductReq{ + Price: gofakeit.Price(100, 1000), + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + } + + // Make the gRPC request to create the product + res, err := integrationFixture.ProductServiceClient.CreateProduct(ctx, request) + Expect(err).To(BeNil()) + Expect(res).NotTo(BeNil()) + Expect(res.ProductId).NotTo(BeEmpty()) + }) + }) + }) + + // "Scenario" step for testing the retrieval of data with a valid ID + Describe("Retrieve product with a valid ID", func() { + // "When" step + When("A request is made to retrieve data with a valid ID", func() { + // "Then" step + It("Should return data with a matching Id", func() { + // Make the gRPC request to retrieve data by ID + res, err := integrationFixture.ProductServiceClient.GetProductById( + ctx, + &productService.GetProductByIdReq{ProductId: id.String()}, + ) + + Expect(err).To(BeNil()) + Expect(res).NotTo(BeNil()) + Expect(res.Product).NotTo(BeNil()) + Expect(res.Product.ProductId).To(Equal(id.String())) + }) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/integration/products/data/repositories/pg_product_repository_integration_test.go b/internal/services/catalogwriteservice/test/integration/products/data/repositories/pg_product_repository_integration_test.go new file mode 100644 index 00000000..384d0890 --- /dev/null +++ b/internal/services/catalogwriteservice/test/integration/products/data/repositories/pg_product_repository_integration_test.go @@ -0,0 +1,245 @@ +//go:build integration +// +build integration + +package repositories + +import ( + "context" + "testing" + "time" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestProductPostgresRepository(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "ProductPostgresRepository Integration Tests") +} + +var _ = Describe("Product Repository Suite", func() { + // Define variables to hold repository and product data + var ( + ctx context.Context + product *models.Product + createdProduct *models.Product + updatedProduct *models.Product + existingProduct *models.Product + err error + id uuid.UUID + ) + + _ = BeforeEach(func() { + By("Seeding the required data") + integrationFixture.SetupTest() + + id = integrationFixture.Items[0].Id + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + _ = BeforeSuite(func() { + ctx = context.Background() + + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + err = integrationFixture.Bus.Start(context.Background()) + Expect(err).ShouldNot(HaveOccurred()) + + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + }) + + _ = AfterSuite(func() { + integrationFixture.Log.Info("TearDownSuite started") + err := integrationFixture.Bus.Stop() + Expect(err).ShouldNot(HaveOccurred()) + time.Sleep(1 * time.Second) + }) + + // "Scenario" step for testing creating a new product in the database + Describe("Creating a new product in the database", func() { + BeforeEach(func() { + product = &models.Product{ + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Id: uuid.NewV4(), + Price: gofakeit.Price(100, 1000), + CreatedAt: time.Now(), + } + }) + + // "When" step + When("CreateProduct function of ProductRepository executed", func() { + BeforeEach(func() { + createdProduct, err = integrationFixture.ProductRepository.CreateProduct(ctx, product) + }) + + // "Then" step + It("Should not return an error", func() { + Expect(err).To(BeNil()) + }) + + It("Should return a non-nil created product", func() { + Expect(createdProduct).NotTo(BeNil()) + }) + + It("Should have the same Id as the input product", func() { + Expect(createdProduct.Id).To(Equal(product.Id)) + }) + + It("Should be able to retrieve the created product from the database", func() { + retrievedProduct, err := integrationFixture.ProductRepository.GetProductById( + ctx, + createdProduct.Id, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(retrievedProduct).NotTo(BeNil()) + Expect(retrievedProduct.ProductId).To(Equal(createdProduct.Id)) + }) + }) + }) + + // "Scenario" step for testing updating an existing product in the database + Describe("Updating an existing product in the database", func() { + BeforeEach(func() { + existingProduct, err = integrationFixture.ProductRepository.GetProductById(ctx, id) + Expect(err).To(BeNil()) + Expect(existingProduct).NotTo(BeNil()) + }) + + // "When" step + When("UpdateProduct function of ProductRepository executed", func() { + BeforeEach(func() { + // Update the name of the existing product + existingProduct.Name = "Updated Product ShortTypeName" + _, err = integrationFixture.ProductRepository.UpdateProduct(ctx, existingProduct) + }) + + // "Then" step + It("Should not return an error", func() { + Expect(err).NotTo(HaveOccurred()) + }) + + It("Should be able to retrieve the updated product from the database", func() { + updatedProduct, err = integrationFixture.ProductRepository.GetProductById( + ctx, + existingProduct.Id, + ) + Expect(err).To(BeNil()) + Expect(updatedProduct).NotTo(BeNil()) + Expect(updatedProduct.Name).To(Equal("Updated Product ShortTypeName")) + // You can add more assertions to validate other properties of the updated product + }) + }) + }) + + // "Scenario" step for testing deleting an existing product in the database + Describe("Deleting an existing product from the database", func() { + BeforeEach(func() { + // Ensure that the product with 'id' exists in the database + product, err := integrationFixture.ProductRepository.GetProductById(ctx, id) + Expect(err).To(BeNil()) + Expect(product).NotTo(BeNil()) + }) + + // "When" step + When("DeleteProduct function of ProductRepository executed", func() { + BeforeEach(func() { + err = integrationFixture.ProductRepository.DeleteProductByID(ctx, id) + }) + + // "Then" step + It("Should not return an error", func() { + Expect(err).To(BeNil()) + }) + + It("Should delete given product from the database", func() { + product, err := integrationFixture.ProductRepository.GetProductById(ctx, id) + Expect(err).To(HaveOccurred()) + Expect(customErrors.IsNotFoundError(err)).To(BeTrue()) + Expect(product).To(BeNil()) + }) + }) + }) + + // "Scenario" step for testing retrieving an existing product from the database + Describe("Retrieving an existing product from the database", func() { + BeforeEach(func() { + // Ensure that the product with 'id' exists in the database + product, err := integrationFixture.ProductRepository.GetProductById(ctx, id) + Expect(err).To(BeNil()) + Expect(product).NotTo(BeNil()) + }) + + // "When" step + When("GetProductById function of ProductRepository executed", func() { + BeforeEach(func() { + existingProduct, err = integrationFixture.ProductRepository.GetProductById(ctx, id) + }) + It("should not return an error", func() { + Expect(err).NotTo(HaveOccurred()) + Expect(existingProduct).NotTo(BeNil()) + }) + + It("should retrieve correct data from database by Id", func() { + Expect(existingProduct.Id).To(Equal(id)) + }) + }) + }) + + // "Scenario" step for testing retrieving a product that does not exist in the database + Describe("Retrieving a product that does not exist in the database", func() { + BeforeEach(func() { + // Ensure that the product with 'id' exists in the database + product, err := integrationFixture.ProductRepository.GetProductById(ctx, id) + Expect(err).To(BeNil()) + Expect(product).NotTo(BeNil()) + }) + + // "When" step + When("GetProductById function of ProductRepository executed", func() { + BeforeEach(func() { + // Use a random UUID that does not exist in the database + nonexistentID := uuid.NewV4() + existingProduct, err = integrationFixture.ProductRepository.GetProductById(ctx, nonexistentID) + }) + + // "Then" step + It("Should return a NotFound error", func() { + Expect(err).To(HaveOccurred()) + Expect(customErrors.IsNotFoundError(err)).To(BeTrue()) + }) + + It("Should not return a product", func() { + Expect(existingProduct).To(BeNil()) + }) + }) + }) + + // "Scenario" step for testing retrieving all existing products from the database + Describe("Retrieving all existing products from the database", func() { + // "When" step + When("GetAllProducts function of ProductRepository executed", func() { + It("should not return an error and return the correct number of products", func() { + res, err := integrationFixture.ProductRepository.GetAllProducts(ctx, utils.NewListQuery(10, 1)) + Expect(err).To(BeNil()) + Expect(res).NotTo(BeNil()) + Expect(len(res.Items)).To(Equal(2)) // Replace with the expected number of products + }) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/integration/products/data/uow/catalogs_unit_of_work_integration_test.go b/internal/services/catalogwriteservice/test/integration/products/data/uow/catalogs_unit_of_work_integration_test.go new file mode 100644 index 00000000..f396ea4d --- /dev/null +++ b/internal/services/catalogwriteservice/test/integration/products/data/uow/catalogs_unit_of_work_integration_test.go @@ -0,0 +1,180 @@ +//go:build integration +// +build integration + +package uow + +import ( + "context" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + data2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "emperror.dev/errors" + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestUnitOfWork(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "CatalogsUnitOfWork Integration Tests") +} + +var _ = Describe("CatalogsUnitOfWork Feature", func() { + // Define variables to hold repository and product data + var ( + ctx context.Context + err error + products *utils.ListResult[*models.Product] + ) + + _ = BeforeEach(func() { + ctx = context.Background() + By("Seeding the required data") + integrationFixture.SetupTest() + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" step for testing a UnitOfWork action that should roll back on error + Describe("Rollback on error", func() { + // "When" step + When("The UnitOfWork Do executed and there is an error in the execution", func() { + It("Should roll back the changes and not affect the database", func() { + err = integrationFixture.CatalogUnitOfWorks.Do(ctx, func(catalogContext data2.CatalogContext) error { + _, err := catalogContext.Products().CreateProduct(ctx, + &models.Product{ + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Id: uuid.NewV4(), + Price: gofakeit.Price(100, 1000), + CreatedAt: time.Now(), + }) + Expect(err).NotTo(HaveOccurred()) // Successful product creation + + return errors.New("error rollback") + }) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(ContainSubstring("error rollback"))) + + products, err := integrationFixture.ProductRepository.GetAllProducts(ctx, utils.NewListQuery(10, 1)) + Expect(err).To(BeNil()) + + Expect(len(products.Items)).To(Equal(2)) // Ensure no changes in the database + }) + }) + }) + + // "Scenario" step for testing a UnitOfWork action that should rollback on panic + Describe("Rollback on panic", func() { + // "When" step + When("The UnitOfWork Do executed and there is an panic in the execution", func() { + It("Should roll back the changes and not affect the database", func() { + err = integrationFixture.CatalogUnitOfWorks.Do(ctx, func(catalogContext data2.CatalogContext) error { + _, err := catalogContext.Products().CreateProduct(ctx, + &models.Product{ + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Id: uuid.NewV4(), + Price: gofakeit.Price(100, 1000), + CreatedAt: time.Now(), + }) + Expect(err).To(BeNil()) // Successful product creation + + panic(errors.New("panic rollback")) + }) + Expect(err).To(HaveOccurred()) + + products, err = integrationFixture.ProductRepository.GetAllProducts(ctx, utils.NewListQuery(10, 1)) + Expect(err).To(BeNil()) + + Expect(len(products.Items)).To(Equal(2)) // Ensure no changes in the database + }) + }) + }) + + // "Scenario" step for testing a UnitOfWork action that should rollback when the context is canceled + Describe("Cancelling the context", func() { + // "When" step + When("the UnitOfWork Do executed and cancel the context", func() { + It("Should roll back the changes and not affect the database", func() { + cancelCtx, cancel := context.WithCancel(ctx) + + err := integrationFixture.CatalogUnitOfWorks.Do( + cancelCtx, + func(catalogContext data2.CatalogContext) error { + _, err := catalogContext.Products().CreateProduct(ctx, + &models.Product{ + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Id: uuid.NewV4(), + Price: gofakeit.Price(100, 1000), + CreatedAt: time.Now(), + }) + Expect(err).To(BeNil()) // Successful product creation + + _, err = catalogContext.Products().CreateProduct(ctx, + &models.Product{ + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Id: uuid.NewV4(), + Price: gofakeit.Price(100, 1000), + CreatedAt: time.Now(), + }) + Expect(err).To(BeNil()) // Successful product creation + + cancel() // Cancel the context + + return err + }, + ) + Expect(err).To(HaveOccurred()) + + // Validate that changes are rolled back in the database + products, err := integrationFixture.ProductRepository.GetAllProducts(ctx, utils.NewListQuery(10, 1)) + Expect(err).To(BeNil()) + Expect(len(products.Items)).To(Equal(2)) // Ensure no changes in the database + }) + }) + }) + + // "Scenario" step for testing a UnitOfWork action that should commit on success + Describe("Commit on success", func() { + // "When" step + When("the UnitOfWork Do executed and operation was successfull", func() { + It("Should commit the changes to the database", func() { + err := integrationFixture.CatalogUnitOfWorks.Do(ctx, func(catalogContext data2.CatalogContext) error { + _, err := catalogContext.Products().CreateProduct(ctx, + &models.Product{ + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Id: uuid.NewV4(), + Price: gofakeit.Price(100, 1000), + CreatedAt: time.Now(), + }) + Expect(err).To(BeNil()) // Successful product creation + + return err + }) + Expect(err).To(BeNil()) // No error indicates success + + // Validate that changes are committed in the database + products, err := integrationFixture.ProductRepository.GetAllProducts(ctx, utils.NewListQuery(10, 1)) + Expect(err).To(BeNil()) + Expect(len(products.Items)).To(Equal(3)) // Ensure changes in the database + }) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/integration/products/features/creatingproduct/v1/create_product_test.go b/internal/services/catalogwriteservice/test/integration/products/features/creatingproduct/v1/create_product_test.go new file mode 100644 index 00000000..0bd40b16 --- /dev/null +++ b/internal/services/catalogwriteservice/test/integration/products/features/creatingproduct/v1/create_product_test.go @@ -0,0 +1,243 @@ +//go:build integration +// +build integration + +package v1 + +import ( + "context" + "testing" + "time" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/hypothesis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging" + createProductCommand "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos" + integrationEvents "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/events/integrationevents" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/brianvoe/gofakeit/v6" + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestCreateProduct(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "Create Product Integration Tests") +} + +// https://specflow.org/learn/gherkin/#learn-gherkin +// scenario +var _ = Describe("Creating Product Feature", func() { + var ( + ctx context.Context + err error + command *createProductCommand.CreateProduct + result *dtos.CreateProductResponseDto + createdProduct *models.Product + id uuid.UUID + shouldPublish hypothesis.Hypothesis[*integrationEvents.ProductCreatedV1] + ) + + _ = BeforeEach(func() { + By("Seeding the required data") + // call base SetupTest hook before running child hook + integrationFixture.SetupTest() + + // child hook codes should be here + id = integrationFixture.Items[0].Id + }) + + _ = AfterEach(func() { + By("Cleanup test data") + // call base TearDownTest hook before running child hook + integrationFixture.TearDownTest() + + // child hook codes should be here + }) + + _ = BeforeSuite(func() { + ctx = context.Background() + + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + err = integrationFixture.Bus.Start(context.Background()) + Expect(err).ShouldNot(HaveOccurred()) + + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + }) + + _ = AfterSuite(func() { + integrationFixture.Log.Info("TearDownSuite started") + err := integrationFixture.Bus.Stop() + Expect(err).ShouldNot(HaveOccurred()) + time.Sleep(1 * time.Second) + }) + + // "Scenario" step for testing creating a new product + Describe( + "Creating a new product and saving it to the database when product doesn't exists", + func() { + Context("Given new product doesn't exists in the system", func() { + BeforeEach(func() { + command, err = createProductCommand.NewCreateProduct( + gofakeit.Name(), + gofakeit.AdjectiveDescriptive(), + gofakeit.Price(150, 6000), + ) + Expect(err).ToNot(HaveOccurred()) + Expect(command).ToNot(BeNil()) + }) + + When( + "the CreateProduct command is executed for non-existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*createProductCommand.CreateProduct, *dtos.CreateProductResponseDto]( + ctx, + command, + ) + }) + + It("Should create the product successfully", func() { + Expect(err).NotTo(HaveOccurred()) + Expect(result).NotTo(BeNil()) + }) + + It( + "Should have a non-empty product ID matching the command ID", + func() { + Expect( + result.ProductID, + ).To(Equal(command.ProductID)) + }, + ) + + It( + "Should be able to retrieve the product from the database", + func() { + createdProduct, err = integrationFixture.CatalogsDBContext.FindProductByID( + ctx, + result.ProductID, + ) + Expect(err).NotTo(HaveOccurred()) + + Expect(result).NotTo(BeNil()) + Expect( + command.ProductID, + ).To(Equal(result.ProductID)) + Expect(createdProduct).NotTo(BeNil()) + }, + ) + }, + ) + }) + }, + ) + + // "Scenario" step for testing creating a product with duplicate data + Describe( + "Creating a new product with duplicate data and already exists product", + func() { + Context("Given product already exists in the system", func() { + BeforeEach(func() { + command = &createProductCommand.CreateProduct{ + Name: gofakeit.Name(), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(150, 6000), + ProductID: id, + } + }) + + When( + "the CreateProduct command is executed for existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*createProductCommand.CreateProduct, *dtos.CreateProductResponseDto]( + ctx, + command, + ) + }) + + It( + "Should return an error indicating duplicate record", + func() { + Expect(err).To(HaveOccurred()) + Expect( + customErrors.IsConflictError( + err, + ), + ).To(BeTrue()) + }, + ) + + It("Should not return a result", func() { + Expect(result).To(BeNil()) + }) + }, + ) + }) + }, + ) + + // "Scenario" step for testing creating a product with duplicate data + Describe( + "Publishing ProductCreated event to the broker when product saved successfully", + func() { + Context("Given new product doesn't exists in the system", func() { + BeforeEach(func() { + shouldPublish = messaging.ShouldProduced[*integrationEvents.ProductCreatedV1]( + ctx, + integrationFixture.Bus, + nil, + ) + command, err = createProductCommand.NewCreateProduct( + gofakeit.Name(), + gofakeit.AdjectiveDescriptive(), + gofakeit.Price(150, 6000), + ) + Expect(err).ToNot(HaveOccurred()) + }) + + When( + "CreateProduct command is executed for non-existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*createProductCommand.CreateProduct, *dtos.CreateProductResponseDto]( + ctx, + command, + ) + }) + + It("Should return no error", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + It("Should return not nil result", func() { + Expect(result).ToNot(BeNil()) + }) + + It( + "Should publish ProductCreated event to the broker", + func() { + // ensuring message published to the rabbitmq broker + shouldPublish.Validate( + ctx, + "there is no published message", + time.Second*30, + ) + }, + ) + }, + ) + }) + }, + ) +}) diff --git a/internal/services/catalogwriteservice/test/integration/products/features/deletingproduct/v1/delete_product_test.go b/internal/services/catalogwriteservice/test/integration/products/features/deletingproduct/v1/delete_product_test.go new file mode 100644 index 00000000..7e23287d --- /dev/null +++ b/internal/services/catalogwriteservice/test/integration/products/features/deletingproduct/v1/delete_product_test.go @@ -0,0 +1,206 @@ +//go:build integration +// +build integration + +package v1 + +import ( + "context" + "fmt" + "testing" + "time" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/hypothesis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging" + v1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1" + integrationEvents "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1/events/integrationevents" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestDeleteProduct(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "Delete Product Integration Tests") +} + +// https://specflow.org/learn/gherkin/#learn-gherkin +// scenario +var _ = Describe("Delete Product Feature", func() { + var ( + ctx context.Context + err error + command *v1.DeleteProduct + result *mediatr.Unit + id uuid.UUID + notExistsId uuid.UUID + shouldPublish hypothesis.Hypothesis[*integrationEvents.ProductDeletedV1] + ) + + _ = BeforeEach(func() { + By("Seeding the required data") + // call base SetupTest hook before running child hook + integrationFixture.SetupTest() + + // child hook codes should be here + id = integrationFixture.Items[0].Id + }) + + _ = AfterEach(func() { + By("Cleanup test data") + // call base TearDownTest hook before running child hook + integrationFixture.TearDownTest() + + // child hook codes should be here + }) + + _ = BeforeSuite(func() { + ctx = context.Background() + + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + err = integrationFixture.Bus.Start(context.Background()) + Expect(err).ShouldNot(HaveOccurred()) + + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + }) + + _ = AfterSuite(func() { + integrationFixture.Log.Info("TearDownSuite started") + err := integrationFixture.Bus.Stop() + Expect(err).ShouldNot(HaveOccurred()) + time.Sleep(1 * time.Second) + }) + + // "Scenario" step for testing deleting an existing product + Describe("Deleting an existing product from the database", func() { + Context("Given product already exists in the system", func() { + BeforeEach(func() { + shouldPublish = messaging.ShouldProduced[*integrationEvents.ProductDeletedV1]( + ctx, + integrationFixture.Bus, + nil, + ) + command, err = v1.NewDeleteProduct(id) + Expect(err).ShouldNot(HaveOccurred()) + }) + + When( + "the DeleteProduct command is executed for existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*v1.DeleteProduct, *mediatr.Unit]( + ctx, + command, + ) + }) + + It("Should not return an error", func() { + Expect(err).NotTo(HaveOccurred()) + }) + + It("Should delete the product from the database", func() { + deletedProduct, err := integrationFixture.CatalogsDBContext.FindProductByID( + ctx, + id, + ) + Expect(err).To(HaveOccurred()) + Expect( + err, + ).To(MatchError(ContainSubstring(fmt.Sprintf("product with id `%s` not found in the database", command.ProductID.String())))) + Expect(deletedProduct).To(BeNil()) + }) + }, + ) + }) + }) + + // "Scenario" step for testing deleting a non-existing product + Describe("Deleting a non-existing product from the database", func() { + Context("Given product does not exists in the system", func() { + BeforeEach(func() { + notExistsId = uuid.NewV4() + command, err = v1.NewDeleteProduct(notExistsId) + Expect(err).ShouldNot(HaveOccurred()) + }) + + When( + "the DeleteProduct command is executed for non-existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*v1.DeleteProduct, *mediatr.Unit]( + ctx, + command, + ) + }) + + It("Should return an error", func() { + Expect(err).To(HaveOccurred()) + }) + + It("Should return a NotFound error", func() { + Expect( + err, + ).To(MatchError(ContainSubstring(fmt.Sprintf("product with id `%s` not found in the database", command.ProductID.String())))) + }) + + It("Should return a custom NotFound error", func() { + Expect(customErrors.IsNotFoundError(err)).To(BeTrue()) + }) + + It("Should not return a result", func() { + Expect(result).To(BeNil()) + }) + }, + ) + }) + }) + + Describe( + "Publishing ProductDeleted event when product deleted successfully", + func() { + Context("Given product already exists in the system", func() { + BeforeEach(func() { + shouldPublish = messaging.ShouldProduced[*integrationEvents.ProductDeletedV1]( + ctx, + integrationFixture.Bus, + nil, + ) + command, err = v1.NewDeleteProduct(id) + Expect(err).ShouldNot(HaveOccurred()) + }) + + When( + "the DeleteProduct command is executed for existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*v1.DeleteProduct, *mediatr.Unit]( + ctx, + command, + ) + }) + + It( + "Should publish ProductDeleted event to the broker", + func() { + // ensuring message published to the rabbitmq broker + shouldPublish.Validate( + ctx, + "there is no published message", + time.Second*30, + ) + }, + ) + }, + ) + }) + }, + ) +}) diff --git a/internal/services/catalogwriteservice/test/integration/products/features/gettingproductbyid/v1/get_product_by_id_test.go b/internal/services/catalogwriteservice/test/integration/products/features/gettingproductbyid/v1/get_product_by_id_test.go new file mode 100644 index 00000000..af567468 --- /dev/null +++ b/internal/services/catalogwriteservice/test/integration/products/features/gettingproductbyid/v1/get_product_by_id_test.go @@ -0,0 +1,166 @@ +//go:build integration +// +build integration + +package v1 + +import ( + "context" + "fmt" + "testing" + "time" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + getProductByIdQuery "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestGetProductById(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "Get Product By ID Integration Tests") +} + +var _ = Describe("Get Product by ID Feature", func() { + var ( + ctx context.Context + id uuid.UUID + query *getProductByIdQuery.GetProductById + result *dtos.GetProductByIdResponseDto + err error + ) + + _ = BeforeEach(func() { + By("Seeding the required data") + // call base SetupTest hook before running child hook + integrationFixture.SetupTest() + + // child hook codes should be here + id = integrationFixture.Items[0].Id + }) + + _ = AfterEach(func() { + By("Cleanup test data") + // call base TearDownTest hook before running child hook + integrationFixture.TearDownTest() + + // child hook codes should be here + }) + + _ = BeforeSuite(func() { + ctx = context.Background() + + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + err = integrationFixture.Bus.Start(context.Background()) + Expect(err).ShouldNot(HaveOccurred()) + + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + }) + + _ = AfterSuite(func() { + integrationFixture.Log.Info("TearDownSuite started") + err := integrationFixture.Bus.Stop() + Expect(err).ShouldNot(HaveOccurred()) + time.Sleep(1 * time.Second) + }) + + // "Scenario" step for testing returning an existing product with correct properties + Describe( + "Returning an existing product with valid Id from the database with correct properties", + func() { + Context("Given products exists in the database", func() { + BeforeEach(func() { + query, err = getProductByIdQuery.NewGetProductById(id) + }) + + // "When" step + When( + "the GteProductById query is executed for existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*getProductByIdQuery.GetProductById, *dtos.GetProductByIdResponseDto]( + ctx, + query, + ) + }) + + // "Then" step + It("Should not return an error", func() { + Expect(err).To(BeNil()) + }) + + It("Should return a non-nil result", func() { + Expect(result).NotTo(BeNil()) + }) + + It( + "Should return a product with the correct ID", + func() { + Expect(result.Product).NotTo(BeNil()) + Expect(result.Product.Id).To(Equal(id)) + }, + ) + }, + ) + }) + }, + ) + + // "Scenario" step for testing returning a NotFound error when record does not exist + Describe( + "Returning a NotFound error when product with specific id does not exist", + func() { + Context("Given products does not exists in the database", func() { + BeforeEach(func() { + // Generate a random UUID that does not exist in the database + id = uuid.NewV4() + query, err = getProductByIdQuery.NewGetProductById(id) + Expect(err).To(BeNil()) + }) + + // "When" step + When( + "the GteProductById query is executed for non-existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*getProductByIdQuery.GetProductById, *dtos.GetProductByIdResponseDto]( + ctx, + query, + ) + }) + + // "Then" step + It("Should return an error", func() { + Expect(err).To(HaveOccurred()) + }) + + It("Should return a NotFound error", func() { + Expect( + err, + ).To(MatchError(ContainSubstring(fmt.Sprintf("product with id `%s` not found in the database", query.ProductID.String())))) + }) + + It("Should return a custom NotFound error", func() { + Expect( + customErrors.IsNotFoundError(err), + ).To(BeTrue()) + }) + + It("Should not return a result", func() { + Expect(result).To(BeNil()) + }) + }, + ) + }) + }, + ) +}) diff --git a/internal/services/catalogwriteservice/test/integration/products/features/gettingproducts/v1/get_products_test.go b/internal/services/catalogwriteservice/test/integration/products/features/gettingproducts/v1/get_products_test.go new file mode 100644 index 00000000..6c6efe2f --- /dev/null +++ b/internal/services/catalogwriteservice/test/integration/products/features/gettingproducts/v1/get_products_test.go @@ -0,0 +1,119 @@ +//go:build integration +// +build integration + +package v1 + +import ( + "context" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + gettingproductsv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/mehdihadeli/go-mediatr" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestGetProducts(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "Get Products Integration Tests") +} + +var _ = Describe("Get All Products Feature", func() { + // Define variables to hold query and result data + var ( + ctx context.Context + query *gettingproductsv1.GetProducts + queryResult *dtos.GetProductsResponseDto + err error + ) + + _ = BeforeEach(func() { + By("Seeding the required data") + // call base SetupTest hook before running child hook + integrationFixture.SetupTest() + + // child hook codes should be here + }) + + _ = AfterEach(func() { + By("Cleanup test data") + // call base TearDownTest hook before running child hook + integrationFixture.TearDownTest() + + // child hook codes should be here + }) + + _ = BeforeSuite(func() { + ctx = context.Background() + + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + err = integrationFixture.Bus.Start(context.Background()) + Expect(err).ShouldNot(HaveOccurred()) + + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + }) + + _ = AfterSuite(func() { + integrationFixture.Log.Info("TearDownSuite started") + err := integrationFixture.Bus.Stop() + Expect(err).ShouldNot(HaveOccurred()) + time.Sleep(1 * time.Second) + }) + + // "Scenario" step for testing getting a list of existing products + Describe("Getting a list of existing products from the database", func() { + Context("Given existing products in the database", func() { + BeforeEach(func() { + // Create a query to retrieve a list of products + query, err = gettingproductsv1.NewGetProducts( + utils.NewListQuery(10, 1), + ) + Expect(err).To(BeNil()) + }) + + // "When" step + When( + "the GteProducts query is executed for existing products", + func() { + BeforeEach(func() { + queryResult, err = mediatr.Send[*gettingproductsv1.GetProducts, *dtos.GetProductsResponseDto]( + ctx, + query, + ) + }) + + // "Then" step + It("Should not return an error", func() { + Expect(err).To(BeNil()) + }) + + It("Should return a non-nil result", func() { + Expect(queryResult).NotTo(BeNil()) + }) + + It("Should return a list of products with items", func() { + Expect(queryResult.Products).NotTo(BeNil()) + Expect(queryResult.Products.Items).NotTo(BeEmpty()) + }) + + It("Should return the expected number of products", func() { + // Replace 'len(c.Products)' with the expected number of products + Expect( + len(queryResult.Products.Items), + ).To(Equal(len(integrationFixture.Items))) + }) + }, + ) + }) + }) +}) diff --git a/internal/services/catalogwriteservice/test/integration/products/features/updatingproduct/v1/update_product_test.go b/internal/services/catalogwriteservice/test/integration/products/features/updatingproduct/v1/update_product_test.go new file mode 100644 index 00000000..03e10946 --- /dev/null +++ b/internal/services/catalogwriteservice/test/integration/products/features/updatingproduct/v1/update_product_test.go @@ -0,0 +1,236 @@ +//go:build integration +// +build integration + +package v1 + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/hypothesis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging" + datamodel "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + v1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1/events/integrationevents" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/integration" + + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestUpdateProduct(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "Updated Products Integration Tests") +} + +var _ = Describe("Update Product Feature", func() { + // Define variables to hold command and result data + var ( + ctx context.Context + existingProduct *datamodel.ProductDataModel + command *v1.UpdateProduct + result *mediatr.Unit + err error + id uuid.UUID + shouldPublish hypothesis.Hypothesis[*integrationevents.ProductUpdatedV1] + ) + + _ = BeforeEach(func() { + By("Seeding the required data") + integrationFixture.SetupTest() + + existingProduct = integrationFixture.Items[0] + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + _ = BeforeSuite(func() { + ctx = context.Background() + + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + err = integrationFixture.Bus.Start(context.Background()) + Expect(err).ShouldNot(HaveOccurred()) + + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + }) + + _ = AfterSuite(func() { + integrationFixture.Log.Info("TearDownSuite started") + err := integrationFixture.Bus.Stop() + Expect(err).ShouldNot(HaveOccurred()) + time.Sleep(1 * time.Second) + }) + + // "Scenario" step for testing updating an existing product + Describe("Updating an existing product in the database", func() { + Context("Given product exists in the database", func() { + BeforeEach(func() { + command, err = v1.NewUpdateProduct( + existingProduct.Id, + "Updated Product ShortTypeName", + existingProduct.Description, + existingProduct.Price, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + // "When" step + When("the UpdateProduct command is executed", func() { + BeforeEach(func() { + result, err = mediatr.Send[*v1.UpdateProduct, *mediatr.Unit]( + ctx, + command, + ) + }) + + // "Then" step + It("Should not return an error", func() { + Expect(err).NotTo(HaveOccurred()) + Expect(result).NotTo(BeNil()) + }) + + It("Should return a non-nil result", func() { + Expect(result).NotTo(BeNil()) + }) + + It( + "Should update the existing product in the database", + func() { + updatedProduct, err := integrationFixture.CatalogsDBContext.FindProductByID( + ctx, + existingProduct.Id, + ) + Expect(err).To(BeNil()) + Expect(updatedProduct).NotTo(BeNil()) + Expect( + updatedProduct.Id, + ).To(Equal(existingProduct.Id)) + Expect( + updatedProduct.Price, + ).To(Equal(existingProduct.Price)) + Expect( + updatedProduct.Name, + ).NotTo(Equal(existingProduct.Name)) + }, + ) + }) + }) + }) + + // "Scenario" step for testing updating a non-existing product + Describe("Updating a non-existing product in the database", func() { + Context("Given product not exists in the database", func() { + BeforeEach(func() { + // Generate a random ID that does not exist in the database + id = uuid.NewV4() + command, err = v1.NewUpdateProduct( + id, + "Updated Product ShortTypeName", + "Updated Product Description", + 100, + ) + Expect(err).NotTo(HaveOccurred()) + }) + + // "When" step + When( + "the UpdateProduct command executed for non-existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*v1.UpdateProduct, *mediatr.Unit]( + ctx, + command, + ) + }) + + // "Then" step + It("Should return an error", func() { + Expect(err).To(HaveOccurred()) + }) + It("Should not return a result", func() { + Expect(result).To(BeNil()) + }) + + It("Should return a NotFound error", func() { + Expect( + err, + ).To(MatchError(ContainSubstring(fmt.Sprintf("product with id `%s` not found", id.String())))) + }) + + It("Should return a custom NotFound error", func() { + Expect(customErrors.IsNotFoundError(err)).To(BeTrue()) + Expect( + customErrors.IsApplicationError( + err, + http.StatusNotFound, + ), + ).To(BeTrue()) + }) + }, + ) + }) + }) + + // "Scenario" step for testing updating an existing product + Describe( + "Publishing ProductUpdated when product updated successfully", + func() { + Context("Given product exists in the database", func() { + BeforeEach(func() { + command, err = v1.NewUpdateProduct( + existingProduct.Id, + "Updated Product ShortTypeName", + existingProduct.Description, + existingProduct.Price, + ) + Expect(err).NotTo(HaveOccurred()) + + shouldPublish = messaging.ShouldProduced[*integrationevents.ProductUpdatedV1]( + ctx, + integrationFixture.Bus, + nil, + ) + }) + + // "When" step + When( + "the UpdateProduct command is executed for existing product", + func() { + BeforeEach(func() { + result, err = mediatr.Send[*v1.UpdateProduct, *mediatr.Unit]( + ctx, + command, + ) + }) + + It( + "Should publish ProductUpdated event to the broker", + func() { + // ensuring message published to the rabbitmq broker + shouldPublish.Validate( + ctx, + "there is no published message", + time.Second*30, + ) + }, + ) + }, + ) + }) + }, + ) +}) diff --git a/internal/services/catalogwriteservice/test/load/.openapi-generator-ignore b/internal/services/catalogwriteservice/test/load/.openapi-generator-ignore new file mode 100644 index 00000000..7484ee59 --- /dev/null +++ b/internal/services/catalogwriteservice/test/load/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/internal/services/catalogwriteservice/test/load/.openapi-generator/FILES b/internal/services/catalogwriteservice/test/load/.openapi-generator/FILES new file mode 100644 index 00000000..297b5aa9 --- /dev/null +++ b/internal/services/catalogwriteservice/test/load/.openapi-generator/FILES @@ -0,0 +1,3 @@ +.openapi-generator-ignore +README.md +script.js diff --git a/internal/services/catalogwriteservice/test/load/.openapi-generator/VERSION b/internal/services/catalogwriteservice/test/load/.openapi-generator/VERSION new file mode 100644 index 00000000..66672d4e --- /dev/null +++ b/internal/services/catalogwriteservice/test/load/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.1.0-SNAPSHOT \ No newline at end of file diff --git a/internal/services/catalogwriteservice/test/load/README.md b/internal/services/catalogwriteservice/test/load/README.md new file mode 100644 index 00000000..64a75216 --- /dev/null +++ b/internal/services/catalogwriteservice/test/load/README.md @@ -0,0 +1,15 @@ +# Generated k6 script + +The `script.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs. + +Global header variables are defined at the top of the file, like `api_key`. Each path in the specification is converted into a [group](https://docs.k6.io/docs/tags-and-groups) in k6 and each group contains all the request methods related to that path. Path and query parameters are extracted from the specification and put at the start of the group. The URL is constructed from the base URL plus path and query. + +If the Swagger/OpenAPI specification used as the input spec contains examples at parameter level, those will be extracted and utilized as parameter values. The `handleParamValue` custom Mustache lambda registered for use in the K6 `script.mustache` template handles the conditional checks, formatting, and outputting of parameter values. If a given parameter has value specified – either in `example` or `examples` field, defined at the parameter level – that value will be used. For list (`examples`), entire list will be output in the generated script and the first element from that list will be assigned as parameter value. If a given parameter does not have an example defined, a placeholder value with `TODO_EDIT_THE_` prefix will be generated for that parameter, and you will have to assign a value before you can run the script. In other words, you can now generate K6 test scripts which are ready to run, provided the Swagger/OpenAPI specification used as the input spec contains examples for all of the path/query parameters; see `modules/openapi-generator/src/test/resources/3_0/examples.yaml` for an example of such specification, and https://swagger.io/docs/specification/adding-examples/ for more information about adding examples. + +k6 specific parameters are in the [`params`](https://docs.k6.io/docs/params-k6http) object, and `body` contains the [request](https://docs.k6.io/docs/http-requests) body which is in the form of `identifier: type`, which the `type` should be substituted by a proper value. Then goes the request and the check. + +[Check](https://docs.k6.io/docs/checks) are like asserts but differ in that they don't halt execution, instead they just store the result of the check, pass or fail, and let the script execution continue. + +Each request is always followed by a 0.1 second [sleep](https://docs.k6.io/docs/sleep-t-1) to prevent the script execution from flooding the system with too many requests simultaneously. + +Note that the default iteration count and VU count is 1. So each request in each group will be executed once. For more information, see the [k6 options](https://docs.k6.io/docs/options). diff --git a/internal/services/catalogwriteservice/test/load/script.js b/internal/services/catalogwriteservice/test/load/script.js new file mode 100644 index 00000000..f722356f --- /dev/null +++ b/internal/services/catalogwriteservice/test/load/script.js @@ -0,0 +1,98 @@ +/* + * Catalogs Write-Service Api + * Catalogs Write-Service Api. + * + * OpenAPI spec version: 1.0 + * + * NOTE: This class is auto generated by OpenAPI Generator. + * https://github.com/OpenAPITools/openapi-generator + * + * OpenAPI generator version: 6.1.0-SNAPSHOT + */ + + +import http from "k6/http"; +import { group, check, sleep } from "k6"; + +const BASE_URL = ""; +// Sleep duration between successive requests. +// You might want to edit the value of this variable or remove calls to the sleep function on the script. +const SLEEP_DURATION = 0.1; +// Global variables should be initialized. + +export default function() { + group("/api/v1/products/search", () => { + let search = 'TODO_EDIT_THE_SEARCH'; // specify value as there is no example value for this parameter in OpenAPI spec + let size = 'TODO_EDIT_THE_SIZE'; // specify value as there is no example value for this parameter in OpenAPI spec + let orderBy = 'TODO_EDIT_THE_ORDERBY'; // specify value as there is no example value for this parameter in OpenAPI spec + let page = 'TODO_EDIT_THE_PAGE'; // specify value as there is no example value for this parameter in OpenAPI spec + + // Request No. 1 + { + let url = BASE_URL + `/api/v1/products/search?orderBy=${orderBy}&page=${page}&search=${search}&size=${size}`; + let request = http.get(url); + + check(request, { + "OK": (r) => r.status === 200 + }); + } + }); + + group("/api/v1/products", () => { + let size = 'TODO_EDIT_THE_SIZE'; // specify value as there is no example value for this parameter in OpenAPI spec + let orderBy = 'TODO_EDIT_THE_ORDERBY'; // specify value as there is no example value for this parameter in OpenAPI spec + let page = 'TODO_EDIT_THE_PAGE'; // specify value as there is no example value for this parameter in OpenAPI spec + + // Request No. 1 + { + let url = BASE_URL + `/api/v1/products?orderBy=${orderBy}&page=${page}&size=${size}`; + let request = http.get(url); + + check(request, { + "OK": (r) => r.status === 200 + }); + + sleep(SLEEP_DURATION); + } + + // Request No. 2 + { + let url = BASE_URL + `/api/v1/products`; + // TODO: edit the parameters of the request body. + let body = {"description": "string", "name": "string", "price": "bigdecimal"}; + let params = {headers: {"Content-Type": "application/json", "Accept": "application/json"}}; + let request = http.post(url, JSON.stringify(body), params); + + check(request, { + "Created": (r) => r.status === 201 + }); + } + }); + + group("/api/v1/products/{id}", () => { + let id = 'TODO_EDIT_THE_ID'; // specify value as there is no example value for this parameter in OpenAPI spec + + // Request No. 1 + { + let url = BASE_URL + `/api/v1/products/${id}`; + let request = http.get(url); + + check(request, { + "OK": (r) => r.status === 200 + }); + + sleep(SLEEP_DURATION); + } + + // Request No. 2 + { + let url = BASE_URL + `/api/v1/products/${id}`; + let request = http.del(url); + + check(request, { + "": (r) => r.status === 204 + }); + } + }); + +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/creatingproduct/v1/create_product_handler_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/creatingproduct/v1/create_product_handler_unit_test.go new file mode 100644 index 00000000..1a3b5d5a --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/creatingproduct/v1/create_product_handler_unit_test.go @@ -0,0 +1,170 @@ +//go:build unit +// +build unit + +package v1 + +import ( + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + datamodels "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + creatingproductv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1" + creatingproductdtosv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + "emperror.dev/errors" + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +type createProductHandlerUnitTests struct { + *unittest.UnitTestSharedFixture + handler cqrs.RequestHandlerWithRegisterer[*creatingproductv1.CreateProduct, *creatingproductdtosv1.CreateProductResponseDto] +} + +func TestCreateProductHandlerUnit(t *testing.T) { + suite.Run(t, &createProductHandlerUnitTests{ + UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t), + }, + ) +} + +func (c *createProductHandlerUnitTests) SetupTest() { + // call base SetupTest hook before running child hook + c.UnitTestSharedFixture.SetupTest() + c.handler = creatingproductv1.NewCreateProductHandler( + fxparams.ProductHandlerParams{ + CatalogsDBContext: c.CatalogDBContext, + Tracer: c.Tracer, + RabbitmqProducer: c.Bus, + Log: c.Log, + }, + ) +} + +func (c *createProductHandlerUnitTests) TearDownTest() { + // call base TearDownTest hook before running child hook + c.UnitTestSharedFixture.TearDownTest() +} + +func (c *createProductHandlerUnitTests) Test_Handle_Should_Create_New_Product_With_Valid_Data() { + id := uuid.NewV4() + + createProduct := &creatingproductv1.CreateProduct{ + ProductID: id, + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.EmojiDescription(), + Price: gofakeit.Price(100, 1000), + } + + c.BeginTx() + _, err := c.handler.Handle(c.Ctx, createProduct) + c.CommitTx() + + c.Require().NoError(err) + + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 1) + + res, err := gormdbcontext.FindModelByID[*datamodels.ProductDataModel, *models.Product]( + c.Ctx, + c.CatalogDBContext, + id, + ) + c.Require().NoError(err) + + c.Assert().Equal(res.Id, id) +} + +func (c *createProductHandlerUnitTests) Test_Handle_Should_Return_Error_For_Duplicate_Item() { + id := uuid.NewV4() + + createProduct := &creatingproductv1.CreateProduct{ + ProductID: id, + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.EmojiDescription(), + Price: gofakeit.Price(100, 1000), + } + + c.BeginTx() + dto, err := c.handler.Handle(c.Ctx, createProduct) + c.Require().NoError(err) + c.Require().NotNil(dto) + c.CommitTx() + + c.BeginTx() + dto, err = c.handler.Handle(c.Ctx, createProduct) + c.CommitTx() + + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 1) + c.True(customErrors.IsConflictError(err)) + c.ErrorContains(err, "product already exists") + c.Nil(dto) +} + +func (c *createProductHandlerUnitTests) Test_Handle_Should_Return_Error_For_Error_In_Bus() { + id := uuid.NewV4() + + createProduct := &creatingproductv1.CreateProduct{ + ProductID: id, + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.EmojiDescription(), + Price: gofakeit.Price(100, 1000), + } + + // override called mock + // https://github.com/stretchr/testify/issues/558 + c.Bus.Mock.ExpectedCalls = nil + c.Bus.On("PublishMessage", mock.Anything, mock.Anything, mock.Anything). + Once(). + Return(errors.New("error in the publish message")) + + c.BeginTx() + + dto, err := c.handler.Handle(c.Ctx, createProduct) + + c.CommitTx() + + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 1) + c.ErrorContains(err, "error in the publish message") + c.ErrorContains( + err, + "error in publishing ProductCreated integration_events event", + ) + c.Nil(dto) +} + +func (c *createProductHandlerUnitTests) Test_Handle_Should_Return_Error_For_Error_In_Mapping() { + id := uuid.NewV4() + + createProduct := &creatingproductv1.CreateProduct{ + ProductID: id, + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.EmojiDescription(), + Price: gofakeit.Price(100, 1000), + } + + mapper.ClearMappings() + + c.BeginTx() + + dto, err := c.handler.Handle(c.Ctx, createProduct) + + c.CommitTx() + + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 0) + c.ErrorContains(err, "error in the mapping") + c.True(customErrors.IsInternalServerError(err)) + c.Nil(dto) +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/creatingproduct/v1/create_product_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/creatingproduct/v1/create_product_unit_test.go new file mode 100644 index 00000000..3d669c5e --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/creatingproduct/v1/create_product_unit_test.go @@ -0,0 +1,85 @@ +//go:build unit +// +build unit + +package v1 + +import ( + "fmt" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + createProductCommand "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/creatingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + "github.com/brianvoe/gofakeit/v6" + "github.com/stretchr/testify/suite" +) + +type createProductUnitTests struct { + *unittest.UnitTestSharedFixture +} + +func TestCreateProductUnit(t *testing.T) { + suite.Run( + t, + &createProductUnitTests{ + UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t), + }, + ) +} + +func (c *createProductUnitTests) Test_New_Create_Product_Should_Return_No_Error_For_Valid_Input() { + name := gofakeit.Name() + description := gofakeit.EmojiDescription() + price := gofakeit.Price(150, 6000) + + createProduct, err := createProductCommand.NewCreateProductWithValidation( + name, + description, + price, + ) + var g interface{} = createProduct + d, ok := g.(cqrs.Command) + if ok { + fmt.Println(d) + } + + c.Assert().NotNil(createProduct) + c.Assert().Equal(name, createProduct.Name) + c.Assert().Equal(price, createProduct.Price) + + c.Require().NoError(err) +} + +func (c *createProductUnitTests) Test_New_Create_Product_Should_Return_Error_For_Invalid_Price() { + command, err := createProductCommand.NewCreateProductWithValidation( + gofakeit.Name(), + gofakeit.EmojiDescription(), + 0, + ) + + c.Require().Error(err) + c.Assert().Nil(command) +} + +func (c *createProductUnitTests) Test_New_Create_Product_Should_Return_Error_For_Empty_Name() { + command, err := createProductCommand.NewCreateProductWithValidation( + "", + gofakeit.EmojiDescription(), + 120, + ) + + c.Require().Error(err) + c.Assert().Nil(command) +} + +func (c *createProductUnitTests) Test_New_Create_Product_Should_Return_Error_For_Empty_Description() { + command, err := createProductCommand.NewCreateProductWithValidation( + gofakeit.Name(), + "", + 120, + ) + + c.Require().Error(err) + c.Assert().Nil(command) +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/deletingproduct/v1/delete_product_handler_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/deletingproduct/v1/delete_product_handler_unit_test.go new file mode 100644 index 00000000..f2c3009c --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/deletingproduct/v1/delete_product_handler_unit_test.go @@ -0,0 +1,119 @@ +//go:build unit +// +build unit + +package v1 + +import ( + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + deletingproductv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + "emperror.dev/errors" + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +type deleteProductHandlerUnitTests struct { + *unittest.UnitTestSharedFixture + handler cqrs.RequestHandlerWithRegisterer[*deletingproductv1.DeleteProduct, *mediatr.Unit] +} + +func TestDeleteProductHandlerUnit(t *testing.T) { + suite.Run( + t, + &deleteProductHandlerUnitTests{ + UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t), + }, + ) +} + +func (c *deleteProductHandlerUnitTests) SetupTest() { + // call base SetupTest hook before running child hook + c.UnitTestSharedFixture.SetupTest() + c.handler = deletingproductv1.NewDeleteProductHandler( + fxparams.ProductHandlerParams{ + Log: c.Log, + CatalogsDBContext: c.CatalogDBContext, + RabbitmqProducer: c.Bus, + Tracer: c.Tracer, + }, + ) +} + +func (c *deleteProductHandlerUnitTests) TearDownTest() { + // call base TearDownTest hook before running child hook + c.UnitTestSharedFixture.TearDownTest() +} + +func (c *deleteProductHandlerUnitTests) Test_Handle_Should_Delete_Product_With_Valid_Id() { + id := c.Products[0].Id + + deleteProduct := &deletingproductv1.DeleteProduct{ + ProductID: id, + } + + c.BeginTx() + _, err := c.handler.Handle(c.Ctx, deleteProduct) + c.CommitTx() + + c.Require().NoError(err) + + p, err := gormdbcontext.FindDataModelByID[*datamodels.ProductDataModel](c.Ctx, c.CatalogDBContext, id) + + c.Require().Nil(p) + c.Require().Error(err) + + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 1) +} + +func (c *deleteProductHandlerUnitTests) Test_Handle_Should_Return_NotFound_Error_When_Id_Is_Invalid() { + id := uuid.NewV4() + + deleteProduct := &deletingproductv1.DeleteProduct{ + ProductID: id, + } + + c.BeginTx() + res, err := c.handler.Handle(c.Ctx, deleteProduct) + c.CommitTx() + + c.Require().Error(err) + c.True(customErrors.IsNotFoundError(err)) + c.Nil(res) + + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 0) +} + +func (c *deleteProductHandlerUnitTests) Test_Handle_Should_Return_Error_For_Error_In_Bus() { + id := c.Products[0].Id + + deleteProduct := &deletingproductv1.DeleteProduct{ + ProductID: id, + } + + // override called mock + // https://github.com/stretchr/testify/issues/558 + c.Bus.Mock.ExpectedCalls = nil + c.Bus.On("PublishMessage", mock.Anything, mock.Anything, mock.Anything). + Once(). + Return(errors.New("error in the publish message")) + + c.BeginTx() + dto, err := c.handler.Handle(c.Ctx, deleteProduct) + c.CommitTx() + + c.Nil(dto) + + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 1) + c.True(customErrors.IsApplicationError(err, http.StatusInternalServerError)) + c.ErrorContains(err, "error in publishing 'ProductDeleted' message") +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/deletingproduct/v1/delete_product_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/deletingproduct/v1/delete_product_unit_test.go new file mode 100644 index 00000000..4a58b943 --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/deletingproduct/v1/delete_product_unit_test.go @@ -0,0 +1,42 @@ +//go:build unit +// +build unit + +package v1 + +import ( + "testing" + + v1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/deletingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/suite" +) + +type deleteProductUnitTests struct { + *unittest.UnitTestSharedFixture +} + +func TestDeleteProductByIdUnit(t *testing.T) { + suite.Run( + t, + &deleteProductUnitTests{UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t)}, + ) +} + +func (c *deleteProductUnitTests) Test_New_Delete_Product_Should_Return_No_Error_For_Valid_Input() { + id := uuid.NewV4() + + query, err := v1.NewDeleteProduct(id) + + c.Assert().NotNil(query) + c.Assert().Equal(query.ProductID, id) + c.Require().NoError(err) +} + +func (c *deleteProductUnitTests) Test_New_Delete_Product_Should_Return_Error_For_Invalid_Id() { + query, err := v1.NewDeleteProduct(uuid.UUID{}) + + c.Assert().Nil(query) + c.Require().Error(err) +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/gettingproductbyid/v1/get_product_by_Id_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/gettingproductbyid/v1/get_product_by_Id_unit_test.go new file mode 100644 index 00000000..696a5e96 --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/gettingproductbyid/v1/get_product_by_Id_unit_test.go @@ -0,0 +1,42 @@ +//go:build unit +// +build unit + +package v1 + +import ( + "testing" + + getProductByIdQuery "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/suite" +) + +type getProductByIdUnitTests struct { + *unittest.UnitTestSharedFixture +} + +func TestGetProductByIdUnit(t *testing.T) { + suite.Run( + t, + &getProductByIdUnitTests{UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t)}, + ) +} + +func (c *getProductByIdUnitTests) Test_New_Get_Product_By_Id_Should_Return_No_Error_For_Valid_Input() { + id := uuid.NewV4() + + query, err := getProductByIdQuery.NewGetProductById(id) + + c.Assert().NotNil(query) + c.Assert().Equal(query.ProductID, id) + c.Require().NoError(err) +} + +func (c *getProductByIdUnitTests) Test_New_Get_Product_By_Id_Should_Return_Error_For_Invalid_Id() { + query, err := getProductByIdQuery.NewGetProductById(uuid.UUID{}) + + c.Assert().Nil(query) + c.Require().Error(err) +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/gettingproductbyid/v1/get_product_by_id_handler_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/gettingproductbyid/v1/get_product_by_id_handler_unit_test.go new file mode 100644 index 00000000..66b349a2 --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/gettingproductbyid/v1/get_product_by_id_handler_unit_test.go @@ -0,0 +1,97 @@ +//go:build unit +// +build unit + +package v1 + +import ( + "fmt" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + gettingproductbyidv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproductbyid/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/suite" +) + +type getProductByIdHandlerTest struct { + *unittest.UnitTestSharedFixture + handler cqrs.RequestHandlerWithRegisterer[*gettingproductbyidv1.GetProductById, *dtos.GetProductByIdResponseDto] +} + +func TestGetProductByIdHandlerUnit(t *testing.T) { + suite.Run(t, &getProductByIdHandlerTest{ + UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t), + }) +} + +func (c *getProductByIdHandlerTest) SetupTest() { + // call base SetupTest hook before running child hook + c.UnitTestSharedFixture.SetupTest() + c.handler = gettingproductbyidv1.NewGetProductByIDHandler( + fxparams.ProductHandlerParams{ + CatalogsDBContext: c.CatalogDBContext, + Tracer: c.Tracer, + RabbitmqProducer: c.Bus, + Log: c.Log, + }) +} + +func (c *getProductByIdHandlerTest) TearDownTest() { + // call base TearDownTest hook before running child hook + c.UnitTestSharedFixture.TearDownTest() +} + +func (c *getProductByIdHandlerTest) Test_Handle_Should_Return_Correct_Product_By_ID() { + product := c.Products[0] + + query, err := gettingproductbyidv1.NewGetProductById(product.Id) + c.Require().NoError(err) + + dto, err := c.handler.Handle(c.Ctx, query) + c.Require().NoError(err) + c.Assert().NotNil(dto) + c.Assert().NotNil(dto.Product) + c.Assert().Equal(dto.Product.Id, product.Id) + c.Assert().Equal(dto.Product.Name, product.Name) +} + +func (c *getProductByIdHandlerTest) Test_Handle_Should_Return_NotFound_Error_For_NotFound_Item() { + id := uuid.NewV4() + + query, err := gettingproductbyidv1.NewGetProductById(id) + c.Require().NoError(err) + + dto, err := c.handler.Handle(c.Ctx, query) + c.Require().Error(err) + c.True(customErrors.IsNotFoundError(err)) + c.ErrorContains( + err, + fmt.Sprintf( + "product_data_model with id `%s` not found in the database", + id.String(), + ), + ) + c.Nil(dto) +} + +func (c *getProductByIdHandlerTest) Test_Handle_Should_Return_Error_For_Error_In_Mapping() { + mapper.ClearMappings() + + product := c.Products[0] + + query, err := gettingproductbyidv1.NewGetProductById(product.Id) + c.Require().NoError(err) + + dto, err := c.handler.Handle(c.Ctx, query) + + c.Nil(dto) + c.Require().Error(err) + c.True(customErrors.IsInternalServerError(err)) + c.ErrorContains(err, "error in the mapping product") +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/gettingproducts/v1/get_products_handler_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/gettingproducts/v1/get_products_handler_unit_test.go new file mode 100644 index 00000000..6e13afcd --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/gettingproducts/v1/get_products_handler_unit_test.go @@ -0,0 +1,74 @@ +//go:build unit +// +build unit + +package v1 + +import ( + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + gettingproductsv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/gettingproducts/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + "github.com/stretchr/testify/suite" +) + +type getProductsHandlerUnitTests struct { + *unittest.UnitTestSharedFixture + handler cqrs.RequestHandlerWithRegisterer[*gettingproductsv1.GetProducts, *dtos.GetProductsResponseDto] +} + +func TestGetProductsUnit(t *testing.T) { + suite.Run( + t, + &getProductsHandlerUnitTests{ + UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t), + }, + ) +} + +func (c *getProductsHandlerUnitTests) SetupTest() { + // call base SetupTest hook before running child hook + c.UnitTestSharedFixture.SetupTest() + c.handler = gettingproductsv1.NewGetProductsHandler( + fxparams.ProductHandlerParams{ + CatalogsDBContext: c.CatalogDBContext, + Tracer: c.Tracer, + RabbitmqProducer: c.Bus, + Log: c.Log, + }) +} + +func (c *getProductsHandlerUnitTests) TearDownTest() { + // call base TearDownTest hook before running child hook + c.UnitTestSharedFixture.TearDownTest() +} + +func (c *getProductsHandlerUnitTests) Test_Handle_Should_Return_Products_Successfully() { + query, err := gettingproductsv1.NewGetProducts(utils.NewListQuery(10, 1)) + c.Require().NoError(err) + + res, err := c.handler.Handle(c.Ctx, query) + c.Require().NoError(err) + c.NotNil(res) + c.NotEmpty(res.Products) + c.Equal(len(c.Products), len(res.Products.Items)) +} + +func (c *getProductsHandlerUnitTests) Test_Handle_Should_Return_Error_For_Mapping_List_Result() { + query, err := gettingproductsv1.NewGetProducts(utils.NewListQuery(10, 1)) + c.Require().NoError(err) + + mapper.ClearMappings() + + res, err := c.handler.Handle(c.Ctx, query) + c.Require().Error(err) + c.True(customErrors.IsApplicationError(err, http.StatusInternalServerError)) + c.Nil(res) +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/searchingproducts/v1/search_products_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/searchingproducts/v1/search_products_unit_test.go new file mode 100644 index 00000000..338ea3f0 --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/searchingproducts/v1/search_products_unit_test.go @@ -0,0 +1,77 @@ +package v1 + +import ( + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + searchingproductsv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/searchingproduct/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + "github.com/stretchr/testify/suite" +) + +type searchProductsHandlerUnitTests struct { + *unittest.UnitTestSharedFixture + handler cqrs.RequestHandlerWithRegisterer[*searchingproductsv1.SearchProducts, *dtos.SearchProductsResponseDto] +} + +func TestSearchProductsUnit(t *testing.T) { + suite.Run( + t, + &searchProductsHandlerUnitTests{ + UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t), + }, + ) +} + +func (c *searchProductsHandlerUnitTests) SetupTest() { + // call base SetupTest hook before running child hook + c.UnitTestSharedFixture.SetupTest() + c.handler = searchingproductsv1.NewSearchProductsHandler( + fxparams.ProductHandlerParams{ + CatalogsDBContext: c.CatalogDBContext, + Tracer: c.Tracer, + RabbitmqProducer: c.Bus, + Log: c.Log, + }) +} + +func (c *searchProductsHandlerUnitTests) TearDownTest() { + // call base TearDownTest hook before running child hook + c.UnitTestSharedFixture.TearDownTest() +} + +func (c *searchProductsHandlerUnitTests) Test_Handle_Should_Return_Products_Successfully() { + query, err := searchingproductsv1.NewSearchProducts( + c.Products[0].Name, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) + + res, err := c.handler.Handle(c.Ctx, query) + c.Require().NoError(err) + c.NotNil(res) + c.NotEmpty(res.Products) + c.Equal(len(res.Products.Items), 1) +} + +func (c *searchProductsHandlerUnitTests) Test_Handle_Should_Return_Error_For_Mapping_List_Result() { + query, err := searchingproductsv1.NewSearchProducts( + c.Products[0].Name, + utils.NewListQuery(10, 1), + ) + c.Require().NoError(err) + + mapper.ClearMappings() + + res, err := c.handler.Handle(c.Ctx, query) + c.Require().Error(err) + c.True(customErrors.IsApplicationError(err, http.StatusInternalServerError)) + c.Nil(res) +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/updatingproduct/v1/update_product_handler_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/updatingproduct/v1/update_product_handler_unit_test.go new file mode 100644 index 00000000..5540f69a --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/updatingproduct/v1/update_product_handler_unit_test.go @@ -0,0 +1,136 @@ +//go:build unit +// +build unit + +package v1 + +import ( + "fmt" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/cqrs" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/postgresgorm/gormdbcontext" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/data/datamodels" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1/fxparams" + updatingoroductsv1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + "emperror.dev/errors" + "github.com/brianvoe/gofakeit/v6" + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +type updateProductHandlerUnitTests struct { + *unittest.UnitTestSharedFixture + handler cqrs.RequestHandlerWithRegisterer[*updatingoroductsv1.UpdateProduct, *mediatr.Unit] +} + +func TestUpdateProductHandlerUnit(t *testing.T) { + suite.Run( + t, + &updateProductHandlerUnitTests{ + UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t), + }, + ) +} + +func (c *updateProductHandlerUnitTests) SetupTest() { + // call base `SetupTest hook` before running child hook + c.UnitTestSharedFixture.SetupTest() + c.handler = updatingoroductsv1.NewUpdateProductHandler( + fxparams.ProductHandlerParams{ + CatalogsDBContext: c.CatalogDBContext, + Tracer: c.Tracer, + RabbitmqProducer: c.Bus, + Log: c.Log, + }, + ) +} + +func (c *updateProductHandlerUnitTests) TearDownTest() { + // call base `TearDownTest hook` before running child hook + c.UnitTestSharedFixture.TearDownTest() +} + +func (c *updateProductHandlerUnitTests) Test_Handle_Should_Update_Product_With_Valid_Data() { + existing := c.Products[0] + + updateProductCommand, err := updatingoroductsv1.NewUpdateProduct( + existing.Id, + gofakeit.Name(), + gofakeit.EmojiDescription(), + existing.Price, + ) + c.Require().NoError(err) + + c.BeginTx() + _, err = c.handler.Handle(c.Ctx, updateProductCommand) + c.CommitTx() + + c.Require().NoError(err) + + updatedProduct, err := gormdbcontext.FindDataModelByID[*datamodels.ProductDataModel]( + c.Ctx, + c.CatalogDBContext, + updateProductCommand.ProductID, + ) + c.Require().NoError(err) + + c.Assert().Equal(updatedProduct.Id, updateProductCommand.ProductID) + c.Assert().Equal(updatedProduct.Name, updateProductCommand.Name) + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 1) +} + +func (c *updateProductHandlerUnitTests) Test_Handle_Should_Return_Error_For_NotFound_Item() { + id := uuid.NewV4() + + command, err := updatingoroductsv1.NewUpdateProduct( + id, + gofakeit.Name(), + gofakeit.EmojiDescription(), + gofakeit.Price(150, 6000), + ) + c.Require().NoError(err) + + c.BeginTx() + _, err = c.handler.Handle(c.Ctx, command) + c.CommitTx() + + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 0) + c.True(customErrors.IsApplicationError(err, http.StatusNotFound)) + c.ErrorContains( + err, + fmt.Sprintf("product with id `%s` not found", id.String()), + ) +} + +func (c *updateProductHandlerUnitTests) Test_Handle_Should_Return_Error_For_Error_In_Bus() { + existing := c.Products[0] + + updateProductCommand, err := updatingoroductsv1.NewUpdateProduct( + existing.Id, + gofakeit.Name(), + gofakeit.EmojiDescription(), + existing.Price, + ) + c.Require().NoError(err) + + // override called mock + // https://github.com/stretchr/testify/issues/558 + c.Bus.Mock.ExpectedCalls = nil + c.Bus.On("PublishMessage", mock.Anything, mock.Anything, mock.Anything). + Once(). + Return(errors.New("error in the publish message")) + + c.BeginTx() + _, err = c.handler.Handle(c.Ctx, updateProductCommand) + c.CommitTx() + + c.Bus.AssertNumberOfCalls(c.T(), "PublishMessage", 1) + c.ErrorContains(err, "error in the publish message") + c.ErrorContains(err, "error in publishing 'ProductUpdated' message") +} diff --git a/internal/services/catalogwriteservice/test/unit/products/features/updatingproduct/v1/update_product_unit_test.go b/internal/services/catalogwriteservice/test/unit/products/features/updatingproduct/v1/update_product_unit_test.go new file mode 100644 index 00000000..52ad1a0c --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/features/updatingproduct/v1/update_product_unit_test.go @@ -0,0 +1,68 @@ +//go:build unit +// +build unit + +package v1 + +import ( + "testing" + + v1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/features/updatingproduct/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/suite" +) + +type updateProductUnitTests struct { + *unittest.UnitTestSharedFixture +} + +func TestUpdateProductUnit(t *testing.T) { + suite.Run( + t, + &updateProductUnitTests{UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t)}, + ) +} + +func (c *updateProductUnitTests) Test_New_Update_Product_Should_Return_No_Error_For_Valid_Input() { + id := uuid.NewV4() + name := gofakeit.Name() + description := gofakeit.EmojiDescription() + price := gofakeit.Price(150, 6000) + + updateProduct, err := v1.NewUpdateProduct(id, name, description, price) + + c.Assert().NotNil(updateProduct) + c.Assert().Equal(id, updateProduct.ProductID) + c.Assert().Equal(name, updateProduct.Name) + c.Assert().Equal(price, updateProduct.Price) + + c.Require().NoError(err) +} + +func (c *updateProductUnitTests) Test_New_Update_Product_Should_Return_Error_For_Invalid_Price() { + command, err := v1.NewUpdateProduct( + uuid.NewV4(), + gofakeit.Name(), + gofakeit.EmojiDescription(), + 0, + ) + + c.Require().Error(err) + c.Assert().Nil(command) +} + +func (c *updateProductUnitTests) Test_New_Update_Product_Should_Return_Error_For_Empty_Name() { + command, err := v1.NewUpdateProduct(uuid.NewV4(), "", gofakeit.EmojiDescription(), 120) + + c.Require().Error(err) + c.Assert().Nil(command) +} + +func (c *updateProductUnitTests) Test_New_Update_Product_Should_Return_Error_For_Empty_Description() { + command, err := v1.NewUpdateProduct(uuid.NewV4(), gofakeit.Name(), "", 120) + + c.Require().Error(err) + c.Assert().Nil(command) +} diff --git a/internal/services/catalogwriteservice/test/unit/products/mappings_profile_test.go b/internal/services/catalogwriteservice/test/unit/products/mappings_profile_test.go new file mode 100644 index 00000000..235d3c34 --- /dev/null +++ b/internal/services/catalogwriteservice/test/unit/products/mappings_profile_test.go @@ -0,0 +1,73 @@ +//go:build unit +// +build unit + +package products + +import ( + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + dtoV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/products/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/catalogwriteservice/internal/shared/testfixtures/unittest" + + "github.com/brianvoe/gofakeit/v6" + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/suite" +) + +type mappingProfileUnitTests struct { + *unittest.UnitTestSharedFixture +} + +func TestMappingProfileUnit(t *testing.T) { + suite.Run( + t, + &mappingProfileUnitTests{UnitTestSharedFixture: unittest.NewUnitTestSharedFixture(t)}, + ) +} + +func (m *mappingProfileUnitTests) Test_Mappings() { + productModel := &models.Product{ + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.EmojiDescription(), + Price: gofakeit.Price(100, 1000), + } + + productDto := &dtoV1.ProductDto{ + Id: uuid.NewV4(), + Name: gofakeit.Name(), + CreatedAt: time.Now(), + Description: gofakeit.EmojiDescription(), + Price: gofakeit.Price(100, 1000), + } + + m.Run("Should_Map_Product_To_ProductDto", func() { + d, err := mapper.Map[*dtoV1.ProductDto](productModel) + m.Require().NoError(err) + m.Equal(productModel.Id, d.Id) + m.Equal(productModel.Name, d.Name) + }) + + m.Run("Should_Map_Nil_Product_To_ProductDto", func() { + d, err := mapper.Map[*dtoV1.ProductDto](*new(models.Product)) + m.Require().NoError(err) + m.Nil(d) + }) + + m.Run("Should_Map_ProductDto_To_Product", func() { + d, err := mapper.Map[*models.Product](productDto) + m.Require().NoError(err) + m.Equal(productDto.Id, d.Id) + m.Equal(productDto.Name, d.Name) + }) + + m.Run("Should_Map_Nil_ProductDto_To_Product", func() { + d, err := mapper.Map[*models.Product](*new(dtoV1.ProductDto)) + m.Require().NoError(err) + m.Nil(d) + }) +} diff --git a/internal/services/orderservice/.air.toml b/internal/services/orderservice/.air.toml new file mode 100644 index 00000000..a4e5f123 --- /dev/null +++ b/internal/services/orderservice/.air.toml @@ -0,0 +1,37 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "tmp\\app\\main.exe" + cmd = "go build -o ./tmp/app/main.exe ./cmd/app/main.go" + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + kill_delay = "0s" + log = "build-errors.log" + send_interrupt = false + stop_on_error = true + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false diff --git a/internal/services/orderservice/.dockerignore b/internal/services/orderservice/.dockerignore new file mode 100644 index 00000000..d2b3b429 --- /dev/null +++ b/internal/services/orderservice/.dockerignore @@ -0,0 +1,2 @@ +.gitignore +Dockerfile diff --git a/internal/services/orderservice/.golangci.yml b/internal/services/orderservice/.golangci.yml new file mode 100644 index 00000000..e311c166 --- /dev/null +++ b/internal/services/orderservice/.golangci.yml @@ -0,0 +1,115 @@ +linters-settings: + dupl: + threshold: 120 + errorlint: + errorf: true + errcheck: + check-type-assertions: true + check-blank: true + exhaustive: + check-generated: false + default-signifies-exhaustive: false + funlen: + lines: 120 + statements: 50 + gocognit: + min-complexity: 40 + gocyclo: + min-complexity: 15 + goconst: + min-len: 2 + min-occurrences: 2 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - dupImport + - unnecessaryBlock + gofumpt: + extra-rules: true + gomnd: + settings: + mnd: + checks: + - argument + - case + - condition + - operation + - return + govet: + check-shadowing: true + misspell: + locale: US + nestif: + min-complexity: 4 + nolintlint: + require-explanation: true + require-specific: true + +linters: + disable-all: true + enable: + - asciicheck + - bodyclose + - cyclop + - dogsled + - dupl + - durationcheck + - errcheck + - errorlint + - exhaustive + - exportloopref + - forbidigo + - funlen + - gci + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - godox + - goerr113 + - gofmt + - gofumpt + - goimports + - gomnd + - gomodguard + - gosec + - gosimple + - govet + - ineffassign + - makezero + - misspell + - nakedret + - nestif + - nlreturn + - noctx + - nolintlint + - paralleltest + - predeclared + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - tparallel + - thelper + - unconvert + - unparam + - unused + - wsl + - whitespace + +run: + skip-dirs: + - docs + +issues: + # Show only new issues created after git revision: e016c66 + new-from-rev: e016c66 diff --git a/internal/services/orderservice/Dockerfile b/internal/services/orderservice/Dockerfile new file mode 100644 index 00000000..69e02e40 --- /dev/null +++ b/internal/services/orderservice/Dockerfile @@ -0,0 +1,38 @@ +FROM golang:1.19.0-alpine AS builder + +# Set Go env +ENV CGO_ENABLED=0 GOOS=linux +WORKDIR /services/catalogs/read_service + +# Install dependencies +RUN apk --update --no-cache add ca-certificates make protoc + +# Download grpc_health_probe +RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ + wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ + chmod +x /bin/grpc_health_probe + +# Build Go binary +COPY Makefile go.mod go.sum ./ +RUN go env -w GOPROXY=https://goproxy.io,direct/ +RUN make init && go mod download +COPY . . +RUN make proto tidy + +# Skaffold passes in debug-oriented compiler flags +ARG SKAFFOLD_GO_GCFLAGS +RUN go build.sh -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /services/catalogs_write/catalogs_read . + +# Deployment container +FROM scratch + +WORKDIR /read_service + +# Definition of this variable is used by 'skaffold debug' to identify a golang binary. +# Default behavior - a failure prints a stack trace for the current goroutine. +# See https://golang.org/pkg/runtime/ +ENV GOTRACEBACK=single + +COPY --from=builder /services/catalogs/read_service /services/catalogs-read + +ENTRYPOINT ["/services/catalogs-read"] diff --git a/internal/services/orderservice/Makefile b/internal/services/orderservice/Makefile new file mode 100644 index 00000000..02dad785 --- /dev/null +++ b/internal/services/orderservice/Makefile @@ -0,0 +1,65 @@ +GOPATH:=$(shell go env GOPATH) + +.PHONY: init +init: + @go get -u google.golang.org/protobuf/proto + @go install github.com/golang/protobuf/protoc-gen-go@latest + +.PHONY: run_orders_service +run_orders_service: + go run ./cmd/main.go + +.PHONY: build_orders_service +build_orders_service: + go build ./cmd/main.go + +.PHONY: test_orders_service +test_orders_service: + go test -v ./... -cover + +.PHONY: lint +lint: + revive -config revive-config.toml -formatter friendly ./... + staticcheck ./... + golangci-lint run ./... + +.PHONY: format +format: + golines -m 120 -w --ignore-generated . + gci write --skip-generated -s standard -s "prefix(github.com/mehdihadeli/go-food-delivery-microservices)" -s default -s blank -s dot --custom-order . + gofumpt -l -w . + +.PHONY: update +update: + @go get -u + +.PHONY: tidy +tidy: + go mod tidy + +.PHONY: deps-reset +deps-reset: + git checkout -- go.mod + go mod tidy + +.PHONY: deps-upgrade +deps-upgrade: + go get -u -t -d -v ./... + go mod tidy + +.PHONY: deps-cleancache +deps-cleancache: + go clean -modcache + +# ============================================================================== +# Linters https://golangci-lint.run/usage/install/ + +.PHONY: run-linter +run-linter: + @echo Starting linters + golangci-lint run ./... + +.PHONY: docker +docker: + @docker build -t go-orders:latest . + diff --git a/internal/services/orderservice/arch-go.yml b/internal/services/orderservice/arch-go.yml new file mode 100644 index 00000000..ca3e481b --- /dev/null +++ b/internal/services/orderservice/arch-go.yml @@ -0,0 +1,4 @@ +#https://github.com/fdaines/arch-go +cyclesRules: + - package: "**.cmd" + shouldNotContainCycles: true \ No newline at end of file diff --git a/internal/services/orderservice/client.http b/internal/services/orderservice/client.http new file mode 100644 index 00000000..645f6fef --- /dev/null +++ b/internal/services/orderservice/client.http @@ -0,0 +1,13 @@ +version: v1 +plugins: + - remote: buf.build/protocolbuffers/plugins/go:v1.28.0-1 + out: proto/gen/ + opt: paths=source_relative + - remote: buf.build/grpc/plugins/go:v1.2.0-1 + out: proto/gen/ + opt: paths=source_relative,require_unimplemented_servers=false + - remote: buf.build/grpc-ecosystem/plugins/grpc-gateway:v2.7.2-1 + out: proto/gen/ + opt: paths=source_relative + - remote: buf.build/grpc-ecosystem/plugins/openapiv2:v2.7.2-1 + out: third_party/openapi \ No newline at end of file diff --git a/internal/services/orderservice/cmd/app/main.go b/internal/services/orderservice/cmd/app/main.go new file mode 100644 index 00000000..6c32f3d3 --- /dev/null +++ b/internal/services/orderservice/cmd/app/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "os" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/app" + + "github.com/pterm/pterm" + "github.com/pterm/pterm/putils" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "orders-microservice", + Short: "orders-microservice based on vertical slice architecture", + Long: `This is a command runner or cli for api architecture in golang.`, + TraverseChildren: true, + Run: func(cmd *cobra.Command, args []string) { + app.NewApp().Run() + }, +} + +// https://github.com/swaggo/swag#how-to-use-it-with-gin + +// @contact.name Mehdi Hadeli +// @contact.url https://github.com/mehdihadeli +// @title Orders Service Api +// @version 1.0 +// @description Orders Service Api +func main() { + pterm.DefaultBigText.WithLetters( + putils.LettersFromStringWithStyle("Orders", pterm.FgLightGreen.ToStyle()), + putils.LettersFromStringWithStyle(" Service", pterm.FgLightMagenta.ToStyle())). + Render() + + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/internal/services/orderservice/cmd/migration/.gitkeep b/internal/services/orderservice/cmd/migration/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/internal/services/orderservice/config/config.development.json b/internal/services/orderservice/config/config.development.json new file mode 100644 index 00000000..6e8c1275 --- /dev/null +++ b/internal/services/orderservice/config/config.development.json @@ -0,0 +1,98 @@ +{ + "appOptions": { + "serviceName": "orderservice", + "deliveryType": "http" + }, + "grpcOptions": { + "name": "orderservice", + "port": ":6005", + "host": "localhost", + "development": true + }, + "echoHttpOptions": { + "name": "orderservice", + "port": ":8000", + "development": true, + "timeout": 30, + "basePath": "/api/v1", + "host": "http://localhost", + "ordersPath": "orders", + "debugHeaders": true, + "httpClientDebug": true, + "debugErrorsResponse": true, + "ignoreLogUrls": [ + "metrics" + ] + }, + "logOptions": { + "level": "debug", + "logType": 0, + "callerEnabled": false + }, + "mongoDbOptions": { + "host": "localhost", + "port": 27017, + "user": "admin", + "password": "admin", + "database": "orders_service", + "useAuth": true + }, + "rabbitmqOptions": { + "autoStart": true, + "reconnecting": true, + "rabbitmqHostOptions": { + "userName": "guest", + "password": "guest", + "hostName": "localhost", + "port": 5672, + "httpPort": 15672 + } + }, + "tracingOptions": { + "enable": true, + "serviceName": "orders-service", + "instrumentationName": "io.opentelemetry.traces.orders-service", + "id": 1, + "useStdout": false, + "alwaysOnSampler": true, + "jaegerExporterOptions": { + "otlpEndpoint": "localhost:4320", + "enabled": true + }, + "zipkinExporterOptions": { + "url": "http://localhost:9411/api/v2/spans" + }, + "otlpProviders": [ + { + "name": "uptrace", + "enabled": false, + "otlpEndpoint": "otlp.uptrace.dev:4317", + "otlpHeaders": { + "uptrace-dsn": "https://@uptrace.dev/" + } + }, + { + "name": "elastic-apm", + "enabled": false, + "otlpEndpoint": "host.docker.internal:4317", + "otlpHeaders": { + "Authorization": "Bearer ${ELASTIC_APM_SECRET_TOKEN}" + } + } + ] + }, + "metricsOptions": { + "metricsRoutePath": "/metrics", + "serviceName": "orders-service", + "instrumentationName": "io.opentelemetry.metrics.orders-service" + }, + "eventStoreDbOptions": { + "host": "localhost", + "httpPort": 2113, + "tcpPort": 1113 , + "subscription": { + "subscriptionId": "orders-subscription", + "prefix": ["order-"] + } + } +} diff --git a/internal/services/orderservice/config/config.go b/internal/services/orderservice/config/config.go new file mode 100644 index 00000000..8e3e901e --- /dev/null +++ b/internal/services/orderservice/config/config.go @@ -0,0 +1,34 @@ +package config + +import ( + "strings" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" +) + +type Config struct { + AppOptions AppOptions `mapstructure:"appOptions"` +} + +func NewConfig(environment environment.Environment) (*Config, error) { + cfg, err := config.BindConfig[*Config](environment) + if err != nil { + return nil, err + } + + return cfg, nil +} + +type AppOptions struct { + DeliveryType string `mapstructure:"deliveryType"` + ServiceName string `mapstructure:"serviceName"` +} + +func (cfg *AppOptions) GetMicroserviceNameUpper() string { + return strings.ToUpper(cfg.ServiceName) +} + +func (cfg *AppOptions) GetMicroserviceName() string { + return cfg.ServiceName +} diff --git a/internal/services/orderservice/config/config.test.json b/internal/services/orderservice/config/config.test.json new file mode 100644 index 00000000..a416ed3a --- /dev/null +++ b/internal/services/orderservice/config/config.test.json @@ -0,0 +1,96 @@ +{ + "appOptions": { + "serviceName": "orderservice", + "deliveryType": "http" + }, + "grpcOptions": { + "name": "orderservice", + "port": ":3302", + "host": "localhost", + "development": true + }, + "echoHttpOptions": { + "name": "orderservice", + "port": ":6002", + "development": true, + "timeout": 30, + "basePath": "/api/v1", + "host": "http://localhost", + "ordersPath": "orders", + "debugHeaders": true, + "httpClientDebug": true, + "debugErrorsResponse": true, + "ignoreLogUrls": ["metrics"] + }, + "logOptions": { + "level": "debug", + "logType": 0, + "callerEnabled": false + }, + "mongoDbOptions": { + "host": "localhost", + "port": 27017, + "user": "admin", + "password": "admin", + "database": "orders_service", + "useAuth": true + }, + "rabbitmqOptions": { + "autoStart": false, + "reconnecting": false, + "rabbitmqHostOptions": { + "userName": "guest", + "password": "guest", + "hostName": "localhost", + "port": 5672, + "httpPort": 15672 + } + }, + "tracingOptions": { + "enable": true, + "serviceName": "orders-service", + "instrumentationName": "io.opentelemetry.traces.orders-service", + "id": 1, + "useStdout": false, + "alwaysOnSampler": true, + "jaegerExporterOptions": { + "otlpEndpoint": "localhost:4320", + "enabled": true + }, + "zipkinExporterOptions": { + "url": "http://localhost:9411/api/v2/spans" + }, + "otlpProviders": [ + { + "name": "uptrace", + "enabled": false, + "otlpEndpoint": "otlp.uptrace.dev:4317", + "otlpHeaders": { + "uptrace-dsn": "https://@uptrace.dev/" + } + }, + { + "name": "elastic-apm", + "enabled": false, + "otlpEndpoint": "host.docker.internal:4317", + "otlpHeaders": { + "Authorization": "Bearer ${ELASTIC_APM_SECRET_TOKEN}" + } + } + ] + }, + "metricsOptions": { + "metricsRoutePath": "/metrics", + "serviceName": "orders-service", + "instrumentationName": "io.opentelemetry.metrics.orders-service" + }, + "eventStoreDbOptions": { + "host": "localhost", + "httpPort": 2113, + "tcpPort": 1113, + "subscription": { + "subscriptionId": "orders-subscription", + "prefix": ["order-"] + } + } +} diff --git a/internal/services/orderservice/config/config_fx.go b/internal/services/orderservice/config/config_fx.go new file mode 100644 index 00000000..3f1f6c6c --- /dev/null +++ b/internal/services/orderservice/config/config_fx.go @@ -0,0 +1,14 @@ +package config + +import "go.uber.org/fx" + +// Module provided to fxlog +// https://uber-go.github.io/fx/modules.html +var Module = fx.Module("appconfigfx", + // - order is not important in provide + // - provide can have parameter and will resolve if registered + // - execute its func only if it requested + fx.Provide( + NewConfig, + ), +) diff --git a/internal/services/orderservice/docs/docs.go b/internal/services/orderservice/docs/docs.go new file mode 100644 index 00000000..4e726fb4 --- /dev/null +++ b/internal/services/orderservice/docs/docs.go @@ -0,0 +1,315 @@ +// Code generated by swaggo/swag. DO NOT EDIT. + +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/orders": { + "get": { + "description": "Get all orders", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Get all orders", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto" + } + } + } + }, + "post": { + "description": "Create new order", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Create order", + "parameters": [ + { + "description": "Order data", + "name": "CreateOrderRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto" + } + } + } + } + }, + "/api/v1/orders/{id}": { + "get": { + "description": "Get order by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Get order by id", + "parameters": [ + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto" + } + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto": { + "type": "object", + "properties": { + "accountEmail": { + "type": "string" + }, + "cancelReason": { + "type": "string" + }, + "canceled": { + "type": "boolean" + }, + "completed": { + "type": "boolean" + }, + "createdAt": { + "type": "string" + }, + "deliveredTime": { + "type": "string" + }, + "deliveryAddress": { + "type": "string" + }, + "id": { + "type": "string" + }, + "orderId": { + "type": "string" + }, + "paid": { + "type": "boolean" + }, + "paymentId": { + "type": "string" + }, + "shopItems": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto" + } + }, + "submitted": { + "type": "boolean" + }, + "totalPrice": { + "type": "number" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto": { + "type": "object", + "properties": { + "accountEmail": { + "type": "string" + }, + "deliveryAddress": { + "type": "string" + }, + "deliveryTime": { + "type": "string" + }, + "shopItems": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto" + } + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto": { + "type": "object", + "properties": { + "Id": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto": { + "type": "object", + "properties": { + "order": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto": { + "type": "object", + "properties": { + "orders": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Orders Service Api", + Description: "Orders Service Api", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/internal/services/orderservice/docs/swagger.json b/internal/services/orderservice/docs/swagger.json new file mode 100644 index 00000000..c2cab1dd --- /dev/null +++ b/internal/services/orderservice/docs/swagger.json @@ -0,0 +1,288 @@ +{ + "swagger": "2.0", + "info": { + "description": "Orders Service Api", + "title": "Orders Service Api", + "contact": { + "name": "Mehdi Hadeli", + "url": "https://github.com/mehdihadeli" + }, + "version": "1.0" + }, + "paths": { + "/api/v1/orders": { + "get": { + "description": "Get all orders", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Get all orders", + "parameters": [ + { + "type": "string", + "name": "orderBy", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto" + } + } + } + }, + "post": { + "description": "Create new order", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Create order", + "parameters": [ + { + "description": "Order data", + "name": "CreateOrderRequestDto", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto" + } + } + } + } + }, + "/api/v1/orders/{id}": { + "get": { + "description": "Get order by id", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Orders" + ], + "summary": "Get order by id", + "parameters": [ + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto" + } + } + } + } + } + }, + "definitions": { + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto": { + "type": "object", + "properties": { + "accountEmail": { + "type": "string" + }, + "cancelReason": { + "type": "string" + }, + "canceled": { + "type": "boolean" + }, + "completed": { + "type": "boolean" + }, + "createdAt": { + "type": "string" + }, + "deliveredTime": { + "type": "string" + }, + "deliveryAddress": { + "type": "string" + }, + "id": { + "type": "string" + }, + "orderId": { + "type": "string" + }, + "paid": { + "type": "boolean" + }, + "paymentId": { + "type": "string" + }, + "shopItems": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto" + } + }, + "submitted": { + "type": "boolean" + }, + "totalPrice": { + "type": "number" + }, + "updatedAt": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto": { + "type": "object", + "properties": { + "accountEmail": { + "type": "string" + }, + "deliveryAddress": { + "type": "string" + }, + "deliveryTime": { + "type": "string" + }, + "shopItems": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto" + } + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto": { + "type": "object", + "properties": { + "Id": { + "type": "string" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto": { + "type": "object", + "properties": { + "order": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto" + } + } + }, + "github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto": { + "type": "object", + "properties": { + "orders": { + "$ref": "#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto" + } + } + }, + "utils.FilterModel": { + "type": "object", + "properties": { + "comparison": { + "type": "string" + }, + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto" + } + }, + "page": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "totalItems": { + "type": "integer" + }, + "totalPage": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/internal/services/orderservice/docs/swagger.yaml b/internal/services/orderservice/docs/swagger.yaml new file mode 100644 index 00000000..56bf1aa7 --- /dev/null +++ b/internal/services/orderservice/docs/swagger.yaml @@ -0,0 +1,186 @@ +definitions: + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto: + properties: + accountEmail: + type: string + cancelReason: + type: string + canceled: + type: boolean + completed: + type: boolean + createdAt: + type: string + deliveredTime: + type: string + deliveryAddress: + type: string + id: + type: string + orderId: + type: string + paid: + type: boolean + paymentId: + type: string + shopItems: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto' + type: array + submitted: + type: boolean + totalPrice: + type: number + updatedAt: + type: string + type: object + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto: + properties: + description: + type: string + price: + type: number + quantity: + type: integer + title: + type: string + type: object + github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemReadDto: + properties: + description: + type: string + price: + type: number + quantity: + type: integer + title: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto + : properties: + accountEmail: + type: string + deliveryAddress: + type: string + deliveryTime: + type: string + shopItems: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.ShopItemDto' + type: array + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto + : properties: + Id: + type: string + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto + : properties: + order: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto' + type: object + ? github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto + : properties: + orders: + $ref: '#/definitions/utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto' + type: object + utils.FilterModel: + properties: + comparison: + type: string + field: + type: string + value: + type: string + type: object + ? utils.ListResult-github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1_OrderReadDto + : properties: + items: + items: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_dtos_v1.OrderReadDto' + type: array + page: + type: integer + size: + type: integer + totalItems: + type: integer + totalPage: + type: integer + type: object +info: + contact: + name: Mehdi Hadeli + url: https://github.com/mehdihadeli + description: Orders Service Api + title: Orders Service Api + version: "1.0" +paths: + /api/v1/orders: + get: + consumes: + - application/json + description: Get all orders + parameters: + - in: query + name: orderBy + type: string + - in: query + name: page + type: integer + - in: query + name: size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_orders_v1_dtos.GetOrdersResponseDto' + summary: Get all orders + tags: + - Orders + post: + consumes: + - application/json + description: Create new order + parameters: + - description: Order data + in: body + name: CreateOrderRequestDto + required: true + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderRequestDto' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_creating_order_v1_dtos.CreateOrderResponseDto' + summary: Create order + tags: + - Orders + /api/v1/orders/{id}: + get: + consumes: + - application/json + description: Get order by id + parameters: + - description: Order ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_mehdihadeli_go-food-delivery-microservices_internal_services_orderservice_internal_orders_features_getting_order_by_id_v1_dtos.GetOrderByIdResponseDto' + summary: Get order by id + tags: + - Orders +swagger: "2.0" diff --git a/internal/services/orderservice/go.mod b/internal/services/orderservice/go.mod new file mode 100644 index 00000000..8387e3dd --- /dev/null +++ b/internal/services/orderservice/go.mod @@ -0,0 +1,246 @@ +module github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice + +go 1.22 + +// https://go.dev/doc/tutorial/call-module-code +replace github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg => ../../pkg/ + +require ( + emperror.dev/errors v0.8.1 + github.com/EventStore/EventStore-Client-Go v1.0.2 + github.com/brianvoe/gofakeit/v6 v6.25.0 + github.com/elastic/go-elasticsearch/v8 v8.10.0 + github.com/gavv/httpexpect/v2 v2.15.0 + github.com/go-ozzo/ozzo-validation v3.6.0+incompatible + github.com/go-playground/validator v9.31.0+incompatible + github.com/goccy/go-json v0.10.2 + github.com/labstack/echo/v4 v4.11.1 + github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg v0.0.0-20230831075934-be8df319f588 + github.com/mehdihadeli/go-mediatr v1.3.0 + github.com/michaelklishin/rabbit-hole v1.5.0 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.28.0 + github.com/pterm/pterm v0.12.69 + github.com/satori/go.uuid v1.2.0 + github.com/spf13/cobra v1.7.0 + github.com/stretchr/testify v1.8.4 + github.com/swaggo/echo-swagger v1.4.1 + github.com/swaggo/swag v1.16.2 + go.mongodb.org/mongo-driver v1.12.1 + go.opentelemetry.io/otel v1.19.0 + go.opentelemetry.io/otel/metric v1.19.0 + go.opentelemetry.io/otel/trace v1.19.0 + go.uber.org/fx v1.20.0 + google.golang.org/grpc v1.58.2 + google.golang.org/protobuf v1.31.0 +) + +require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.1.0 // indirect + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/hcsshim v0.11.1 // indirect + github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect + github.com/ahmetb/go-linq/v3 v3.2.0 // indirect + github.com/ajg/form v1.5.1 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/avast/retry-go v3.0.0+incompatible // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/caarlos0/env/v8 v8.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/containerd/containerd v1.7.6 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v24.0.6+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/doug-martin/goqu/v9 v9.18.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/elastic/elastic-transport-go/v8 v8.3.0 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/glebarez/sqlite v1.10.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-reflect v1.2.0 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang-migrate/migrate/v4 v4.16.2 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/gookit/color v1.5.4 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/imkira/go-interpol v1.1.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.1 // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jackc/puddle v1.3.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/kamva/mgm/v3 v3.5.0 // indirect + github.com/klauspost/compress v1.17.0 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect + github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mcuadros/go-defaults v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/nolleh/caption_json_formatter v0.2.2 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/opencontainers/runc v1.1.9 // indirect + github.com/openzipkin/zipkin-go v0.4.2 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rabbitmq/amqp091-go v1.8.1 // indirect + github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect + github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect + github.com/redis/go-redis/v9 v9.2.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/sanity-io/litter v1.5.5 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/shirou/gopsutil/v3 v3.23.9 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.16.0 // indirect + github.com/stretchr/objx v0.5.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/swaggo/files/v2 v2.0.0 // indirect + github.com/testcontainers/testcontainers-go v0.25.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/ulule/limiter/v3 v3.11.2 // indirect + github.com/uptrace/bun v1.1.16 // indirect + github.com/uptrace/bun/driver/pgdriver v1.1.16 // indirect + github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3 // indirect + github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3 // indirect + github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.47.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.0 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.45.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.19.0 // indirect + go.opentelemetry.io/otel/sdk v1.19.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.19.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.2 // indirect + gorm.io/gorm v1.25.5 // indirect + gorm.io/plugin/opentelemetry v0.1.4 // indirect + mellium.im/sasl v0.3.1 // indirect + modernc.org/libc v1.24.1 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.6.0 // indirect + modernc.org/sqlite v1.25.0 // indirect + moul.io/http2curl/v2 v2.3.0 // indirect +) diff --git a/internal/services/orderservice/go.sum b/internal/services/orderservice/go.sum new file mode 100644 index 00000000..909258ac --- /dev/null +++ b/internal/services/orderservice/go.sum @@ -0,0 +1,1325 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= +cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0= +emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/EventStore/EventStore-Client-Go v1.0.2 h1:onM2TIInLhWUJwUQ/5a/8blNrrbhwrtm7Tpmg13ohiw= +github.com/EventStore/EventStore-Client-Go v1.0.2/go.mod h1:NOqSOtNxqGizr1Qnf7joGGLK6OkeoLV/QEI893A43H0= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.1 h1:hJ3s7GbWlGK4YVV92sO88BQSyF4ZLVy7/awqOlPxFbA= +github.com/Microsoft/hcsshim v0.11.1/go.mod h1:nFJmaO4Zr5Y7eADdFOpYswDDlNVbvcIJJNJLECr5JQg= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= +github.com/ahmetb/go-linq/v3 v3.2.0 h1:BEuMfp+b59io8g5wYzNoFe9pWPalRklhlhbiU3hYZDE= +github.com/ahmetb/go-linq/v3 v3.2.0/go.mod h1:haQ3JfOeWK8HpVxMtHHEMPVgBKiYyQ+f1/kLZh/cj9U= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +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/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/brianvoe/gofakeit/v6 v6.25.0 h1:ZpFjktOpLZUeF8q223o0rUuXtA+m5qW5srjvVi+JkXk= +github.com/brianvoe/gofakeit/v6 v6.25.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= +github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.7.6 h1:oNAVsnhPoy4BTPQivLgTzI9Oleml9l/+eYIDYXRCYo8= +github.com/containerd/containerd v1.7.6/go.mod h1:SY6lrkkuJT40BVNO37tlYTSnKJnP5AXBc0fhx0q+TJ4= +github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= +github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= +github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY= +github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/elastic/elastic-transport-go/v8 v8.0.0-20230329154755-1a3c63de0db6/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI= +github.com/elastic/elastic-transport-go/v8 v8.3.0 h1:DJGxovyQLXGr62e9nDMPSxRyWION0Bh6d9eCFBriiHo= +github.com/elastic/elastic-transport-go/v8 v8.3.0/go.mod h1:87Tcz8IVNe6rVSLdBux1o/PEItLtyabHU3naC7IoqKI= +github.com/elastic/go-elasticsearch/v8 v8.10.0 h1:ALg3DMxSrx07YmeMNcfPf7cFh1Ep2+Qa19EOXTbwr2k= +github.com/elastic/go-elasticsearch/v8 v8.10.0/go.mod h1:NGmpvohKiRHXI0Sw4fuUGn6hYOmAXlyCphKpzVBiqDE= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/httpexpect/v2 v2.15.0 h1:CCnFk9of4l4ijUhnMxyoEpJsIIBKcuWIFLMwwGTZxNs= +github.com/gavv/httpexpect/v2 v2.15.0/go.mod h1:7myOP3A3VyS4+qnA4cm8DAad8zMN+7zxDB80W9f8yIc= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= +github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/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/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms= +github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= +github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.2/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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e h1:XmA6L9IPRdUr28a+SK/oMchGgQy159wvzXA5tJ7l+40= +github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e/go.mod h1:AFIo+02s+12CEg8Gzz9kzhCbmbq6JcKNrhHffCGA9z4= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= +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/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= +github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kamva/mgm/v3 v3.5.0 h1:/2mNshpqwAC9spdzJZ0VR/UZ/SY/PsNTrMjT111KQjM= +github.com/kamva/mgm/v3 v3.5.0/go.mod h1:F4J1hZnXQMkqL3DZgR7Z7BOuiTqQG/JTic3YzliG4jk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= +github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc= +github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY= +github.com/mehdihadeli/go-mediatr v1.3.0 h1:hrb5Scp/nsiR3Y62mjZ0Tc5UX/dRJl4nDFkINBEIESA= +github.com/mehdihadeli/go-mediatr v1.3.0/go.mod h1:lsG+hyH+pEOhmZiZl0KPO72BcZiEReF03CBk4GVJB0k= +github.com/michaelklishin/rabbit-hole v1.5.0 h1:Bex27BiFDsijCM9D0ezSHqyy0kehpYHuNKaPqq/a4RM= +github.com/michaelklishin/rabbit-hole v1.5.0/go.mod h1:vvI1uOitYZi0O5HEGXhaWC1XT80Gy+HvFheJ+5Krlhk= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +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/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nolleh/caption_json_formatter v0.2.2 h1:EKsOr/fCllNQF2ZoajfbSDlV73BNV1bDu1aTTSRrlN0= +github.com/nolleh/caption_json_formatter v0.2.2/go.mod h1:5FYofZA8NAej/eFxa12FvyQKosU1LfyKizZPlY0JojU= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA= +github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc95/go.mod h1:z+bZxa/+Tz/FmYVWkhUajJdzFeOqjc5vrqskhVyHGUM= +github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM= +github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= +github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= +github.com/ory/dockertest/v3 v3.6.3/go.mod h1:EFLcVUOl8qCwp9NyDAcCDtq/QviLtYswW/VbWzUnTNE= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.69 h1:fBCKnB8dSLAl8FlYRQAWYGp2WTI/Xm/tKJ21Hyo9USw= +github.com/pterm/pterm v0.12.69/go.mod h1:wl06ko9MHnqxz4oDV++IORDpjCzw6+mfrvf0MPj6fdk= +github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= +github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= +github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= +github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= +github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= +github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= +github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= +github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= +github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= +github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= +github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= +github.com/testcontainers/testcontainers-go v0.25.0 h1:erH6cQjsaJrH+rJDU9qIf89KFdhK0Bft0aEZHlYC3Vs= +github.com/testcontainers/testcontainers-go v0.25.0/go.mod h1:4sC9SiJyzD1XFi59q8umTQYWxnkweEc5OjVtTUlJzqQ= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA= +github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI= +github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A= +github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8= +github.com/uptrace/bun/driver/pgdriver v1.1.16 h1:b/NiSXk6Ldw7KLfMLbOqIkm4odHd7QiNOCPLqPFJjK4= +github.com/uptrace/bun/driver/pgdriver v1.1.16/go.mod h1:Rmfbc+7lx1z/umjMyAxkOHK81LgnGj71XC5YpA6k1vU= +github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3 h1:m5eNyOhch/7tyK6aN6eRRpNoD1vM8PNh64dA05X22Js= +github.com/uptrace/opentelemetry-go-extra/otellogrus v0.2.3/go.mod h1:APPUXm9BbpH7NFkfpbw04raZSitzl19/3NOCu0rbI4E= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3 h1:LyGS9cIZV0YVhE81zwfMhIE2l2flcj3wn5IoK4VkbWA= +github.com/uptrace/opentelemetry-go-extra/otelutil v0.2.3/go.mod h1:RvCYhPchLhvQ9l9C9goblbgO7BaKt597kBMf5mgKyo0= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3 h1:2na5W81H38Z4qXCQCuzlcdSMiTWgPJ6XeZIArq6VIJE= +github.com/uptrace/opentelemetry-go-extra/otelzap v0.2.3/go.mod h1:9IVEh9mPv3NwFf99dVLX15FqVgdpZJ8RMDo/Cr0vK74= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= +github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vmihailenco/msgpack/v5 v5.4.0 h1:hRM0digJwyR6vll33NNAwCFguy5JuBD6jxDmQP3l608= +github.com/vmihailenco/msgpack/v5 v5.4.0/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= +go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= +go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0 h1:bldpPC7XAv7f7LKTwNfRkNdzRhjtXaWybZFFa16dAb8= +go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.45.0/go.mod h1:xhkNpJG3D+kmuaciNTco7cdK27Fb77J9Iqcq5CMe4Y8= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= +go.opentelemetry.io/contrib/instrumentation/host v0.45.0 h1:1uzNKJDqZ6y6F5J6aKWgJjRREpKiGhBvKHlWon/bqB4= +go.opentelemetry.io/contrib/instrumentation/host v0.45.0/go.mod h1:vlqPvzDsmB4+jlERxBRXsdLCD6Q0LoBzxHqNXp3qvG4= +go.opentelemetry.io/contrib/propagators/ot v1.20.0 h1:duH7mgL6VGQH7e7QEAVOFkCQXWpCb4PjTtrhdrYrJRQ= +go.opentelemetry.io/contrib/propagators/ot v1.20.0/go.mod h1:gijQzxOq0JLj9lyZhTvqjDddGV/zaNagpPIn+2r8CEI= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 h1:ZtfnDL+tUrs1F0Pzfwbg2d59Gru9NCH3bgSHBM6LDwU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0 h1:NmnYCiR0qNufkldjVvyQfZTHSdzeHoZ41zggMsdMcLM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.42.0/go.mod h1:UVAO61+umUsHLtYb8KXXRoHtxUkdOPkYidzW3gipRLQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +go.opentelemetry.io/otel/exporters/prometheus v0.42.0 h1:jwV9iQdvp38fxXi8ZC+lNpxjK16MRcZlpDYvbuO1FiA= +go.opentelemetry.io/otel/exporters/prometheus v0.42.0/go.mod h1:f3bYiqNqhoPxkvI2LrXqQVC546K7BuRDL/kKuxkujhA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0 h1:4jJuoeOo9W6hZnz+r046fyoH5kykZPRvKfUXJVfMpB0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.42.0/go.mod h1:/MtYTE1SfC2QIcE0bDot6fIX+h+WvXjgTqgn9P0LNPE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0 h1:EGY0h5mGliP9o/nIkVuLI0vRiQqmsYOcbwCuotksO1o= +go.opentelemetry.io/otel/exporters/zipkin v1.19.0/go.mod h1:JQgTGJP11yi3o4GHzIWYodhPisxANdqxF1eHwDSnJrI= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= +go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= +go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= +google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= +google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c= +gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/plugin/opentelemetry v0.1.4 h1:7p0ocWELjSSRI7NCKPW2mVe6h43YPini99sNJcbsTuc= +gorm.io/plugin/opentelemetry v0.1.4/go.mod h1:tndJHOdvPT0pyGhOb8E2209eXJCUxhC5UpKw7bGVWeI= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= +mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= +modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= +modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= +modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= +modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/services/orderservice/internal/orders/configurations/mappings/mappings_profile.go b/internal/services/orderservice/internal/orders/configurations/mappings/mappings_profile.go new file mode 100644 index 00000000..0d772819 --- /dev/null +++ b/internal/services/orderservice/internal/orders/configurations/mappings/mappings_profile.go @@ -0,0 +1,222 @@ +package mappings + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/aggregate" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/value_objects" + grpcOrderService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/grpc/genproto" + + "google.golang.org/protobuf/types/known/timestamppb" +) + +func ConfigureOrdersMappings() error { + // Order -> OrderDto + err := mapper.CreateMap[*aggregate.Order, *dtosV1.OrderDto]() + if err != nil { + return err + } + + // OrderDto -> Order + err = mapper.CreateCustomMap[*dtosV1.OrderDto, *aggregate.Order]( + func(orderDto *dtosV1.OrderDto) *aggregate.Order { + items, err := mapper.Map[[]*value_objects.ShopItem](orderDto.ShopItems) + if err != nil { + return nil + } + + //payment, err := mapper.Map[*entities.Payment](orderDto.Payment) + //if err != nil { + // return nil + //} + + order, err := aggregate.NewOrder( + orderDto.Id, + items, + orderDto.AccountEmail, + orderDto.DeliveryAddress, + orderDto.DeliveredTime, + orderDto.CreatedAt, + ) + if err != nil { + return nil + } + + return order + }, + ) + if err != nil { + return err + } + + // read_models.OrderReadModel -> dtos.OrderReadDto + err = mapper.CreateMap[*read_models.OrderReadModel, *dtosV1.OrderReadDto]() + if err != nil { + return err + } + + // dtos.OrderReadDto -> grpcOrderService.OrderReadModel + // custom filed map not support yet like ForMember so we have to create a custom map because of some timestamp fields map to time.Time + err = mapper.CreateCustomMap[*dtosV1.OrderReadDto, *grpcOrderService.OrderReadModel]( + func(orderReadDto *dtosV1.OrderReadDto) *grpcOrderService.OrderReadModel { + if orderReadDto == nil { + return nil + } + items, err := mapper.Map[[]*grpcOrderService.ShopItemReadModel](orderReadDto.ShopItems) + if err != nil { + return nil + } + + return &grpcOrderService.OrderReadModel{ + Id: orderReadDto.Id, + OrderId: orderReadDto.OrderId, + PaymentId: orderReadDto.PaymentId, + DeliveredTime: timestamppb.New(orderReadDto.DeliveredTime), + TotalPrice: orderReadDto.TotalPrice, + DeliveryAddress: orderReadDto.DeliveryAddress, + AccountEmail: orderReadDto.AccountEmail, + Canceled: orderReadDto.Canceled, + Completed: orderReadDto.Completed, + Paid: orderReadDto.Paid, + Submitted: orderReadDto.Submitted, + CancelReason: orderReadDto.CancelReason, + ShopItems: items, + CreatedAt: timestamppb.New(orderReadDto.CreatedAt), + UpdatedAt: timestamppb.New(orderReadDto.UpdatedAt), + } + }, + ) + if err != nil { + return err + } + + // dtos.ShopItemReadDto -> grpcOrderService.ShopItemReadModel + err = mapper.CreateMap[*dtosV1.ShopItemReadDto, *grpcOrderService.ShopItemReadModel]() + if err != nil { + return err + } + + // ShopItem -> ShopItemDto + err = mapper.CreateMap[*value_objects.ShopItem, *dtosV1.ShopItemDto]() + if err != nil { + return err + } + + // ShopItemDto -> ShopItem + err = mapper.CreateCustomMap[*dtosV1.ShopItemDto, *value_objects.ShopItem]( + func(src *dtosV1.ShopItemDto) *value_objects.ShopItem { + return value_objects.CreateNewShopItem( + src.Title, + src.Description, + src.Quantity, + src.Price, + ) + }, + ) + if err != nil { + return err + } + + // dtos.ShopItemDto -> read_models.ShopItemReadModel + err = mapper.CreateMap[*dtosV1.ShopItemDto, *read_models.ShopItemReadModel]() + if err != nil { + return err + } + + // read_models.ShopItemReadModel -> dtos.ShopItemReadDto + err = mapper.CreateMap[*read_models.ShopItemReadModel, *dtosV1.ShopItemReadDto]() + if err != nil { + return err + } + + // value_objects.ShopItem -> grpcOrderService.ShopItem + err = mapper.CreateCustomMap[*value_objects.ShopItem, *grpcOrderService.ShopItem]( + func(src *value_objects.ShopItem) *grpcOrderService.ShopItem { + return &grpcOrderService.ShopItem{ + Title: src.Title(), + Description: src.Description(), + Quantity: src.Quantity(), + Price: src.Price(), + } + }, + ) + if err != nil { + return err + } + + // grpcOrderService.ShopItem -> value_objects.ShopItem + err = mapper.CreateCustomMap[*grpcOrderService.ShopItem, *value_objects.ShopItem]( + func(src *grpcOrderService.ShopItem) *value_objects.ShopItem { + return value_objects.CreateNewShopItem( + src.Title, + src.Description, + src.Quantity, + src.Price, + ) + }, + ) + if err != nil { + return err + } + + // grpcOrderService.ShopItem -> dtos.ShopItemDto + err = mapper.CreateMap[*grpcOrderService.ShopItem, *dtosV1.ShopItemDto]() + if err != nil { + return err + } + + // aggregate.Order -> grpcOrderService.Order + err = mapper.CreateCustomMap[*aggregate.Order, *grpcOrderService.Order]( + func(order *aggregate.Order) *grpcOrderService.Order { + items, err := mapper.Map[[]*grpcOrderService.ShopItem](order.ShopItems()) + if err != nil { + return nil + } + + return &grpcOrderService.Order{ + OrderId: order.Id().String(), + DeliveryAddress: order.DeliveryAddress(), + DeliveredTime: timestamppb.New(order.DeliveredTime()), + AccountEmail: order.AccountEmail(), + Canceled: order.Canceled(), + Completed: order.Completed(), + Paid: order.Paid(), + CancelReason: order.CancelReason(), + Submitted: order.Submitted(), + TotalPrice: order.TotalPrice(), + CreatedAt: timestamppb.New(order.CreatedAt()), + UpdatedAt: timestamppb.New(order.UpdatedAt()), + ShopItems: items, + PaymentId: order.PaymentId().String(), + } + }, + ) + if err != nil { + return err + } + + err = mapper.CreateCustomMap[*utils.ListResult[*dtosV1.OrderReadDto], *grpcOrderService.GetOrdersRes]( + func(orders *utils.ListResult[*dtosV1.OrderReadDto]) *grpcOrderService.GetOrdersRes { + o, err := mapper.Map[[]*grpcOrderService.OrderReadModel](orders.Items) + if err != nil { + return nil + } + return &grpcOrderService.GetOrdersRes{ + Pagination: &grpcOrderService.Pagination{ + Size: int32(orders.Size), + Page: int32(orders.Page), + TotalItems: orders.TotalItems, + TotalPages: int32(orders.TotalPage), + }, + Orders: o, + } + }, + ) + if err != nil { + return err + } + + return nil +} diff --git a/internal/services/orderservice/internal/orders/configurations/mediatr/mediator_configurations.go b/internal/services/orderservice/internal/orders/configurations/mediatr/mediator_configurations.go new file mode 100644 index 00000000..348b3e8a --- /dev/null +++ b/internal/services/orderservice/internal/orders/configurations/mediatr/mediator_configurations.go @@ -0,0 +1,48 @@ +package mediatr + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/store" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + repositories2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" + createOrderCommandV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/commands" + createOrderDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos" + getOrderByIdDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos" + getOrderByIdQueryV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries" + getOrdersDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos" + getOrdersQueryV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_orders/v1/queries" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/aggregate" + + "github.com/mehdihadeli/go-mediatr" +) + +func ConfigOrdersMediator( + logger logger.Logger, + mongoOrderReadRepository repositories2.OrderMongoRepository, + orderAggregateStore store.AggregateStore[*aggregate.Order], + tracer tracing.AppTracer, +) error { + // https://stackoverflow.com/questions/72034479/how-to-implement-generic-interfaces + err := mediatr.RegisterRequestHandler[*createOrderCommandV1.CreateOrder, *createOrderDtosV1.CreateOrderResponseDto]( + createOrderCommandV1.NewCreateOrderHandler(logger, orderAggregateStore, tracer), + ) + if err != nil { + return err + } + + err = mediatr.RegisterRequestHandler[*getOrderByIdQueryV1.GetOrderById, *getOrderByIdDtosV1.GetOrderByIdResponseDto]( + getOrderByIdQueryV1.NewGetOrderByIdHandler(logger, mongoOrderReadRepository, tracer), + ) + if err != nil { + return err + } + + err = mediatr.RegisterRequestHandler[*getOrdersQueryV1.GetOrders, *getOrdersDtosV1.GetOrdersResponseDto]( + getOrdersQueryV1.NewGetOrdersHandler(logger, mongoOrderReadRepository, tracer), + ) + if err != nil { + return err + } + + return nil +} diff --git a/internal/services/orderservice/internal/orders/configurations/orders_module_configurator.go b/internal/services/orderservice/internal/orders/configurations/orders_module_configurator.go new file mode 100644 index 00000000..bedaa262 --- /dev/null +++ b/internal/services/orderservice/internal/orders/configurations/orders_module_configurator.go @@ -0,0 +1,79 @@ +package configurations + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/store" + contracts2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + grpcServer "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/configurations/mappings" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/configurations/mediatr" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/aggregate" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/grpc" + ordersservice "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/grpc/genproto" + + "github.com/go-playground/validator" + googleGrpc "google.golang.org/grpc" +) + +type OrdersModuleConfigurator struct { + contracts2.Application +} + +func NewOrdersModuleConfigurator( + app contracts2.Application, +) *OrdersModuleConfigurator { + return &OrdersModuleConfigurator{ + Application: app, + } +} + +func (c *OrdersModuleConfigurator) ConfigureOrdersModule() { + c.ResolveFunc( + func(logger logger.Logger, + server customEcho.EchoHttpServer, + orderRepository repositories.OrderMongoRepository, + orderAggregateStore store.AggregateStore[*aggregate.Order], + tracer tracing.AppTracer, + ) error { + // config Orders Mappings + err := mappings.ConfigureOrdersMappings() + if err != nil { + return err + } + + // config Orders Mediators + err = mediatr.ConfigOrdersMediator(logger, orderRepository, orderAggregateStore, tracer) + if err != nil { + return err + } + + return nil + }, + ) +} + +func (c *OrdersModuleConfigurator) MapOrdersEndpoints() { + // config Orders Http Endpoints + c.ResolveFuncWithParamTag(func(endpoints []route.Endpoint) { + for _, endpoint := range endpoints { + endpoint.MapEndpoint() + } + }, `group:"order-routes"`, + ) + + // config Orders Grpc Endpoints + c.ResolveFunc( + func(ordersGrpcServer grpcServer.GrpcServer, ordersMetrics *contracts.OrdersMetrics, logger logger.Logger, validator *validator.Validate) error { + orderGrpcService := grpc.NewOrderGrpcService(logger, validator, ordersMetrics) + ordersGrpcServer.GrpcServiceBuilder().RegisterRoutes(func(server *googleGrpc.Server) { + ordersservice.RegisterOrdersServiceServer(server, orderGrpcService) + }) + return nil + }, + ) +} diff --git a/internal/services/orderservice/internal/orders/configurations/rabbitmq/rabbitmq_configurations.go b/internal/services/orderservice/internal/orders/configurations/rabbitmq/rabbitmq_configurations.go new file mode 100644 index 00000000..3a5792ab --- /dev/null +++ b/internal/services/orderservice/internal/orders/configurations/rabbitmq/rabbitmq_configurations.go @@ -0,0 +1,17 @@ +package rabbitmq + +import ( + rabbitmqConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + producerConfigurations "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/producer/configurations" + createOrderIntegrationEventsV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/events/integration_events" +) + +func ConfigOrdersRabbitMQ(builder rabbitmqConfigurations.RabbitMQConfigurationBuilder) { + // add custom message type mappings + // utils.RegisterCustomMessageTypesToRegistrty(map[string]types.IMessage{"orderCreatedV1": &OrderCreatedV1{}}) + + builder.AddProducer( + createOrderIntegrationEventsV1.OrderCreatedV1{}, + func(builder producerConfigurations.RabbitMQProducerConfigurationBuilder) { + }) +} diff --git a/internal/services/orderservice/internal/orders/contracts/params/order_projection_params.go b/internal/services/orderservice/internal/orders/contracts/params/order_projection_params.go new file mode 100644 index 00000000..5b40325a --- /dev/null +++ b/internal/services/orderservice/internal/orders/contracts/params/order_projection_params.go @@ -0,0 +1,13 @@ +package params + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/projection" + + "go.uber.org/fx" +) + +type OrderProjectionParams struct { + fx.In + + Projections []projection.IProjection `group:"projections"` +} diff --git a/internal/services/orderservice/internal/orders/contracts/params/order_route_params.go b/internal/services/orderservice/internal/orders/contracts/params/order_route_params.go new file mode 100644 index 00000000..619d8211 --- /dev/null +++ b/internal/services/orderservice/internal/orders/contracts/params/order_route_params.go @@ -0,0 +1,19 @@ +package params + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/contracts" + + "github.com/go-playground/validator" + "github.com/labstack/echo/v4" + "go.uber.org/fx" +) + +type OrderRouteParams struct { + fx.In + + OrdersMetrics *contracts.OrdersMetrics + Logger logger.Logger + OrdersGroup *echo.Group `name:"order-echo-group"` + Validator *validator.Validate +} diff --git a/internal/services/orderservice/internal/orders/contracts/repositories/order_repository.go b/internal/services/orderservice/internal/orders/contracts/repositories/order_repository.go new file mode 100644 index 00000000..53fe0dce --- /dev/null +++ b/internal/services/orderservice/internal/orders/contracts/repositories/order_repository.go @@ -0,0 +1,41 @@ +package repositories + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + + uuid "github.com/satori/go.uuid" +) + +type orderReadRepository interface { + GetAllOrders( + ctx context.Context, + listQuery *utils.ListQuery, + ) (*utils.ListResult[*read_models.OrderReadModel], error) + SearchOrders( + ctx context.Context, + searchText string, + listQuery *utils.ListQuery, + ) (*utils.ListResult[*read_models.OrderReadModel], error) + GetOrderById(ctx context.Context, uuid uuid.UUID) (*read_models.OrderReadModel, error) + GetOrderByOrderId(ctx context.Context, orderId uuid.UUID) (*read_models.OrderReadModel, error) + CreateOrder( + ctx context.Context, + order *read_models.OrderReadModel, + ) (*read_models.OrderReadModel, error) + UpdateOrder( + ctx context.Context, + order *read_models.OrderReadModel, + ) (*read_models.OrderReadModel, error) + DeleteOrderByID(ctx context.Context, uuid uuid.UUID) error +} + +type OrderElasticRepository interface { + orderReadRepository +} + +type OrderMongoRepository interface { + orderReadRepository +} diff --git a/internal/services/orderservice/internal/orders/data/repositories/elastic_order_read_repository.go b/internal/services/orderservice/internal/orders/data/repositories/elastic_order_read_repository.go new file mode 100644 index 00000000..07f7326b --- /dev/null +++ b/internal/services/orderservice/internal/orders/data/repositories/elastic_order_read_repository.go @@ -0,0 +1,82 @@ +package repositories + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + + "github.com/elastic/go-elasticsearch/v8" + uuid "github.com/satori/go.uuid" +) + +type elasticOrderReadRepository struct { + log logger.Logger + elasticClient *elasticsearch.Client + tracer tracing.AppTracer +} + +func NewElasticOrderReadRepository( + log logger.Logger, + elasticClient *elasticsearch.Client, + tracer tracing.AppTracer, +) repositories.OrderElasticRepository { + return &elasticOrderReadRepository{log: log, elasticClient: elasticClient, tracer: tracer} +} + +func (e elasticOrderReadRepository) GetAllOrders( + ctx context.Context, + listQuery *utils.ListQuery, +) (*utils.ListResult[*read_models.OrderReadModel], error) { + // TODO implement me + panic("implement me") +} + +func (e elasticOrderReadRepository) SearchOrders( + ctx context.Context, + searchText string, + listQuery *utils.ListQuery, +) (*utils.ListResult[*read_models.OrderReadModel], error) { + // TODO implement me + panic("implement me") +} + +func (e elasticOrderReadRepository) GetOrderById( + ctx context.Context, + uuid uuid.UUID, +) (*read_models.OrderReadModel, error) { + // TODO implement me + panic("implement me") +} + +func (e elasticOrderReadRepository) GetOrderByOrderId( + ctx context.Context, + uuid uuid.UUID, +) (*read_models.OrderReadModel, error) { + // TODO implement me + panic("implement me") +} + +func (e elasticOrderReadRepository) CreateOrder( + ctx context.Context, + order *read_models.OrderReadModel, +) (*read_models.OrderReadModel, error) { + // TODO implement me + panic("implement me") +} + +func (e elasticOrderReadRepository) UpdateOrder( + ctx context.Context, + order *read_models.OrderReadModel, +) (*read_models.OrderReadModel, error) { + // TODO implement me + panic("implement me") +} + +func (e elasticOrderReadRepository) DeleteOrderByID(ctx context.Context, uuid uuid.UUID) error { + // TODO implement me + panic("implement me") +} diff --git a/internal/services/orderservice/internal/orders/data/repositories/mongo_order_read_repository.go b/internal/services/orderservice/internal/orders/data/repositories/mongo_order_read_repository.go new file mode 100644 index 00000000..5f0a22a5 --- /dev/null +++ b/internal/services/orderservice/internal/orders/data/repositories/mongo_order_read_repository.go @@ -0,0 +1,290 @@ +package repositories + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + utils2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + + "emperror.dev/errors" + uuid "github.com/satori/go.uuid" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + attribute2 "go.opentelemetry.io/otel/attribute" +) + +const ( + orderCollection = "orders" +) + +type mongoOrderReadRepository struct { + log logger.Logger + mongoOptions *mongodb.MongoDbOptions + mongoClient *mongo.Client + tracer tracing.AppTracer +} + +func NewMongoOrderReadRepository( + log logger.Logger, + cfg *mongodb.MongoDbOptions, + mongoClient *mongo.Client, + tracer tracing.AppTracer, +) repositories.OrderMongoRepository { + return &mongoOrderReadRepository{ + log: log, + mongoOptions: cfg, + mongoClient: mongoClient, + tracer: tracer, + } +} + +func (m mongoOrderReadRepository) GetAllOrders( + ctx context.Context, + listQuery *utils.ListQuery, +) (*utils.ListResult[*read_models.OrderReadModel], error) { + ctx, span := m.tracer.Start(ctx, "mongoOrderReadRepository.GetAllOrders") + defer span.End() + + collection := m.mongoClient.Database(m.mongoOptions.Database).Collection(orderCollection) + + result, err := mongodb.Paginate[*read_models.OrderReadModel](ctx, listQuery, collection, nil) + if err != nil { + return nil, utils2.TraceErrFromSpan( + span, + errors.WrapIf( + err, + "[mongoOrderReadRepository_GetAllOrders.Paginate] error in the paginate", + ), + ) + } + + m.log.Infow( + "[mongoOrderReadRepository.GetAllOrders] orders loaded", + logger.Fields{"OrdersResult": result}, + ) + + span.SetAttributes(attribute.Object("OrdersResult", result)) + + return result, nil +} + +func (m mongoOrderReadRepository) SearchOrders( + ctx context.Context, + searchText string, + listQuery *utils.ListQuery, +) (*utils.ListResult[*read_models.OrderReadModel], error) { + ctx, span := m.tracer.Start(ctx, "mongoOrderReadRepository.SearchOrders") + span.SetAttributes(attribute2.String("SearchText", searchText)) + defer span.End() + + collection := m.mongoClient.Database(m.mongoOptions.Database).Collection(orderCollection) + + filter := bson.D{ + {Key: "$or", Value: bson.A{ + bson.D{{Key: "name", Value: primitive.Regex{Pattern: searchText, Options: "gi"}}}, + bson.D{ + {Key: "description", Value: primitive.Regex{Pattern: searchText, Options: "gi"}}, + }, + }}, + } + + result, err := mongodb.Paginate[*read_models.OrderReadModel](ctx, listQuery, collection, filter) + if err != nil { + return nil, utils2.TraceErrFromSpan( + span, + errors.WrapIf( + err, + "[mongoOrderReadRepository_SearchOrders.Paginate] error in the paginate", + ), + ) + } + span.SetAttributes(attribute.Object("OrdersResult", result)) + + m.log.Infow( + fmt.Sprintf( + "[mongoOrderReadRepository.SearchOrders] orders loaded for search term '%s'", + searchText, + ), + logger.Fields{"OrdersResult": result}, + ) + + return result, nil +} + +func (m mongoOrderReadRepository) GetOrderById( + ctx context.Context, + id uuid.UUID, +) (*read_models.OrderReadModel, error) { + ctx, span := m.tracer.Start(ctx, "mongoOrderReadRepository.GetOrderById") + span.SetAttributes(attribute2.String("Id", id.String())) + defer span.End() + + collection := m.mongoClient.Database(m.mongoOptions.Database).Collection(orderCollection) + + var order read_models.OrderReadModel + if err := collection.FindOne(ctx, bson.M{"_id": id.String()}).Decode(&order); err != nil { + // ErrNoDocuments means that the filter did not match any documents in the collection + if err == mongo.ErrNoDocuments { + return nil, nil + } + return nil, utils2.TraceErrFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "[mongoOrderReadRepository_GetOrderById.FindOne] can't find the order with id %s into the database.", + id, + ), + ), + ) + } + span.SetAttributes(attribute.Object("Order", order)) + + m.log.Infow( + fmt.Sprintf("[mongoOrderReadRepository.GetOrderById] order with id %s laoded", id.String()), + logger.Fields{"Order": order, "Id": id}, + ) + + return &order, nil +} + +func (m mongoOrderReadRepository) GetOrderByOrderId( + ctx context.Context, + orderId uuid.UUID, +) (*read_models.OrderReadModel, error) { + ctx, span := m.tracer.Start(ctx, "mongoOrderReadRepository.GetOrderByOrderId") + span.SetAttributes(attribute2.String("OrderId", orderId.String())) + defer span.End() + + collection := m.mongoClient.Database(m.mongoOptions.Database).Collection(orderCollection) + + var order read_models.OrderReadModel + if err := collection.FindOne(ctx, bson.M{"orderId": orderId.String()}).Decode(&order); err != nil { + // ErrNoDocuments means that the filter did not match any documents in the collection + if err == mongo.ErrNoDocuments { + return nil, nil + } + return nil, utils2.TraceErrFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "[mongoOrderReadRepository_GetOrderById.FindOne] can't find the order with orderId %s into the database.", + orderId.String(), + ), + ), + ) + } + span.SetAttributes(attribute.Object("Order", order)) + + m.log.Infow( + fmt.Sprintf( + "[mongoOrderReadRepository.GetOrderById] order with orderId %s laoded", + orderId.String(), + ), + logger.Fields{"Order": order, "orderId": orderId}, + ) + + return &order, nil +} + +func (m mongoOrderReadRepository) CreateOrder( + ctx context.Context, + order *read_models.OrderReadModel, +) (*read_models.OrderReadModel, error) { + ctx, span := m.tracer.Start(ctx, "mongoOrderReadRepository.CreateOrder") + defer span.End() + + collection := m.mongoClient.Database(m.mongoOptions.Database).Collection(orderCollection) + _, err := collection.InsertOne(ctx, order, &options.InsertOneOptions{}) + if err != nil { + return nil, utils2.TraceErrFromSpan( + span, + errors.WrapIf( + err, + "[mongoOrderReadRepository_CreateOrder.InsertOne] error in the inserting order into the database.", + ), + ) + } + span.SetAttributes(attribute.Object("Order", order)) + + m.log.Infow( + fmt.Sprintf( + "[mongoOrderReadRepository.CreateOrder] order with id '%s' created", + order.OrderId, + ), + logger.Fields{"Order": order, "Id": order.OrderId}, + ) + + return order, nil +} + +func (m mongoOrderReadRepository) UpdateOrder( + ctx context.Context, + order *read_models.OrderReadModel, +) (*read_models.OrderReadModel, error) { + ctx, span := m.tracer.Start(ctx, "mongoOrderReadRepository.UpdateOrder") + defer span.End() + + collection := m.mongoClient.Database(m.mongoOptions.Database).Collection(orderCollection) + + ops := options.FindOneAndUpdate() + ops.SetReturnDocument(options.After) + ops.SetUpsert(true) + + var updated read_models.OrderReadModel + if err := collection.FindOneAndUpdate(ctx, bson.M{"_id": order.OrderId}, bson.M{"$set": order}, ops).Decode(&updated); err != nil { + return nil, utils2.TraceErrFromSpan( + span, + errors.WrapIf( + err, + fmt.Sprintf( + "[mongoOrderReadRepository_UpdateOrder.FindOneAndUpdate] error in updating order with id %s into the database.", + order.OrderId, + ), + ), + ) + } + span.SetAttributes(attribute.Object("Order", order)) + + m.log.Infow( + fmt.Sprintf( + "[mongoOrderReadRepository.UpdateOrder] order with id '%s' updated", + order.OrderId, + ), + logger.Fields{"Order": order, "Id": order.OrderId}, + ) + + return &updated, nil +} + +func (m mongoOrderReadRepository) DeleteOrderByID(ctx context.Context, uuid uuid.UUID) error { + ctx, span := m.tracer.Start(ctx, "mongoOrderReadRepository.DeleteOrderByID") + span.SetAttributes(attribute2.String("Id", uuid.String())) + defer span.End() + + collection := m.mongoClient.Database(m.mongoOptions.Database).Collection(orderCollection) + + if err := collection.FindOneAndDelete(ctx, bson.M{"_id": uuid.String()}).Err(); err != nil { + return utils2.TraceErrFromSpan(span, errors.WrapIf(err, fmt.Sprintf( + "[mongoOrderReadRepository_DeleteOrderByID.FindOneAndDelete] error in deleting order with id %d from the database.", + uuid, + ))) + } + + m.log.Infow( + fmt.Sprintf("[mongoOrderReadRepository.DeleteOrderByID] order with id %s deleted", uuid), + logger.Fields{"Id": uuid}, + ) + + return nil +} diff --git a/internal/services/orderservice/internal/orders/dtos/v1/order_dto.go b/internal/services/orderservice/internal/orders/dtos/v1/order_dto.go new file mode 100644 index 00000000..639a40d2 --- /dev/null +++ b/internal/services/orderservice/internal/orders/dtos/v1/order_dto.go @@ -0,0 +1,25 @@ +package dtosV1 + +import ( + "time" + + uuid "github.com/satori/go.uuid" +) + +type OrderDto struct { + Id uuid.UUID `json:"id"` + ShopItems []*ShopItemDto `json:"shopItems"` + AccountEmail string `json:"accountEmail"` + DeliveryAddress string `json:"deliveryAddress"` + CancelReason string `json:"cancelReason"` + TotalPrice float64 `json:"totalPrice"` + DeliveredTime time.Time `json:"deliveredTime"` + Paid bool `json:"paid"` + Submitted bool `json:"submitted"` + Completed bool `json:"completed"` + Canceled bool `json:"canceled"` + PaymentId uuid.UUID `json:"paymentId"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + OriginalVersion int64 `json:"originalVersion"` +} diff --git a/internal/services/orderservice/internal/orders/dtos/v1/order_read_dto.go b/internal/services/orderservice/internal/orders/dtos/v1/order_read_dto.go new file mode 100644 index 00000000..0ea9f3c7 --- /dev/null +++ b/internal/services/orderservice/internal/orders/dtos/v1/order_read_dto.go @@ -0,0 +1,21 @@ +package dtosV1 + +import "time" + +type OrderReadDto struct { + Id string `json:"id"` + OrderId string `json:"orderId"` + ShopItems []*ShopItemReadDto `json:"shopItems"` + AccountEmail string `json:"accountEmail"` + DeliveryAddress string `json:"deliveryAddress"` + CancelReason string `json:"cancelReason"` + TotalPrice float64 `json:"totalPrice"` + DeliveredTime time.Time `json:"deliveredTime"` + Paid bool `json:"paid"` + Submitted bool `json:"submitted"` + Completed bool `json:"completed"` + Canceled bool `json:"canceled"` + PaymentId string `json:"paymentId"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/internal/services/orderservice/internal/orders/dtos/v1/shopItem_dto.go b/internal/services/orderservice/internal/orders/dtos/v1/shopItem_dto.go new file mode 100644 index 00000000..a70c710f --- /dev/null +++ b/internal/services/orderservice/internal/orders/dtos/v1/shopItem_dto.go @@ -0,0 +1,8 @@ +package dtosV1 + +type ShopItemDto struct { + Title string `json:"title"` + Description string `json:"description"` + Quantity uint64 `json:"quantity"` + Price float64 `json:"price"` +} diff --git a/internal/services/orderservice/internal/orders/dtos/v1/shopItem_read_dto.go b/internal/services/orderservice/internal/orders/dtos/v1/shopItem_read_dto.go new file mode 100644 index 00000000..dbfffaa3 --- /dev/null +++ b/internal/services/orderservice/internal/orders/dtos/v1/shopItem_read_dto.go @@ -0,0 +1,8 @@ +package dtosV1 + +type ShopItemReadDto struct { + Title string `json:"title"` + Description string `json:"description"` + Quantity uint64 `json:"quantity"` + Price float64 `json:"price"` +} diff --git a/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/invalid_delivery_address_error.go b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/invalid_delivery_address_error.go new file mode 100644 index 00000000..2fe49a3e --- /dev/null +++ b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/invalid_delivery_address_error.go @@ -0,0 +1,37 @@ +package domainExceptions + +import ( + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + + "emperror.dev/errors" +) + +type invalidDeliveryAddressError struct { + customErrors.BadRequestError +} +type InvalidDeliveryAddressError interface { + customErrors.BadRequestError +} + +func NewInvalidDeliveryAddressError(message string) error { + bad := customErrors.NewBadRequestError(message) + customErr := customErrors.GetCustomError(bad).(customErrors.BadRequestError) + br := &invalidDeliveryAddressError{ + BadRequestError: customErr, + } + + return errors.WithStackIf(br) +} + +func (i *invalidDeliveryAddressError) isInvalidAddress() bool { + return true +} + +func IsInvalidDeliveryAddressError(err error) bool { + var ia *invalidDeliveryAddressError + if errors.As(err, &ia) { + return ia.isInvalidAddress() + } + + return false +} diff --git a/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/invalid_email_error.go b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/invalid_email_error.go new file mode 100644 index 00000000..4a093e52 --- /dev/null +++ b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/invalid_email_error.go @@ -0,0 +1,39 @@ +package domainExceptions + +import ( + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + + "emperror.dev/errors" +) + +type invalidEmailAddressError struct { + customErrors.BadRequestError +} + +type InvalidEmailAddressError interface { + customErrors.BadRequestError +} + +func NewInvalidEmailAddressError(message string) error { + bad := customErrors.NewBadRequestError(message) + customErr := customErrors.GetCustomError(bad).(customErrors.BadRequestError) + br := &invalidEmailAddressError{ + BadRequestError: customErr, + } + + return errors.WithStackIf(br) +} + +func (i *invalidEmailAddressError) isInvalidEmailAddressError() bool { + return true +} + +func IsInvalidEmailAddressError(err error) bool { + var ie *invalidEmailAddressError + + if errors.As(err, &ie) { + return ie.isInvalidEmailAddressError() + } + + return false +} diff --git a/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/order_domain_errors_test.go b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/order_domain_errors_test.go new file mode 100644 index 00000000..90bbca75 --- /dev/null +++ b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/order_domain_errors_test.go @@ -0,0 +1,55 @@ +package domainExceptions + +import ( + "fmt" + "testing" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + errorUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils/errorutils" + + "github.com/stretchr/testify/assert" +) + +func Test_Order_Shop_Items_Required_Error(t *testing.T) { + err := NewOrderShopItemsRequiredError("order items required") + assert.True(t, IsOrderShopItemsRequiredError(err)) + fmt.Println(errorUtils.ErrorsWithStack(err)) +} + +func Test_Order_Not_Found_Error(t *testing.T) { + err := NewOrderNotFoundError(1) + assert.True(t, IsOrderNotFoundError(err)) + fmt.Println(errorUtils.ErrorsWithStack(err)) +} + +func Test_Invalid_Delivery_Address_Error(t *testing.T) { + t.Parallel() + + err := NewInvalidDeliveryAddressError("address is not valid") + assert.True(t, IsInvalidDeliveryAddressError(err)) + fmt.Println(errorUtils.ErrorsWithStack(err)) +} + +func Test_Is_Not_Invalid_Delivery_Address_Error( + t *testing.T, +) { + t.Parallel() + + err := customErrors.NewBadRequestError("address is not valid") + assert.False(t, IsInvalidDeliveryAddressError(err)) +} + +func Test_InvalidEmail_Address_Error(t *testing.T) { + t.Parallel() + + err := NewInvalidEmailAddressError("email address is not valid") + assert.True(t, IsInvalidEmailAddressError(err)) + fmt.Println(errorUtils.ErrorsWithStack(err)) +} + +func Test_Is_Not_InvalidEmail_Address_Error(t *testing.T) { + t.Parallel() + + err := customErrors.NewBadRequestError("email address is not valid") + assert.False(t, IsInvalidEmailAddressError(err)) +} diff --git a/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/order_items_required_errors.go b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/order_items_required_errors.go new file mode 100644 index 00000000..5d44d020 --- /dev/null +++ b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/order_items_required_errors.go @@ -0,0 +1,38 @@ +package domainExceptions + +import ( + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + + "emperror.dev/errors" +) + +type orderShopItemsRequiredError struct { + customErrors.BadRequestError +} + +type OrderShopItemsRequiredError interface { + customErrors.BadRequestError +} + +func NewOrderShopItemsRequiredError(message string) error { + bad := customErrors.NewBadRequestError(message) + customErr := customErrors.GetCustomError(bad).(customErrors.BadRequestError) + br := &orderShopItemsRequiredError{ + BadRequestError: customErr, + } + + return errors.WithStackIf(br) +} + +func (i *orderShopItemsRequiredError) isOrderShopItemsRequiredError() bool { + return true +} + +func IsOrderShopItemsRequiredError(err error) bool { + var os *orderShopItemsRequiredError + if errors.As(err, &os) { + return os.isOrderShopItemsRequiredError() + } + + return false +} diff --git a/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/order_not_found_error.go b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/order_not_found_error.go new file mode 100644 index 00000000..5e5cad41 --- /dev/null +++ b/internal/services/orderservice/internal/orders/exceptions/domain_exceptions/order_not_found_error.go @@ -0,0 +1,42 @@ +package domainExceptions + +import ( + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + + "emperror.dev/errors" +) + +type orderNotFoundError struct { + customErrors.NotFoundError +} + +type OrderNotFoundError interface { + customErrors.NotFoundError +} + +func NewOrderNotFoundError(id int) error { + notFound := customErrors.NewNotFoundError( + fmt.Sprintf("order with id %d not found", id), + ) + customErr := customErrors.GetCustomError(notFound).(customErrors.NotFoundError) + br := &orderNotFoundError{ + NotFoundError: customErr, + } + + return errors.WithStackIf(br) +} + +func (i *orderNotFoundError) isorderNotFoundError() bool { + return true +} + +func IsOrderNotFoundError(err error) bool { + var os *orderNotFoundError + if errors.As(err, &os) { + return os.isorderNotFoundError() + } + + return false +} diff --git a/internal/services/orderservice/internal/orders/features/creating_order/v1/commands/create_order.go b/internal/services/orderservice/internal/orders/features/creating_order/v1/commands/create_order.go new file mode 100644 index 00000000..9278c346 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/creating_order/v1/commands/create_order.go @@ -0,0 +1,54 @@ +package createOrderCommandV1 + +import ( + "time" + + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + + validation "github.com/go-ozzo/ozzo-validation" + uuid "github.com/satori/go.uuid" +) + +// https://echo.labstack.com/guide/request/ +// https://github.com/go-playground/validator +type CreateOrder struct { + OrderId uuid.UUID + ShopItems []*dtosV1.ShopItemDto + AccountEmail string + DeliveryAddress string + DeliveryTime time.Time + CreatedAt time.Time +} + +func NewCreateOrder( + shopItems []*dtosV1.ShopItemDto, + accountEmail, deliveryAddress string, + deliveryTime time.Time, +) (*CreateOrder, error) { + command := &CreateOrder{ + OrderId: uuid.NewV4(), + ShopItems: shopItems, + AccountEmail: accountEmail, + DeliveryAddress: deliveryAddress, + DeliveryTime: deliveryTime, + CreatedAt: time.Now(), + } + + err := command.Validate() + if err != nil { + return nil, err + } + + return command, nil +} + +func (c CreateOrder) Validate() error { + return validation.ValidateStruct(&c, + validation.Field(&c.OrderId, validation.Required), + validation.Field(&c.ShopItems, validation.Required), + validation.Field(&c.AccountEmail, validation.Required), + validation.Field(&c.DeliveryAddress, validation.Required), + validation.Field(&c.DeliveryTime, validation.Required), + validation.Field(&c.CreatedAt, validation.Required), + ) +} diff --git a/internal/services/orderservice/internal/orders/features/creating_order/v1/commands/create_order_handler.go b/internal/services/orderservice/internal/orders/features/creating_order/v1/commands/create_order_handler.go new file mode 100644 index 00000000..378c16cd --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/creating_order/v1/commands/create_order_handler.go @@ -0,0 +1,95 @@ +package createOrderCommandV1 + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/store" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + customAttribute "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/aggregate" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/value_objects" + + "go.opentelemetry.io/otel/attribute" +) + +type CreateOrderHandler struct { + log logger.Logger + // goland can't detect this generic type, but it is ok in vscode + aggregateStore store.AggregateStore[*aggregate.Order] + tracer tracing.AppTracer +} + +func NewCreateOrderHandler( + log logger.Logger, + aggregateStore store.AggregateStore[*aggregate.Order], + tracer tracing.AppTracer, +) *CreateOrderHandler { + return &CreateOrderHandler{log: log, aggregateStore: aggregateStore, tracer: tracer} +} + +func (c *CreateOrderHandler) Handle( + ctx context.Context, + command *CreateOrder, +) (*dtos.CreateOrderResponseDto, error) { + ctx, span := c.tracer.Start(ctx, "CreateOrderHandler.Handle") + span.SetAttributes(attribute.String("OrderId", command.OrderId.String())) + span.SetAttributes(customAttribute.Object("Command", command)) + defer span.End() + + shopItems, err := mapper.Map[[]*value_objects.ShopItem](command.ShopItems) + if err != nil { + return nil, utils.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + "[CreateOrderHandler_Handle.Map] error in the mapping shopItems", + ), + ) + } + + order, err := aggregate.NewOrder( + command.OrderId, + shopItems, + command.AccountEmail, + command.DeliveryAddress, + command.DeliveryTime, + command.CreatedAt, + ) + if err != nil { + return nil, utils.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + "[CreateOrderHandler_Handle.NewOrder] error in creating new order", + ), + ) + } + + _, err = c.aggregateStore.Store(order, nil, ctx) + if err != nil { + return nil, utils.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + "[CreateOrderHandler_Handle.Store] error in storing order aggregate", + ), + ) + } + + response := &dtos.CreateOrderResponseDto{OrderId: order.Id()} + + span.SetAttributes(customAttribute.Object("CreateOrderResponseDto", response)) + + c.log.Infow( + fmt.Sprintf("[CreateOrderHandler.Handle] order with id: {%s} created", command.OrderId), + logger.Fields{"ProductId": command.OrderId}, + ) + + return response, nil +} diff --git a/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos/create_order_request_dto.go b/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos/create_order_request_dto.go new file mode 100644 index 00000000..ff2c1064 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos/create_order_request_dto.go @@ -0,0 +1,18 @@ +package dtos + +import ( + customTypes "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/customtypes" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" +) + +// https://echo.labstack.com/guide/binding/ +// https://echo.labstack.com/guide/request/ +// https://github.com/go-playground/validator + +// CreateOrderRequestDto validation will handle in command level +type CreateOrderRequestDto struct { + ShopItems []*dtosV1.ShopItemDto `json:"shopItems"` + AccountEmail string `json:"accountEmail"` + DeliveryAddress string `json:"deliveryAddress"` + DeliveryTime customTypes.CustomTime `json:"deliveryTime"` +} diff --git a/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos/create_order_response_dto.go b/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos/create_order_response_dto.go new file mode 100644 index 00000000..ecd912f2 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos/create_order_response_dto.go @@ -0,0 +1,8 @@ +package dtos + +import uuid "github.com/satori/go.uuid" + +// https://echo.labstack.com/guide/response/ +type CreateOrderResponseDto struct { + OrderId uuid.UUID `json:"Id"` +} diff --git a/internal/services/orderservice/internal/orders/features/creating_order/v1/endpoints/create_order_endpoint.go b/internal/services/orderservice/internal/orders/features/creating_order/v1/endpoints/create_order_endpoint.go new file mode 100644 index 00000000..ad01e35a --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/creating_order/v1/endpoints/create_order_endpoint.go @@ -0,0 +1,97 @@ +package createOrderV1 + +import ( + "fmt" + "net/http" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/params" + createOrderCommandV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/commands" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type createOrderEndpoint struct { + params.OrderRouteParams +} + +func NewCreteOrderEndpoint(params params.OrderRouteParams) route.Endpoint { + return &createOrderEndpoint{OrderRouteParams: params} +} + +func (ep *createOrderEndpoint) MapEndpoint() { + ep.OrdersGroup.POST("", ep.handler()) +} + +// Create Order +// @Tags Orders +// @Summary Create order +// @Description Create new order +// @Accept json +// @Produce json +// @Param CreateOrderRequestDto body dtos.CreateOrderRequestDto true "Order data" +// @Success 201 {object} dtos.CreateOrderResponseDto +// @Router /api/v1/orders [post] +func (ep *createOrderEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + ep.OrdersMetrics.CreateOrderHttpRequests.Add(ctx, 1) + + request := &dtos.CreateOrderRequestDto{} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "[createOrderEndpoint_handler.Bind] error in the binding request", + ) + ep.Logger.Errorf( + fmt.Sprintf("[createOrderEndpoint_handler.Bind] err: %v", badRequestErr), + ) + return badRequestErr + } + + command, err := createOrderCommandV1.NewCreateOrder( + request.ShopItems, + request.AccountEmail, + request.DeliveryAddress, + time.Time(request.DeliveryTime), + ) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "[createOrderEndpoint_handler.StructCtx] command validation failed", + ) + ep.Logger.Errorf( + fmt.Sprintf("[createOrderEndpoint_handler.StructCtx] err: %v", validationErr), + ) + return validationErr + } + + result, err := mediatr.Send[*createOrderCommandV1.CreateOrder, *dtos.CreateOrderResponseDto]( + ctx, + command, + ) + if err != nil { + err = errors.WithMessage( + err, + "[createOrderEndpoint_handler.Send] error in sending CreateOrder", + ) + ep.Logger.Errorw( + fmt.Sprintf( + "[createOrderEndpoint_handler.Send] id: {%s}, err: %v", + command.OrderId, + err, + ), + logger.Fields{"Id": command.OrderId}, + ) + return err + } + + return c.JSON(http.StatusCreated, result) + } +} diff --git a/internal/services/orderservice/internal/orders/features/creating_order/v1/events/domain_events/order_created.go b/internal/services/orderservice/internal/orders/features/creating_order/v1/events/domain_events/order_created.go new file mode 100644 index 00000000..e26e9afd --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/creating_order/v1/events/domain_events/order_created.go @@ -0,0 +1,64 @@ +package domainEvents + +import ( + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + domainExceptions "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/exceptions/domain_exceptions" + + uuid "github.com/satori/go.uuid" +) + +type OrderCreatedV1 struct { + *domain.DomainEvent + OrderId uuid.UUID `json:"order_id"` + ShopItems []*dtosV1.ShopItemDto `json:"shopItems" bson:"shopItems,omitempty"` + AccountEmail string `json:"accountEmail" bson:"accountEmail,omitempty"` + DeliveryAddress string `json:"deliveryAddress" bson:"deliveryAddress,omitempty"` + CreatedAt time.Time `json:"createdAt" bson:"createdAt,omitempty"` + DeliveredTime time.Time `json:"deliveredTime" bson:"deliveredTime,omitempty"` +} + +func NewOrderCreatedEventV1( + aggregateId uuid.UUID, + shopItems []*dtosV1.ShopItemDto, + accountEmail, deliveryAddress string, + deliveredTime time.Time, + createdAt time.Time, +) (*OrderCreatedV1, error) { + if shopItems == nil || len(shopItems) == 0 { + return nil, domainExceptions.NewOrderShopItemsRequiredError("shopItems is required") + } + + if deliveryAddress == "" { + return nil, domainExceptions.NewInvalidDeliveryAddressError("deliveryAddress is invalid") + } + + if accountEmail == "" { + return nil, domainExceptions.NewInvalidEmailAddressError("accountEmail is invalid") + } + + if createdAt.IsZero() { + return nil, customErrors.NewDomainError("createdAt can't be zero") + } + + if deliveredTime.IsZero() { + return nil, customErrors.NewDomainError("deliveredTime can't be zero") + } + + eventData := &OrderCreatedV1{ + ShopItems: shopItems, + OrderId: aggregateId, + AccountEmail: accountEmail, + DeliveryAddress: deliveryAddress, + CreatedAt: createdAt, + DeliveredTime: deliveredTime, + } + + eventData.DomainEvent = domain.NewDomainEvent(typeMapper.GetTypeName(eventData)) + + return eventData, nil +} diff --git a/internal/services/orderservice/internal/orders/features/creating_order/v1/events/integration_events/order_created.go b/internal/services/orderservice/internal/orders/features/creating_order/v1/events/integration_events/order_created.go new file mode 100644 index 00000000..750866ab --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/creating_order/v1/events/integration_events/order_created.go @@ -0,0 +1,20 @@ +package integrationEvents + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/types" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + + uuid "github.com/satori/go.uuid" +) + +type OrderCreatedV1 struct { + *types.Message + *dtosV1.OrderReadDto +} + +func NewOrderCreatedV1(orderReadDto *dtosV1.OrderReadDto) *OrderCreatedV1 { + return &OrderCreatedV1{ + OrderReadDto: orderReadDto, + Message: types.NewMessage(uuid.NewV4().String()), + } +} diff --git a/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos/get_order_by_id_request_dto.go b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos/get_order_by_id_request_dto.go new file mode 100644 index 00000000..576ee9fb --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos/get_order_by_id_request_dto.go @@ -0,0 +1,7 @@ +package dtos + +import uuid "github.com/satori/go.uuid" + +type GetOrderByIdRequestDto struct { + Id uuid.UUID `param:"id" json:"-"` +} diff --git a/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos/get_order_by_id_response_dto.go b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos/get_order_by_id_response_dto.go new file mode 100644 index 00000000..62308d3b --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos/get_order_by_id_response_dto.go @@ -0,0 +1,7 @@ +package dtos + +import dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + +type GetOrderByIdResponseDto struct { + Order *dtosV1.OrderReadDto `json:"order"` +} diff --git a/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/endpoints/get_order_by_id_endpoint.go b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/endpoints/get_order_by_id_endpoint.go new file mode 100644 index 00000000..e408b648 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/endpoints/get_order_by_id_endpoint.go @@ -0,0 +1,89 @@ +package endpoints + +import ( + "fmt" + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/params" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type getOrderByIdEndpoint struct { + params.OrderRouteParams +} + +func NewGetOrderByIdEndpoint(params params.OrderRouteParams) route.Endpoint { + return &getOrderByIdEndpoint{OrderRouteParams: params} +} + +func (ep *getOrderByIdEndpoint) MapEndpoint() { + ep.OrdersGroup.GET("/:id", ep.handler()) +} + +// Get Order By ID +// @Tags Orders +// @Summary Get order by id +// @Description Get order by id +// @Accept json +// @Produce json +// @Param id path string true "Order ID" +// @Success 200 {object} dtos.GetOrderByIdResponseDto +// @Router /api/v1/orders/{id} [get] +func (ep *getOrderByIdEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + ep.OrdersMetrics.GetOrderByIdHttpRequests.Add(ctx, 1) + + request := &dtos.GetOrderByIdRequestDto{} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "[getProductByIdEndpoint_handler.Bind] error in the binding request", + ) + ep.Logger.Errorf( + fmt.Sprintf("[getProductByIdEndpoint_handler.Bind] err: %v", badRequestErr), + ) + return badRequestErr + } + + query, err := queries.NewGetOrderById(request.Id) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "[getProductByIdEndpoint_handler.StructCtx] query validation failed", + ) + ep.Logger.Errorf("[getProductByIdEndpoint_handler.StructCtx] err: %v", validationErr) + return validationErr + } + + queryResult, err := mediatr.Send[*queries.GetOrderById, *dtos.GetOrderByIdResponseDto]( + ctx, + query, + ) + if err != nil { + err = errors.WithMessage( + err, + "[getProductByIdEndpoint_handler.Send] error in sending GetOrderById", + ) + ep.Logger.Errorw( + fmt.Sprintf( + "[getProductByIdEndpoint_handler.Send] id: {%s}, err: %v", + query.Id, + err, + ), + logger.Fields{"Id": query.Id}, + ) + return err + } + + return c.JSON(http.StatusOK, queryResult) + } +} diff --git a/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries/get_order_by_id.go b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries/get_order_by_id.go new file mode 100644 index 00000000..9fdb2ef8 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries/get_order_by_id.go @@ -0,0 +1,27 @@ +package queries + +import ( + validation "github.com/go-ozzo/ozzo-validation" + uuid "github.com/satori/go.uuid" +) + +type GetOrderById struct { + Id uuid.UUID +} + +func NewGetOrderById(id uuid.UUID) (*GetOrderById, error) { + query := &GetOrderById{Id: id} + + err := query.Validate() + if err != nil { + return nil, err + } + + return query, nil +} + +func (g GetOrderById) Validate() error { + return validation.ValidateStruct(&g, + validation.Field(&g.Id, validation.Required), + ) +} diff --git a/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries/get_order_by_id_handler.go b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries/get_order_by_id_handler.go new file mode 100644 index 00000000..4ac75984 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries/get_order_by_id_handler.go @@ -0,0 +1,96 @@ +package queries + +import ( + "context" + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos" + + attribute2 "go.opentelemetry.io/otel/attribute" +) + +type GetOrderByIdHandler struct { + log logger.Logger + orderMongoRepository repositories.OrderMongoRepository + tracer tracing.AppTracer +} + +func NewGetOrderByIdHandler( + log logger.Logger, + orderMongoRepository repositories.OrderMongoRepository, + tracer tracing.AppTracer, +) *GetOrderByIdHandler { + return &GetOrderByIdHandler{ + log: log, + orderMongoRepository: orderMongoRepository, + tracer: tracer, + } +} + +func (q *GetOrderByIdHandler) Handle( + ctx context.Context, + query *GetOrderById, +) (*dtos.GetOrderByIdResponseDto, error) { + ctx, span := q.tracer.Start(ctx, "GetOrderByIdHandler.Handle") + span.SetAttributes(attribute2.String("Id", query.Id.String())) + span.SetAttributes(attribute.Object("Query", query)) + defer span.End() + + // get order by order-read id + order, err := q.orderMongoRepository.GetOrderById(ctx, query.Id) + if err != nil { + return nil, utils.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + fmt.Sprintf( + "[GetOrderByIdHandler_Handle.GetProductById] error in getting order with id %s in the mongo repository", + query.Id.String(), + ), + ), + ) + } + + if order == nil { + // get order by order-write id + order, err = q.orderMongoRepository.GetOrderByOrderId(ctx, query.Id) + if err != nil { + return nil, utils.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + fmt.Sprintf( + "[GetOrderByIdHandler_Handle.GetProductById] error in getting order with orderId %s in the mongo repository", + query.Id.String(), + ), + ), + ) + } + } + + orderDto, err := mapper.Map[*dtosV1.OrderReadDto](order) + if err != nil { + return nil, utils.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + "[GetOrderByIdHandler_Handle.Map] error in the mapping order", + ), + ) + } + + q.log.Infow( + fmt.Sprintf("[GetOrderByIdHandler.Handle] order with id: {%s} fetched", query.Id.String()), + logger.Fields{"Id": query.Id}, + ) + + return &dtos.GetOrderByIdResponseDto{Order: orderDto}, nil +} diff --git a/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos/get_orders_request_dto.go b/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos/get_orders_request_dto.go new file mode 100644 index 00000000..686d18e5 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos/get_orders_request_dto.go @@ -0,0 +1,7 @@ +package dtos + +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + +type GetOrdersRequestDto struct { + *utils.ListQuery +} diff --git a/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos/get_orders_response_dto.go b/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos/get_orders_response_dto.go new file mode 100644 index 00000000..c09ffeb2 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos/get_orders_response_dto.go @@ -0,0 +1,10 @@ +package dtos + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" +) + +type GetOrdersResponseDto struct { + Orders *utils.ListResult[*dtosV1.OrderReadDto] +} diff --git a/internal/services/orderservice/internal/orders/features/getting_orders/v1/endpoints/get_orders_endpoint.go b/internal/services/orderservice/internal/orders/features/getting_orders/v1/endpoints/get_orders_endpoint.go new file mode 100644 index 00000000..55cfadac --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_orders/v1/endpoints/get_orders_endpoint.go @@ -0,0 +1,84 @@ +package endpoints + +import ( + "fmt" + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/params" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_orders/v1/queries" + + "emperror.dev/errors" + "github.com/labstack/echo/v4" + "github.com/mehdihadeli/go-mediatr" +) + +type getOrdersEndpoint struct { + params.OrderRouteParams +} + +func NewGetOrdersEndpoint(params params.OrderRouteParams) route.Endpoint { + return &getOrdersEndpoint{OrderRouteParams: params} +} + +func (ep *getOrdersEndpoint) MapEndpoint() { + ep.OrdersGroup.GET("", ep.handler()) +} + +// GetAllOrders +// @Tags Orders +// @Summary Get all orders +// @Description Get all orders +// @Accept json +// @Produce json +// @Param getOrdersRequestDto query dtos.GetOrdersRequestDto false "GetOrdersRequestDto" +// @Success 200 {object} dtos.GetOrdersResponseDto +// @Router /api/v1/orders [get] +func (ep *getOrdersEndpoint) handler() echo.HandlerFunc { + return func(c echo.Context) error { + ctx := c.Request().Context() + ep.OrdersMetrics.GetOrdersHttpRequests.Add(ctx, 1) + + listQuery, err := utils.GetListQueryFromCtx(c) + if err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "[getOrdersEndpoint_handler.GetListQueryFromCtx] error in getting data from query string", + ) + ep.Logger.Errorf( + fmt.Sprintf( + "[getOrdersEndpoint_handler.GetListQueryFromCtx] err: %v", + badRequestErr, + ), + ) + return err + } + + request := &dtos.GetOrdersRequestDto{ListQuery: listQuery} + if err := c.Bind(request); err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "[getOrdersEndpoint_handler.Bind] error in the binding request", + ) + ep.Logger.Errorf(fmt.Sprintf("[getOrdersEndpoint_handler.Bind] err: %v", badRequestErr)) + return badRequestErr + } + + query := queries.NewGetOrders(request.ListQuery) + + queryResult, err := mediatr.Send[*queries.GetOrders, *dtos.GetOrdersResponseDto](ctx, query) + if err != nil { + err = errors.WithMessage( + err, + "[getOrdersEndpoint_handler.Send] error in sending GetOrders", + ) + ep.Logger.Error(fmt.Sprintf("[getOrdersEndpoint_handler.Send] err: {%v}", err)) + return err + } + + return c.JSON(http.StatusOK, queryResult) + } +} diff --git a/internal/services/orderservice/internal/orders/features/getting_orders/v1/queries/get_orders.go b/internal/services/orderservice/internal/orders/features/getting_orders/v1/queries/get_orders.go new file mode 100644 index 00000000..e94e5d7c --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_orders/v1/queries/get_orders.go @@ -0,0 +1,13 @@ +package queries + +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + +// Ref: https://golangbot.com/inheritance/ + +type GetOrders struct { + *utils.ListQuery +} + +func NewGetOrders(query *utils.ListQuery) *GetOrders { + return &GetOrders{ListQuery: query} +} diff --git a/internal/services/orderservice/internal/orders/features/getting_orders/v1/queries/get_orders_handler.go b/internal/services/orderservice/internal/orders/features/getting_orders/v1/queries/get_orders_handler.go new file mode 100644 index 00000000..842e7b5c --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/getting_orders/v1/queries/get_orders_handler.go @@ -0,0 +1,68 @@ +package queries + +import ( + "context" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + utils2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos" +) + +type GetOrdersHandler struct { + log logger.Logger + mongoOrderReadRepository repositories.OrderMongoRepository + tracer tracing.AppTracer +} + +func NewGetOrdersHandler( + log logger.Logger, + mongoOrderReadRepository repositories.OrderMongoRepository, + tracer tracing.AppTracer, +) *GetOrdersHandler { + return &GetOrdersHandler{ + log: log, + mongoOrderReadRepository: mongoOrderReadRepository, + tracer: tracer, + } +} + +func (c *GetOrdersHandler) Handle( + ctx context.Context, + query *GetOrders, +) (*dtos.GetOrdersResponseDto, error) { + ctx, span := c.tracer.Start(ctx, "GetOrdersHandler.Handle") + span.SetAttributes(attribute.Object("Query", query)) + defer span.End() + + products, err := c.mongoOrderReadRepository.GetAllOrders(ctx, query.ListQuery) + if err != nil { + return nil, utils2.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + "[GetOrdersHandler_Handle.GetAllOrders] error in getting orders in the repository", + ), + ) + } + + listResultDto, err := utils.ListResultToListResultDto[*dtosV1.OrderReadDto](products) + if err != nil { + return nil, utils2.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + "[GetOrdersHandler_Handle.ListResultToListResultDto] error in the mapping ListResultToListResultDto", + ), + ) + } + + c.log.Info("[GetOrdersHandler.Handle] orders fetched") + + return &dtos.GetOrdersResponseDto{Orders: listResultDto}, nil +} diff --git a/internal/services/orderservice/internal/orders/features/submitting_order/v1/commands/submit_order.go b/internal/services/orderservice/internal/orders/features/submitting_order/v1/commands/submit_order.go new file mode 100644 index 00000000..6e6e7157 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/submitting_order/v1/commands/submit_order.go @@ -0,0 +1,13 @@ +package commands + +import ( + uuid "github.com/satori/go.uuid" +) + +type SubmitOrder struct { + OrderId uuid.UUID `validate:"required"` +} + +func NewSubmitOrder(orderId uuid.UUID) *SubmitOrder { + return &SubmitOrder{OrderId: orderId} +} diff --git a/internal/services/orderservice/internal/orders/features/submitting_order/v1/events/domain_events/order_submitted.go b/internal/services/orderservice/internal/orders/features/submitting_order/v1/events/domain_events/order_submitted.go new file mode 100644 index 00000000..0dd17090 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/submitting_order/v1/events/domain_events/order_submitted.go @@ -0,0 +1,23 @@ +package domainEvents + +import ( + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + + uuid "github.com/satori/go.uuid" +) + +type OrderSubmittedV1 struct { + OrderId uuid.UUID `json:"orderId" bson:"orderId,omitempty"` +} + +func NewSubmitOrderV1(orderId uuid.UUID) (*OrderSubmittedV1, error) { + if orderId == uuid.Nil { + return nil, customErrors.NewDomainError(fmt.Sprintf("orderId {%s} is invalid", orderId)) + } + + event := OrderSubmittedV1{OrderId: orderId} + + return &event, nil +} diff --git a/internal/services/orderservice/internal/orders/features/updating_shopping_card/v1/commands/update_shopping_card.go b/internal/services/orderservice/internal/orders/features/updating_shopping_card/v1/commands/update_shopping_card.go new file mode 100644 index 00000000..dda69cd4 --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/updating_shopping_card/v1/commands/update_shopping_card.go @@ -0,0 +1,16 @@ +package commands + +import ( + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + + uuid "github.com/satori/go.uuid" +) + +type UpdateShoppingCart struct { + OrderId uuid.UUID `validate:"required"` + ShopItems []*dtosV1.ShopItemDto `validate:"required"` +} + +func NewUpdateShoppingCart(orderId uuid.UUID, shopItems []*dtosV1.ShopItemDto) *UpdateShoppingCart { + return &UpdateShoppingCart{OrderId: orderId, ShopItems: shopItems} +} diff --git a/internal/services/orderservice/internal/orders/features/updating_shopping_card/v1/events/shopping_card_updated.go b/internal/services/orderservice/internal/orders/features/updating_shopping_card/v1/events/shopping_card_updated.go new file mode 100644 index 00000000..7e81679d --- /dev/null +++ b/internal/services/orderservice/internal/orders/features/updating_shopping_card/v1/events/shopping_card_updated.go @@ -0,0 +1,21 @@ +package domainEvent + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/value_objects" +) + +type ShoppingCartUpdatedV1 struct { + *domain.DomainEvent + ShopItems []*value_objects.ShopItem `json:"shopItems" bson:"shopItems,omitempty"` +} + +func NewShoppingCartUpdatedV1(shopItems []*value_objects.ShopItem) (*ShoppingCartUpdatedV1, error) { + //if shopItems == nil { + // return nil, domainExceptions.ErrOrderShopItemsIsRequired + //} + + eventData := ShoppingCartUpdatedV1{ShopItems: shopItems} + + return &eventData, nil +} diff --git a/internal/services/orderservice/internal/orders/models/orders/aggregate/order.go b/internal/services/orderservice/internal/orders/models/orders/aggregate/order.go new file mode 100644 index 00000000..3391d25a --- /dev/null +++ b/internal/services/orderservice/internal/orders/models/orders/aggregate/order.go @@ -0,0 +1,199 @@ +package aggregate + +// https://www.eventstore.com/blog/what-is-event-sourcing + +import ( + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/domain" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/errors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + typeMapper "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/reflection/typemapper" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + domainExceptions "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/exceptions/domain_exceptions" + createOrderDomainEventsV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/events/domain_events" + updateOrderDomainEventsV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/updating_shopping_card/v1/events" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/value_objects" + + "github.com/goccy/go-json" + uuid "github.com/satori/go.uuid" +) + +type Order struct { + *models.EventSourcedAggregateRoot + shopItems []*value_objects.ShopItem + accountEmail string + deliveryAddress string + cancelReason string + totalPrice float64 + deliveredTime time.Time + paid bool + submitted bool + completed bool + canceled bool + paymentId uuid.UUID + createdAt time.Time + updatedAt time.Time +} + +func (o *Order) NewEmptyAggregate() { + // http://arch-stable.blogspot.com/2012/05/golang-call-inherited-constructor.html + base := models.NewEventSourcedAggregateRoot(typeMapper.GetFullTypeName(o), o.When) + o.EventSourcedAggregateRoot = base +} + +func NewOrder( + id uuid.UUID, + shopItems []*value_objects.ShopItem, + accountEmail, deliveryAddress string, + deliveredTime time.Time, + createdAt time.Time, +) (*Order, error) { + order := &Order{} + order.NewEmptyAggregate() + order.SetId(id) + + if shopItems == nil || len(shopItems) == 0 { + return nil, domainExceptions.NewOrderShopItemsRequiredError( + "[Order_NewOrder] order items is required", + ) + } + + itemsDto, err := mapper.Map[[]*dtosV1.ShopItemDto](shopItems) + if err != nil { + return nil, customErrors.NewDomainErrorWrap( + err, + "[Order_NewOrder.Map] error in the mapping []ShopItems to []ShopItemsDto", + ) + } + + event, err := createOrderDomainEventsV1.NewOrderCreatedEventV1( + id, + itemsDto, + accountEmail, + deliveryAddress, + deliveredTime, + createdAt, + ) + if err != nil { + return nil, customErrors.NewDomainErrorWrap( + err, + "[Order_NewOrder.NewOrderCreatedEventV1] error in creating order created event", + ) + } + + err = order.Apply(event, true) + if err != nil { + return nil, customErrors.NewDomainErrorWrap( + err, + "[Order_NewOrder.Apply] error in applying created event", + ) + } + + return order, nil +} + +func (o *Order) UpdateShoppingCard(shopItems []*value_objects.ShopItem) error { + event, err := updateOrderDomainEventsV1.NewShoppingCartUpdatedV1(shopItems) + if err != nil { + return err + } + + err = o.Apply(event, true) + if err != nil { + return err + } + + return nil +} + +func (o *Order) When(event domain.IDomainEvent) error { + switch evt := event.(type) { + + case *createOrderDomainEventsV1.OrderCreatedV1: + return o.onOrderCreated(evt) + + default: + return errors.InvalidEventTypeError + } +} + +func (o *Order) onOrderCreated(evt *createOrderDomainEventsV1.OrderCreatedV1) error { + items, err := mapper.Map[[]*value_objects.ShopItem](evt.ShopItems) + if err != nil { + return err + } + + o.accountEmail = evt.AccountEmail + o.shopItems = items + o.deliveryAddress = evt.DeliveryAddress + o.deliveredTime = evt.DeliveredTime + o.createdAt = evt.CreatedAt + o.SetId(evt.GetAggregateId()) // o.SetId(evt.Id) + + return nil +} + +func (o *Order) ShopItems() []*value_objects.ShopItem { + return o.shopItems +} + +func (o *Order) PaymentId() uuid.UUID { + return o.paymentId +} + +func (o *Order) AccountEmail() string { + return o.accountEmail +} + +func (o *Order) DeliveryAddress() string { + return o.deliveryAddress +} + +func (o *Order) DeliveredTime() time.Time { + return o.deliveredTime +} + +func (o *Order) CreatedAt() time.Time { + return o.createdAt +} + +func (o *Order) TotalPrice() float64 { + return getShopItemsTotalPrice(o.shopItems) +} + +func (o *Order) Paid() bool { + return o.paid +} + +func (o *Order) Submitted() bool { + return o.submitted +} + +func (o *Order) Completed() bool { + return o.completed +} + +func (o *Order) Canceled() bool { + return o.canceled +} + +func (o *Order) CancelReason() string { + return o.cancelReason +} + +func (o *Order) String() string { + j, _ := json.Marshal(o) + return string(j) +} + +func getShopItemsTotalPrice(shopItems []*value_objects.ShopItem) float64 { + var totalPrice float64 = 0 + for _, item := range shopItems { + totalPrice += item.Price() * float64(item.Quantity()) + } + + return totalPrice +} diff --git a/internal/services/orderservice/internal/orders/models/orders/read_models/order_read.go b/internal/services/orderservice/internal/orders/models/orders/read_models/order_read.go new file mode 100644 index 00000000..2b56a575 --- /dev/null +++ b/internal/services/orderservice/internal/orders/models/orders/read_models/order_read.go @@ -0,0 +1,56 @@ +package read_models + +import ( + "time" + + uuid "github.com/satori/go.uuid" +) + +type OrderReadModel struct { + // we generate id ourself because auto generate mongo string id column with type _id is not an uuid + Id string `json:"id" bson:"_id,omitempty"` // https://www.mongodb.com/docs/drivers/go/current/fundamentals/crud/write-operations/insert/#the-_id-field + OrderId string `json:"orderId" bson:"orderId,omitempty"` + ShopItems []*ShopItemReadModel `json:"shopItems,omitempty" bson:"shopItems,omitempty"` + AccountEmail string `json:"accountEmail,omitempty" bson:"accountEmail,omitempty"` + DeliveryAddress string `json:"deliveryAddress,omitempty" bson:"deliveryAddress,omitempty"` + CancelReason string `json:"cancelReason,omitempty" bson:"cancelReason,omitempty"` + TotalPrice float64 `json:"totalPrice,omitempty" bson:"totalPrice,omitempty"` + DeliveredTime time.Time `json:"deliveredTime,omitempty" bson:"deliveredTime,omitempty"` + Paid bool `json:"paid,omitempty" bson:"paid,omitempty"` + Submitted bool `json:"submitted,omitempty" bson:"submitted,omitempty"` + Completed bool `json:"completed,omitempty" bson:"completed,omitempty"` + Canceled bool `json:"canceled,omitempty" bson:"canceled,omitempty"` + PaymentId string `json:"paymentId" bson:"paymentId,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty" bson:"updatedAt,omitempty"` +} + +func NewOrderReadModel( + orderId uuid.UUID, + items []*ShopItemReadModel, + accountEmail string, + deliveryAddress string, + deliveryTime time.Time, +) *OrderReadModel { + return &OrderReadModel{ + Id: uuid.NewV4(). + String(), + // we generate id ourself because auto generate mongo string id column with type _id is not an uuid + OrderId: orderId.String(), + ShopItems: items, + AccountEmail: accountEmail, + DeliveryAddress: deliveryAddress, + TotalPrice: getShopItemsTotalPrice(items), + DeliveredTime: deliveryTime, + CreatedAt: time.Now(), + } +} + +func getShopItemsTotalPrice(shopItems []*ShopItemReadModel) float64 { + var totalPrice float64 = 0 + for _, item := range shopItems { + totalPrice += item.Price * float64(item.Quantity) + } + + return totalPrice +} diff --git a/internal/services/orderservice/internal/orders/models/orders/read_models/shop_item_read.go b/internal/services/orderservice/internal/orders/models/orders/read_models/shop_item_read.go new file mode 100644 index 00000000..1027b093 --- /dev/null +++ b/internal/services/orderservice/internal/orders/models/orders/read_models/shop_item_read.go @@ -0,0 +1,12 @@ +package read_models + +type ShopItemReadModel struct { + Title string `json:"title,omitempty" bson:"title,omitempty"` + Description string `json:"description,omitempty" bson:"description,omitempty"` + Quantity uint64 `json:"quantity,omitempty" bson:"quantity,omitempty"` + Price float64 `json:"price,omitempty" bson:"price,omitempty"` +} + +func NewShopItemReadModel(title string, description string, quantity uint64, price float64) *ShopItemReadModel { + return &ShopItemReadModel{Title: title, Description: description, Quantity: quantity, Price: price} +} diff --git a/internal/services/orderservice/internal/orders/models/orders/value_objects/shop_item.go b/internal/services/orderservice/internal/orders/models/orders/value_objects/shop_item.go new file mode 100644 index 00000000..76aeda83 --- /dev/null +++ b/internal/services/orderservice/internal/orders/models/orders/value_objects/shop_item.go @@ -0,0 +1,46 @@ +package value_objects + +import ( + "fmt" +) + +type ShopItem struct { + title string + description string + quantity uint64 + price float64 +} + +func CreateNewShopItem(title string, description string, quantity uint64, price float64) *ShopItem { + return &ShopItem{ + title: title, + description: description, + quantity: quantity, + price: price, + } +} + +func (s *ShopItem) Title() string { + return s.title +} + +func (s *ShopItem) Description() string { + return s.description +} + +func (s *ShopItem) Quantity() uint64 { + return s.quantity +} + +func (s *ShopItem) Price() float64 { + return s.price +} + +func (s *ShopItem) String() string { + return fmt.Sprintf("Title: {%s}, Description: {%s}, Quantity: {%v}, Price: {%v},", + s.title, + s.description, + s.quantity, + s.price, + ) +} diff --git a/internal/services/orderservice/internal/orders/orders_fx.go b/internal/services/orderservice/internal/orders/orders_fx.go new file mode 100644 index 00000000..bb4f4b58 --- /dev/null +++ b/internal/services/orderservice/internal/orders/orders_fx.go @@ -0,0 +1,47 @@ +package orders + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/web/route" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/data/repositories" + createOrderV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/endpoints" + getOrderByIdV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/endpoints" + getOrdersV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_orders/v1/endpoints" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/aggregate" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/projections" + + "github.com/labstack/echo/v4" + "go.uber.org/fx" +) + +var Module = fx.Module( + "ordersfx", + + // Other provides + fx.Provide(fx.Annotate(repositories.NewMongoOrderReadRepository)), + fx.Provide(repositories.NewElasticOrderReadRepository), + + fx.Provide(eventstroredb.NewEventStoreAggregateStore[*aggregate.Order]), + fx.Provide(fx.Annotate(func(catalogsServer customEcho.EchoHttpServer) *echo.Group { + var g *echo.Group + catalogsServer.RouteBuilder().RegisterGroupFunc("/api/v1", func(v1 *echo.Group) { + group := v1.Group("/orders") + g = group + }) + + return g + }, fx.ResultTags(`name:"order-echo-group"`))), + + fx.Provide( + route.AsRoute(createOrderV1.NewCreteOrderEndpoint, "order-routes"), + route.AsRoute(getOrderByIdV1.NewGetOrderByIdEndpoint, "order-routes"), + route.AsRoute(getOrdersV1.NewGetOrdersEndpoint, "order-routes"), + ), + + fx.Provide( + es.AsProjection(projections.NewElasticOrderProjection), + es.AsProjection(projections.NewMongoOrderProjection), + ), +) diff --git a/internal/services/orderservice/internal/orders/projections/elastic_order_projection.go b/internal/services/orderservice/internal/orders/projections/elastic_order_projection.go new file mode 100644 index 00000000..96de831c --- /dev/null +++ b/internal/services/orderservice/internal/orders/projections/elastic_order_projection.go @@ -0,0 +1,27 @@ +package projections + +import ( + "context" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/projection" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" +) + +type elasticOrderProjection struct { + elasticOrderReadRepository repositories.OrderElasticRepository +} + +func NewElasticOrderProjection( + elasticOrderReadRepository repositories.OrderElasticRepository, +) projection.IProjection { + return &elasticOrderProjection{elasticOrderReadRepository: elasticOrderReadRepository} +} + +func (e elasticOrderProjection) ProcessEvent( + ctx context.Context, + streamEvent *models.StreamEvent, +) error { + // TODO: Handling and projecting event to elastic read model + return nil +} diff --git a/internal/services/orderservice/internal/orders/projections/mongo_order_projection.go b/internal/services/orderservice/internal/orders/projections/mongo_order_projection.go new file mode 100644 index 00000000..5bf0870c --- /dev/null +++ b/internal/services/orderservice/internal/orders/projections/mongo_order_projection.go @@ -0,0 +1,136 @@ +package projections + +import ( + "context" + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/producer" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/projection" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/models" + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + createOrderDomainEventsV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/events/domain_events" + createOrderIntegrationEventsV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/events/integration_events" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + + "emperror.dev/errors" + attribute2 "go.opentelemetry.io/otel/attribute" +) + +type mongoOrderProjection struct { + mongoOrderRepository repositories.OrderMongoRepository + rabbitmqProducer producer.Producer + logger logger.Logger + tracer tracing.AppTracer +} + +func NewMongoOrderProjection( + mongoOrderRepository repositories.OrderMongoRepository, + rabbitmqProducer producer.Producer, + logger logger.Logger, + tracer tracing.AppTracer, +) projection.IProjection { + return &mongoOrderProjection{ + mongoOrderRepository: mongoOrderRepository, + rabbitmqProducer: rabbitmqProducer, + logger: logger, + tracer: tracer, + } +} + +func (m mongoOrderProjection) ProcessEvent( + ctx context.Context, + streamEvent *models.StreamEvent, +) error { + // Handling and projecting event to elastic read model + switch evt := streamEvent.Event.(type) { + case *createOrderDomainEventsV1.OrderCreatedV1: + return m.onOrderCreated(ctx, evt) + } + + return nil +} + +func (m *mongoOrderProjection) onOrderCreated( + ctx context.Context, + evt *createOrderDomainEventsV1.OrderCreatedV1, +) error { + ctx, span := m.tracer.Start(ctx, "mongoOrderProjection.onOrderCreated") + span.SetAttributes(attribute.Object("Event", evt)) + span.SetAttributes(attribute2.String("OrderId", evt.OrderId.String())) + defer span.End() + + items, err := mapper.Map[[]*read_models.ShopItemReadModel](evt.ShopItems) + if err != nil { + return errors.WrapIf( + err, + "[mongoOrderProjection_onOrderCreated.Map] error in mapping shopItems", + ) + } + + orderRead := read_models.NewOrderReadModel( + evt.OrderId, + items, + evt.AccountEmail, + evt.DeliveryAddress, + evt.DeliveredTime, + ) + _, err = m.mongoOrderRepository.CreateOrder(ctx, orderRead) + if err != nil { + return utils.TraceErrFromSpan( + span, + errors.WrapIf( + err, + "[mongoOrderProjection_onOrderCreated.CreateOrder] error in creating order with mongoOrderRepository", + ), + ) + } + + orderReadDto, err := mapper.Map[*dtosV1.OrderReadDto](orderRead) + if err != nil { + return utils.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + "[mongoOrderProjection_onOrderCreated.Map] error in mapping OrderReadDto", + ), + ) + } + + orderCreatedEvent := createOrderIntegrationEventsV1.NewOrderCreatedV1(orderReadDto) + + err = m.rabbitmqProducer.PublishMessage(ctx, orderCreatedEvent, nil) + if err != nil { + return utils.TraceErrFromSpan( + span, + customErrors.NewApplicationErrorWrap( + err, + "[mongoOrderProjection_onOrderCreated.PublishMessage] error in publishing OrderCreated integration_events event", + ), + ) + } + + m.logger.Infow( + fmt.Sprintf( + "[mongoOrderProjection.onOrderCreated] OrderCreated message with messageId `%s` published to the rabbitmq broker", + orderCreatedEvent.MessageId, + ), + logger.Fields{"MessageId": orderCreatedEvent.MessageId, "Id": orderCreatedEvent.OrderId}, + ) + + m.logger.Infow( + fmt.Sprintf( + "[mongoOrderProjection.onOrderCreated] order with id '%s' created", + orderCreatedEvent.Id, + ), + logger.Fields{"Id": orderRead.Id, "MessageId": orderCreatedEvent.MessageId}, + ) + + return nil +} diff --git a/internal/services/orderservice/internal/shared/app/app.go b/internal/services/orderservice/internal/shared/app/app.go new file mode 100644 index 00000000..fb9d7493 --- /dev/null +++ b/internal/services/orderservice/internal/shared/app/app.go @@ -0,0 +1,25 @@ +package app + +import "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/configurations/orders" + +type App struct{} + +func NewApp() *App { + return &App{} +} + +func (a *App) Run() { + // configure dependencies + appBuilder := NewOrdersApplicationBuilder() + appBuilder.ProvideModule(orders.OrderServiceModule) + + app := appBuilder.Build() + + // configure application + app.ConfigureOrders() + + app.MapOrdersEndpoints() + + app.Logger().Info("Starting orders_service application") + app.Run() +} diff --git a/internal/services/orderservice/internal/shared/app/application.go b/internal/services/orderservice/internal/shared/app/application.go new file mode 100644 index 00000000..6617e980 --- /dev/null +++ b/internal/services/orderservice/internal/shared/app/application.go @@ -0,0 +1,27 @@ +package app + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/configurations/orders" + + "go.uber.org/fx" +) + +type OrdersApplication struct { + *orders.OrdersServiceConfigurator +} + +func NewOrdersApplication( + providers []interface{}, + decorates []interface{}, + options []fx.Option, + logger logger.Logger, + environment environment.Environment, +) *OrdersApplication { + app := fxapp.NewApplication(providers, decorates, options, logger, environment) + return &OrdersApplication{ + OrdersServiceConfigurator: orders.NewOrdersServiceConfigurator(app), + } +} diff --git a/internal/services/orderservice/internal/shared/app/application_builder.go b/internal/services/orderservice/internal/shared/app/application_builder.go new file mode 100644 index 00000000..b0a9432a --- /dev/null +++ b/internal/services/orderservice/internal/shared/app/application_builder.go @@ -0,0 +1,24 @@ +package app + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" +) + +type OrdersApplicationBuilder struct { + contracts.ApplicationBuilder +} + +func NewOrdersApplicationBuilder() *OrdersApplicationBuilder { + return &OrdersApplicationBuilder{fxapp.NewApplicationBuilder()} +} + +func (a *OrdersApplicationBuilder) Build() *OrdersApplication { + return NewOrdersApplication( + a.GetProvides(), + a.GetDecorates(), + a.Options(), + a.Logger(), + a.Environment(), + ) +} diff --git a/internal/services/orderservice/internal/shared/app/test/test_app.go b/internal/services/orderservice/internal/shared/app/test/test_app.go new file mode 100644 index 00000000..597b9727 --- /dev/null +++ b/internal/services/orderservice/internal/shared/app/test/test_app.go @@ -0,0 +1,136 @@ +package test + +import ( + "context" + "os" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/store" + config4 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc" + config3 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/bus" + config2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/eventstoredb" + mongo2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/mongo" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/containers/testcontainer/redis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/aggregate" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/configurations/orders" + ordersService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/grpc/genproto" + + "github.com/EventStore/EventStore-Client-Go/esdb" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/mongo" +) + +type TestApp struct{} + +type TestAppResult struct { + Cfg *config.Config + Bus bus.RabbitmqBus + Container contracts.Container + Logger logger.Logger + RabbitmqOptions *config2.RabbitmqOptions + EchoHttpOptions *config3.EchoHttpOptions + EventStoreDbOptions *config4.EventStoreDbOptions + OrderMongoRepository repositories.OrderMongoRepository + OrderAggregateStore store.AggregateStore[*aggregate.Order] + OrdersServiceClient ordersService.OrdersServiceClient + MongoClient *mongo.Client + EsdbClient *esdb.Client + MongoDbOptions *mongodb.MongoDbOptions + GrpcClient grpc.GrpcClient +} + +func NewTestApp() *TestApp { + return &TestApp{} +} + +func (a *TestApp) Run(t *testing.T) (result *TestAppResult) { + lifetimeCtx := context.Background() + + // ref: https://github.com/uber-go/fx/blob/master/app_test.go + appBuilder := NewOrdersTestApplicationBuilder(t) + appBuilder.ProvideModule(orders.OrderServiceModule) + + appBuilder.Decorate(rabbitmq.RabbitmqContainerOptionsDecorator(t, lifetimeCtx)) + appBuilder.Decorate(eventstoredb.EventstoreDBContainerOptionsDecorator(t, lifetimeCtx)) + appBuilder.Decorate(mongo2.MongoContainerOptionsDecorator(t, lifetimeCtx)) + appBuilder.Decorate(redis.RedisContainerOptionsDecorator(t, lifetimeCtx)) + + testApp := appBuilder.Build() + + testApp.ConfigureOrders() + + testApp.MapOrdersEndpoints() + + testApp.ResolveFunc( + func( + cfg *config.Config, + bus bus.RabbitmqBus, + logger logger.Logger, + rabbitmqOptions *config2.RabbitmqOptions, + echoOptions *config3.EchoHttpOptions, + grpcClient grpc.GrpcClient, + eventStoreDbOptions *config4.EventStoreDbOptions, + orderMongoRepository repositories.OrderMongoRepository, + orderAggregateStore store.AggregateStore[*aggregate.Order], + mongoClient *mongo.Client, + esdbClient *esdb.Client, + mongoDbOptions *mongodb.MongoDbOptions, + ) { + result = &TestAppResult{ + Bus: bus, + Cfg: cfg, + Container: testApp, + Logger: logger, + RabbitmqOptions: rabbitmqOptions, + MongoClient: mongoClient, + MongoDbOptions: mongoDbOptions, + EchoHttpOptions: echoOptions, + EsdbClient: esdbClient, + EventStoreDbOptions: eventStoreDbOptions, + OrderMongoRepository: orderMongoRepository, + OrderAggregateStore: orderAggregateStore, + OrdersServiceClient: ordersService.NewOrdersServiceClient( + grpcClient.GetGrpcConnection(), + ), + GrpcClient: grpcClient, + } + }, + ) + + // we need a longer timout for up and running our testcontainers + duration := time.Second * 300 + + // short timeout for handling start hooks and setup dependencies + startCtx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + err := testApp.Start(startCtx) + if err != nil { + t.Errorf("Error starting, err: %v", err) + os.Exit(1) + } + + //// waiting for grpc endpoint becomes ready in the given timeout + //err = result.GrpcClient.WaitForAvailableConnection() + //require.NoError(t, err) + + t.Cleanup(func() { + // short timeout for handling stop hooks + stopCtx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + err = testApp.Stop(stopCtx) + require.NoError(t, err) + }) + + return +} diff --git a/internal/services/orderservice/internal/shared/app/test/test_application.go b/internal/services/orderservice/internal/shared/app/test/test_application.go new file mode 100644 index 00000000..a3570164 --- /dev/null +++ b/internal/services/orderservice/internal/shared/app/test/test_application.go @@ -0,0 +1,44 @@ +package test + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/config/environment" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/test" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/app" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/configurations/orders" + + "go.uber.org/fx" + "go.uber.org/fx/fxtest" +) + +type OrdersTestApplication struct { + *app.OrdersApplication + tb fxtest.TB +} + +func NewOrdersTestApplication( + tb fxtest.TB, + providers []interface{}, + decorates []interface{}, + options []fx.Option, + logger logger.Logger, + environment environment.Environment, +) *OrdersTestApplication { + testApp := test.NewTestApplication( + tb, + providers, + decorates, + options, + logger, + environment, + ) + + orderApplication := &app.OrdersApplication{ + OrdersServiceConfigurator: orders.NewOrdersServiceConfigurator(testApp), + } + + return &OrdersTestApplication{ + OrdersApplication: orderApplication, + tb: tb, + } +} diff --git a/internal/services/orderservice/internal/shared/app/test/test_application_builder.go b/internal/services/orderservice/internal/shared/app/test/test_application_builder.go new file mode 100644 index 00000000..ef90ed79 --- /dev/null +++ b/internal/services/orderservice/internal/shared/app/test/test_application_builder.go @@ -0,0 +1,31 @@ +package test + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/test" + + "go.uber.org/fx/fxtest" +) + +type OrdersTestApplicationBuilder struct { + contracts.ApplicationBuilder + tb fxtest.TB +} + +func NewOrdersTestApplicationBuilder(tb fxtest.TB) *OrdersTestApplicationBuilder { + return &OrdersTestApplicationBuilder{ + ApplicationBuilder: test.NewTestApplicationBuilder(tb), + tb: tb, + } +} + +func (a *OrdersTestApplicationBuilder) Build() *OrdersTestApplication { + return NewOrdersTestApplication( + a.tb, + a.GetProvides(), + a.GetDecorates(), + a.Options(), + a.Logger(), + a.Environment(), + ) +} diff --git a/internal/services/orderservice/internal/shared/configurations/orders/infrastructure/infrastructure_configurator.go b/internal/services/orderservice/internal/shared/configurations/orders/infrastructure/infrastructure_configurator.go new file mode 100644 index 00000000..74123c32 --- /dev/null +++ b/internal/services/orderservice/internal/shared/configurations/orders/infrastructure/infrastructure_configurator.go @@ -0,0 +1,44 @@ +package infrastructure + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics" + metricspipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics/mediatr/pipelines" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + tracingpipelines "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/mediatr/pipelines" + + "github.com/mehdihadeli/go-mediatr" +) + +type InfrastructureConfigurator struct { + contracts.Application +} + +func NewInfrastructureConfigurator( + app contracts.Application, +) *InfrastructureConfigurator { + return &InfrastructureConfigurator{ + Application: app, + } +} + +func (ic *InfrastructureConfigurator) ConfigInfrastructures() { + ic.ResolveFunc( + func(l logger.Logger, tracer tracing.AppTracer, metrics metrics.AppMetrics) error { + err := mediatr.RegisterRequestPipelineBehaviors( + loggingpipelines.NewMediatorLoggingPipeline(l), + tracingpipelines.NewMediatorTracingPipeline( + tracer, + tracingpipelines.WithLogger(l), + ), + metricspipelines.NewMediatorMetricsPipeline( + metrics, + metricspipelines.WithLogger(l), + ), + ) + + return err + }, + ) +} diff --git a/internal/services/orderservice/internal/shared/configurations/orders/infrastructure/infrastructure_fx.go b/internal/services/orderservice/internal/shared/configurations/orders/infrastructure/infrastructure_fx.go new file mode 100644 index 00000000..3fc57e7d --- /dev/null +++ b/internal/services/orderservice/internal/shared/configurations/orders/infrastructure/infrastructure_fx.go @@ -0,0 +1,52 @@ +package infrastructure + +import ( + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/elasticsearch" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/grpc" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/health" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/metrics" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/configurations" + rabbitmq2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/configurations/rabbitmq" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/params" + + "github.com/go-playground/validator" + "go.uber.org/fx" +) + +// https://pmihaylov.com/shared-components-go-microservices/ + +var Module = fx.Module( + "infrastructurefx", + // Modules + core.Module, + customEcho.Module, + grpc.Module, + mongodb.Module, + elasticsearch.Module, + eventstroredb.ModuleFunc( + func(params params.OrderProjectionParams) eventstroredb.ProjectionBuilderFuc { + return func(builder eventstroredb.ProjectionsBuilder) { + builder.AddProjections(params.Projections) + } + }, + ), + rabbitmq.ModuleFunc( + func() configurations.RabbitMQConfigurationBuilderFuc { + return func(builder configurations.RabbitMQConfigurationBuilder) { + rabbitmq2.ConfigOrdersRabbitMQ(builder) + } + }, + ), + health.Module, + tracing.Module, + metrics.Module, + + // Other provides + fx.Provide(validator.New), +) diff --git a/internal/services/orderservice/internal/shared/configurations/orders/orders_configurator.go b/internal/services/orderservice/internal/shared/configurations/orders/orders_configurator.go new file mode 100644 index 00000000..b822ed93 --- /dev/null +++ b/internal/services/orderservice/internal/shared/configurations/orders/orders_configurator.go @@ -0,0 +1,78 @@ +package orders + +import ( + "fmt" + "net/http" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/configurations" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/configurations/orders/infrastructure" + + "github.com/labstack/echo/v4" +) + +type OrdersServiceConfigurator struct { + contracts.Application + infrastructureConfigurator *infrastructure.InfrastructureConfigurator + ordersModuleConfigurator *configurations.OrdersModuleConfigurator +} + +func NewOrdersServiceConfigurator( + app contracts.Application, +) *OrdersServiceConfigurator { + infraConfigurator := infrastructure.NewInfrastructureConfigurator(app) + ordersModuleConfigurator := configurations.NewOrdersModuleConfigurator(app) + + return &OrdersServiceConfigurator{ + Application: app, + infrastructureConfigurator: infraConfigurator, + ordersModuleConfigurator: ordersModuleConfigurator, + } +} + +func (ic *OrdersServiceConfigurator) ConfigureOrders() { + // Shared + // Infrastructure + ic.infrastructureConfigurator.ConfigInfrastructures() + + // Shared + // Orders service configurations + + // Modules + // Order module + ic.ordersModuleConfigurator.ConfigureOrdersModule() +} + +func (ic *OrdersServiceConfigurator) MapOrdersEndpoints() { + // Shared + ic.ResolveFunc( + func(ordersServer customEcho.EchoHttpServer, cfg *config.Config) error { + ordersServer.SetupDefaultMiddlewares() + + // config orders root endpoint + ordersServer.RouteBuilder(). + RegisterRoutes(func(e *echo.Echo) { + e.GET("", func(ec echo.Context) error { + return ec.String( + http.StatusOK, + fmt.Sprintf( + "%s is running...", + cfg.AppOptions.GetMicroserviceNameUpper(), + ), + ) + }) + }) + + // config orders swagger + ic.configSwagger(ordersServer.RouteBuilder()) + + return nil + }, + ) + + // Modules + // Orders Module endpoints + ic.ordersModuleConfigurator.MapOrdersEndpoints() +} diff --git a/internal/services/orderservice/internal/shared/configurations/orders/orders_configurator_swagger.go b/internal/services/orderservice/internal/shared/configurations/orders/orders_configurator_swagger.go new file mode 100644 index 00000000..05881b1d --- /dev/null +++ b/internal/services/orderservice/internal/shared/configurations/orders/orders_configurator_swagger.go @@ -0,0 +1,19 @@ +package orders + +import ( + customEcho "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/customecho/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/docs" + + "github.com/labstack/echo/v4" + echoSwagger "github.com/swaggo/echo-swagger" +) + +func (ic *OrdersServiceConfigurator) configSwagger(routeBuilder *customEcho.RouteBuilder) { + docs.SwaggerInfo.Version = "1.0" + docs.SwaggerInfo.Title = "Orders Service Api" + docs.SwaggerInfo.Description = "Orders Service Api." + + routeBuilder.RegisterRoutes(func(e *echo.Echo) { + e.GET("/swagger/*", echoSwagger.WrapHandler) + }) +} diff --git a/internal/services/orderservice/internal/shared/configurations/orders/orders_fx.go b/internal/services/orderservice/internal/shared/configurations/orders/orders_fx.go new file mode 100644 index 00000000..c4e7a50f --- /dev/null +++ b/internal/services/orderservice/internal/shared/configurations/orders/orders_fx.go @@ -0,0 +1,215 @@ +package orders + +import ( + "fmt" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/configurations/orders/infrastructure" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/contracts" + + "go.opentelemetry.io/otel/metric" + api "go.opentelemetry.io/otel/metric" + "go.uber.org/fx" +) + +// https://pmihaylov.com/shared-components-go-microservices/ + +var OrderServiceModule = fx.Module( + "ordersfx", + // Shared Modules + config.Module, + infrastructure.Module, + + // Features Modules + orders.Module, + + // Other provides + fx.Provide(configOrdersMetrics), +) + +// ref: https://github.com/open-telemetry/opentelemetry-go/blob/main/example/prometheus/main.go + +func configOrdersMetrics( + cfg *config.Config, + meter metric.Meter, +) (*contracts.OrdersMetrics, error) { + if meter == nil { + return nil, nil + } + + appOptions := cfg.AppOptions + successGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_success_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of success grpc requests"), + ) + if err != nil { + return nil, err + } + + errorGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_error_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of error grpc requests"), + ) + if err != nil { + return nil, err + } + + createOrderGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_create_order_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of create order grpc requests"), + ) + if err != nil { + return nil, err + } + + updateOrderGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_update_order_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of update order grpc requests"), + ) + if err != nil { + return nil, err + } + + payOrderGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_pay_order_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of pay order grpc requests"), + ) + if err != nil { + return nil, err + } + + submitOrderGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_submit_order_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of submit order grpc requests"), + ) + if err != nil { + return nil, err + } + + getOrderByIdGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_get_order_by_id_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of get order by id grpc requests"), + ) + if err != nil { + return nil, err + } + + getOrdersGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_get_orders_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of get orders grpc requests"), + ) + if err != nil { + return nil, err + } + + searchOrderGrpcRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_search_order_grpc_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of search order grpc requests"), + ) + if err != nil { + return nil, err + } + + getOrdersHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_get_orders_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of get orders http requests"), + ) + if err != nil { + return nil, err + } + + createOrderHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_create_order_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of create order http requests"), + ) + if err != nil { + return nil, err + } + + updateOrderHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_update_order_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of update order http requests"), + ) + if err != nil { + return nil, err + } + + payOrderHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_pay_order_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of pay order http requests"), + ) + if err != nil { + return nil, err + } + + submitOrderHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_submit_order_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of submit order http requests"), + ) + if err != nil { + return nil, err + } + + getOrderByIdHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_get_order_by_id_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of get order by id http requests"), + ) + if err != nil { + return nil, err + } + + searchOrderHttpRequests, err := meter.Float64Counter( + fmt.Sprintf("%s_search_order_http_requests_total", appOptions.ServiceName), + api.WithDescription("The total number of search order http requests"), + ) + if err != nil { + return nil, err + } + + deleteOrderRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf("%s_delete_order_rabbitmq_messages_total", appOptions.ServiceName), + api.WithDescription("The total number of delete order rabbirmq messages"), + ) + if err != nil { + return nil, err + } + + createOrderRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf("%s_create_order_rabbitmq_messages_total", appOptions.ServiceName), + api.WithDescription("The total number of create order rabbirmq messages"), + ) + if err != nil { + return nil, err + } + + updateOrderRabbitMQMessages, err := meter.Float64Counter( + fmt.Sprintf("%s_update_order_rabbitmq_messages_total", appOptions.ServiceName), + api.WithDescription("The total number of update order rabbirmq messages"), + ) + if err != nil { + return nil, err + } + + return &contracts.OrdersMetrics{ + CreateOrderHttpRequests: createOrderHttpRequests, + SuccessGrpcRequests: successGrpcRequests, + ErrorGrpcRequests: errorGrpcRequests, + CreateOrderGrpcRequests: createOrderGrpcRequests, + UpdateOrderGrpcRequests: updateOrderGrpcRequests, + PayOrderGrpcRequests: payOrderGrpcRequests, + SubmitOrderGrpcRequests: submitOrderGrpcRequests, + GetOrderByIdGrpcRequests: getOrderByIdGrpcRequests, + GetOrdersGrpcRequests: getOrdersGrpcRequests, + SearchOrderGrpcRequests: searchOrderGrpcRequests, + GetOrdersHttpRequests: getOrdersHttpRequests, + UpdateOrderHttpRequests: updateOrderHttpRequests, + PayOrderHttpRequests: payOrderHttpRequests, + SubmitOrderHttpRequests: submitOrderHttpRequests, + GetOrderByIdHttpRequests: getOrderByIdHttpRequests, + SearchOrderHttpRequests: searchOrderHttpRequests, + DeleteOrderRabbitMQMessages: deleteOrderRabbitMQMessages, + CreateOrderRabbitMQMessages: createOrderRabbitMQMessages, + UpdateOrderRabbitMQMessages: updateOrderRabbitMQMessages, + }, nil +} diff --git a/internal/services/orderservice/internal/shared/contracts/orders_metrics.go b/internal/services/orderservice/internal/shared/contracts/orders_metrics.go new file mode 100644 index 00000000..86541422 --- /dev/null +++ b/internal/services/orderservice/internal/shared/contracts/orders_metrics.go @@ -0,0 +1,36 @@ +package contracts + +import ( + "go.opentelemetry.io/otel/metric" +) + +type OrdersMetrics struct { + SuccessGrpcRequests metric.Float64Counter + ErrorGrpcRequests metric.Float64Counter + + CreateOrderGrpcRequests metric.Float64Counter + UpdateOrderGrpcRequests metric.Float64Counter + PayOrderGrpcRequests metric.Float64Counter + SubmitOrderGrpcRequests metric.Float64Counter + GetOrderByIdGrpcRequests metric.Float64Counter + GetOrdersGrpcRequests metric.Float64Counter + SearchOrderGrpcRequests metric.Float64Counter + + SuccessHttpRequests metric.Float64Counter + ErrorHttpRequests metric.Float64Counter + + CreateOrderHttpRequests metric.Float64Counter + UpdateOrderHttpRequests metric.Float64Counter + PayOrderHttpRequests metric.Float64Counter + SubmitOrderHttpRequests metric.Float64Counter + GetOrderByIdHttpRequests metric.Float64Counter + SearchOrderHttpRequests metric.Float64Counter + GetOrdersHttpRequests metric.Float64Counter + + SuccessRabbitMQMessages metric.Float64Counter + ErrorRabbitMQMessages metric.Float64Counter + + CreateOrderRabbitMQMessages metric.Float64Counter + UpdateOrderRabbitMQMessages metric.Float64Counter + DeleteOrderRabbitMQMessages metric.Float64Counter +} diff --git a/internal/services/orderservice/internal/shared/grpc/genproto/orders.pb.go b/internal/services/orderservice/internal/shared/grpc/genproto/orders.pb.go new file mode 100644 index 00000000..9800c343 --- /dev/null +++ b/internal/services/orderservice/internal/shared/grpc/genproto/orders.pb.go @@ -0,0 +1,1526 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v4.23.4 +// source: orderservice/orders.proto + +package orders_service + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ShopItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Title string `protobuf:"bytes,1,opt,name=Title,proto3" json:"Title,omitempty"` + Description string `protobuf:"bytes,2,opt,name=Description,proto3" json:"Description,omitempty"` + Quantity uint64 `protobuf:"varint,3,opt,name=Quantity,proto3" json:"Quantity,omitempty"` + Price float64 `protobuf:"fixed64,4,opt,name=Price,proto3" json:"Price,omitempty"` +} + +func (x *ShopItem) Reset() { + *x = ShopItem{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShopItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShopItem) ProtoMessage() {} + +func (x *ShopItem) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShopItem.ProtoReflect.Descriptor instead. +func (*ShopItem) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{0} +} + +func (x *ShopItem) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *ShopItem) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ShopItem) GetQuantity() uint64 { + if x != nil { + return x.Quantity + } + return 0 +} + +func (x *ShopItem) GetPrice() float64 { + if x != nil { + return x.Price + } + return 0 +} + +type Order struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId string `protobuf:"bytes,1,opt,name=OrderId,proto3" json:"OrderId,omitempty"` + ShopItems []*ShopItem `protobuf:"bytes,2,rep,name=ShopItems,proto3" json:"ShopItems,omitempty"` + Paid bool `protobuf:"varint,3,opt,name=Paid,proto3" json:"Paid,omitempty"` + Submitted bool `protobuf:"varint,4,opt,name=Submitted,proto3" json:"Submitted,omitempty"` + Completed bool `protobuf:"varint,5,opt,name=Completed,proto3" json:"Completed,omitempty"` + Canceled bool `protobuf:"varint,6,opt,name=Canceled,proto3" json:"Canceled,omitempty"` + TotalPrice float64 `protobuf:"fixed64,7,opt,name=TotalPrice,proto3" json:"TotalPrice,omitempty"` + AccountEmail string `protobuf:"bytes,8,opt,name=AccountEmail,proto3" json:"AccountEmail,omitempty"` + CancelReason string `protobuf:"bytes,9,opt,name=CancelReason,proto3" json:"CancelReason,omitempty"` + DeliveryAddress string `protobuf:"bytes,10,opt,name=DeliveryAddress,proto3" json:"DeliveryAddress,omitempty"` + DeliveredTime *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=DeliveredTime,proto3" json:"DeliveredTime,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` + PaymentId string `protobuf:"bytes,14,opt,name=PaymentId,proto3" json:"PaymentId,omitempty"` +} + +func (x *Order) Reset() { + *x = Order{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Order) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Order) ProtoMessage() {} + +func (x *Order) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Order.ProtoReflect.Descriptor instead. +func (*Order) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{1} +} + +func (x *Order) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *Order) GetShopItems() []*ShopItem { + if x != nil { + return x.ShopItems + } + return nil +} + +func (x *Order) GetPaid() bool { + if x != nil { + return x.Paid + } + return false +} + +func (x *Order) GetSubmitted() bool { + if x != nil { + return x.Submitted + } + return false +} + +func (x *Order) GetCompleted() bool { + if x != nil { + return x.Completed + } + return false +} + +func (x *Order) GetCanceled() bool { + if x != nil { + return x.Canceled + } + return false +} + +func (x *Order) GetTotalPrice() float64 { + if x != nil { + return x.TotalPrice + } + return 0 +} + +func (x *Order) GetAccountEmail() string { + if x != nil { + return x.AccountEmail + } + return "" +} + +func (x *Order) GetCancelReason() string { + if x != nil { + return x.CancelReason + } + return "" +} + +func (x *Order) GetDeliveryAddress() string { + if x != nil { + return x.DeliveryAddress + } + return "" +} + +func (x *Order) GetDeliveredTime() *timestamppb.Timestamp { + if x != nil { + return x.DeliveredTime + } + return nil +} + +func (x *Order) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Order) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +func (x *Order) GetPaymentId() string { + if x != nil { + return x.PaymentId + } + return "" +} + +type OrderReadModel struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=Id,proto3" json:"Id,omitempty"` + OrderId string `protobuf:"bytes,2,opt,name=OrderId,proto3" json:"OrderId,omitempty"` + ShopItems []*ShopItemReadModel `protobuf:"bytes,3,rep,name=ShopItems,proto3" json:"ShopItems,omitempty"` + Paid bool `protobuf:"varint,4,opt,name=Paid,proto3" json:"Paid,omitempty"` + Submitted bool `protobuf:"varint,5,opt,name=Submitted,proto3" json:"Submitted,omitempty"` + Completed bool `protobuf:"varint,6,opt,name=Completed,proto3" json:"Completed,omitempty"` + Canceled bool `protobuf:"varint,7,opt,name=Canceled,proto3" json:"Canceled,omitempty"` + TotalPrice float64 `protobuf:"fixed64,8,opt,name=TotalPrice,proto3" json:"TotalPrice,omitempty"` + AccountEmail string `protobuf:"bytes,9,opt,name=AccountEmail,proto3" json:"AccountEmail,omitempty"` + CancelReason string `protobuf:"bytes,10,opt,name=CancelReason,proto3" json:"CancelReason,omitempty"` + DeliveryAddress string `protobuf:"bytes,11,opt,name=DeliveryAddress,proto3" json:"DeliveryAddress,omitempty"` + DeliveredTime *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=DeliveredTime,proto3" json:"DeliveredTime,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` + PaymentId string `protobuf:"bytes,15,opt,name=PaymentId,proto3" json:"PaymentId,omitempty"` +} + +func (x *OrderReadModel) Reset() { + *x = OrderReadModel{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OrderReadModel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OrderReadModel) ProtoMessage() {} + +func (x *OrderReadModel) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OrderReadModel.ProtoReflect.Descriptor instead. +func (*OrderReadModel) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{2} +} + +func (x *OrderReadModel) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *OrderReadModel) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *OrderReadModel) GetShopItems() []*ShopItemReadModel { + if x != nil { + return x.ShopItems + } + return nil +} + +func (x *OrderReadModel) GetPaid() bool { + if x != nil { + return x.Paid + } + return false +} + +func (x *OrderReadModel) GetSubmitted() bool { + if x != nil { + return x.Submitted + } + return false +} + +func (x *OrderReadModel) GetCompleted() bool { + if x != nil { + return x.Completed + } + return false +} + +func (x *OrderReadModel) GetCanceled() bool { + if x != nil { + return x.Canceled + } + return false +} + +func (x *OrderReadModel) GetTotalPrice() float64 { + if x != nil { + return x.TotalPrice + } + return 0 +} + +func (x *OrderReadModel) GetAccountEmail() string { + if x != nil { + return x.AccountEmail + } + return "" +} + +func (x *OrderReadModel) GetCancelReason() string { + if x != nil { + return x.CancelReason + } + return "" +} + +func (x *OrderReadModel) GetDeliveryAddress() string { + if x != nil { + return x.DeliveryAddress + } + return "" +} + +func (x *OrderReadModel) GetDeliveredTime() *timestamppb.Timestamp { + if x != nil { + return x.DeliveredTime + } + return nil +} + +func (x *OrderReadModel) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *OrderReadModel) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +func (x *OrderReadModel) GetPaymentId() string { + if x != nil { + return x.PaymentId + } + return "" +} + +type ShopItemReadModel struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Title string `protobuf:"bytes,1,opt,name=Title,proto3" json:"Title,omitempty"` + Description string `protobuf:"bytes,2,opt,name=Description,proto3" json:"Description,omitempty"` + Quantity uint64 `protobuf:"varint,3,opt,name=Quantity,proto3" json:"Quantity,omitempty"` + Price float64 `protobuf:"fixed64,4,opt,name=Price,proto3" json:"Price,omitempty"` +} + +func (x *ShopItemReadModel) Reset() { + *x = ShopItemReadModel{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShopItemReadModel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShopItemReadModel) ProtoMessage() {} + +func (x *ShopItemReadModel) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShopItemReadModel.ProtoReflect.Descriptor instead. +func (*ShopItemReadModel) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{3} +} + +func (x *ShopItemReadModel) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *ShopItemReadModel) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ShopItemReadModel) GetQuantity() uint64 { + if x != nil { + return x.Quantity + } + return 0 +} + +func (x *ShopItemReadModel) GetPrice() float64 { + if x != nil { + return x.Price + } + return 0 +} + +type CreateOrderReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AccountEmail string `protobuf:"bytes,1,opt,name=AccountEmail,proto3" json:"AccountEmail,omitempty"` + ShopItems []*ShopItem `protobuf:"bytes,2,rep,name=ShopItems,proto3" json:"ShopItems,omitempty"` + DeliveryAddress string `protobuf:"bytes,3,opt,name=DeliveryAddress,proto3" json:"DeliveryAddress,omitempty"` + DeliveryTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=DeliveryTime,proto3" json:"DeliveryTime,omitempty"` +} + +func (x *CreateOrderReq) Reset() { + *x = CreateOrderReq{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateOrderReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateOrderReq) ProtoMessage() {} + +func (x *CreateOrderReq) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateOrderReq.ProtoReflect.Descriptor instead. +func (*CreateOrderReq) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateOrderReq) GetAccountEmail() string { + if x != nil { + return x.AccountEmail + } + return "" +} + +func (x *CreateOrderReq) GetShopItems() []*ShopItem { + if x != nil { + return x.ShopItems + } + return nil +} + +func (x *CreateOrderReq) GetDeliveryAddress() string { + if x != nil { + return x.DeliveryAddress + } + return "" +} + +func (x *CreateOrderReq) GetDeliveryTime() *timestamppb.Timestamp { + if x != nil { + return x.DeliveryTime + } + return nil +} + +type CreateOrderRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId string `protobuf:"bytes,1,opt,name=OrderId,proto3" json:"OrderId,omitempty"` +} + +func (x *CreateOrderRes) Reset() { + *x = CreateOrderRes{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateOrderRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateOrderRes) ProtoMessage() {} + +func (x *CreateOrderRes) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateOrderRes.ProtoReflect.Descriptor instead. +func (*CreateOrderRes) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateOrderRes) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +type SubmitOrderReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId string `protobuf:"bytes,1,opt,name=OrderId,proto3" json:"OrderId,omitempty"` +} + +func (x *SubmitOrderReq) Reset() { + *x = SubmitOrderReq{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubmitOrderReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitOrderReq) ProtoMessage() {} + +func (x *SubmitOrderReq) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitOrderReq.ProtoReflect.Descriptor instead. +func (*SubmitOrderReq) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{6} +} + +func (x *SubmitOrderReq) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +type SubmitOrderRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId string `protobuf:"bytes,1,opt,name=OrderId,proto3" json:"OrderId,omitempty"` +} + +func (x *SubmitOrderRes) Reset() { + *x = SubmitOrderRes{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubmitOrderRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitOrderRes) ProtoMessage() {} + +func (x *SubmitOrderRes) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitOrderRes.ProtoReflect.Descriptor instead. +func (*SubmitOrderRes) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{7} +} + +func (x *SubmitOrderRes) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +type GetOrderByIDReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=Id,proto3" json:"Id,omitempty"` +} + +func (x *GetOrderByIDReq) Reset() { + *x = GetOrderByIDReq{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOrderByIDReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOrderByIDReq) ProtoMessage() {} + +func (x *GetOrderByIDReq) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOrderByIDReq.ProtoReflect.Descriptor instead. +func (*GetOrderByIDReq) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{8} +} + +func (x *GetOrderByIDReq) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type GetOrderByIDRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Order *OrderReadModel `protobuf:"bytes,1,opt,name=Order,proto3" json:"Order,omitempty"` +} + +func (x *GetOrderByIDRes) Reset() { + *x = GetOrderByIDRes{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOrderByIDRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOrderByIDRes) ProtoMessage() {} + +func (x *GetOrderByIDRes) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOrderByIDRes.ProtoReflect.Descriptor instead. +func (*GetOrderByIDRes) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{9} +} + +func (x *GetOrderByIDRes) GetOrder() *OrderReadModel { + if x != nil { + return x.Order + } + return nil +} + +type UpdateShoppingCartReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OrderId string `protobuf:"bytes,1,opt,name=OrderId,proto3" json:"OrderId,omitempty"` + ShopItems []*ShopItem `protobuf:"bytes,2,rep,name=ShopItems,proto3" json:"ShopItems,omitempty"` +} + +func (x *UpdateShoppingCartReq) Reset() { + *x = UpdateShoppingCartReq{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateShoppingCartReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateShoppingCartReq) ProtoMessage() {} + +func (x *UpdateShoppingCartReq) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateShoppingCartReq.ProtoReflect.Descriptor instead. +func (*UpdateShoppingCartReq) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{10} +} + +func (x *UpdateShoppingCartReq) GetOrderId() string { + if x != nil { + return x.OrderId + } + return "" +} + +func (x *UpdateShoppingCartReq) GetShopItems() []*ShopItem { + if x != nil { + return x.ShopItems + } + return nil +} + +type UpdateShoppingCartRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UpdateShoppingCartRes) Reset() { + *x = UpdateShoppingCartRes{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateShoppingCartRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateShoppingCartRes) ProtoMessage() {} + +func (x *UpdateShoppingCartRes) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateShoppingCartRes.ProtoReflect.Descriptor instead. +func (*UpdateShoppingCartRes) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{11} +} + +type GetOrdersReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SearchText string `protobuf:"bytes,1,opt,name=SearchText,proto3" json:"SearchText,omitempty"` + Page int32 `protobuf:"varint,2,opt,name=Page,proto3" json:"Page,omitempty"` + Size int32 `protobuf:"varint,3,opt,name=Size,proto3" json:"Size,omitempty"` +} + +func (x *GetOrdersReq) Reset() { + *x = GetOrdersReq{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOrdersReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOrdersReq) ProtoMessage() {} + +func (x *GetOrdersReq) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOrdersReq.ProtoReflect.Descriptor instead. +func (*GetOrdersReq) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{12} +} + +func (x *GetOrdersReq) GetSearchText() string { + if x != nil { + return x.SearchText + } + return "" +} + +func (x *GetOrdersReq) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *GetOrdersReq) GetSize() int32 { + if x != nil { + return x.Size + } + return 0 +} + +type GetOrdersRes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pagination *Pagination `protobuf:"bytes,1,opt,name=Pagination,proto3" json:"Pagination,omitempty"` + Orders []*OrderReadModel `protobuf:"bytes,2,rep,name=Orders,proto3" json:"Orders,omitempty"` +} + +func (x *GetOrdersRes) Reset() { + *x = GetOrdersRes{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOrdersRes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOrdersRes) ProtoMessage() {} + +func (x *GetOrdersRes) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOrdersRes.ProtoReflect.Descriptor instead. +func (*GetOrdersRes) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{13} +} + +func (x *GetOrdersRes) GetPagination() *Pagination { + if x != nil { + return x.Pagination + } + return nil +} + +func (x *GetOrdersRes) GetOrders() []*OrderReadModel { + if x != nil { + return x.Orders + } + return nil +} + +type Pagination struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TotalItems int64 `protobuf:"varint,1,opt,name=TotalItems,proto3" json:"TotalItems,omitempty"` + TotalPages int32 `protobuf:"varint,2,opt,name=TotalPages,proto3" json:"TotalPages,omitempty"` + Page int32 `protobuf:"varint,3,opt,name=Page,proto3" json:"Page,omitempty"` + Size int32 `protobuf:"varint,4,opt,name=Size,proto3" json:"Size,omitempty"` + HasMore bool `protobuf:"varint,5,opt,name=HasMore,proto3" json:"HasMore,omitempty"` +} + +func (x *Pagination) Reset() { + *x = Pagination{} + if protoimpl.UnsafeEnabled { + mi := &file_order_service_orders_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Pagination) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Pagination) ProtoMessage() {} + +func (x *Pagination) ProtoReflect() protoreflect.Message { + mi := &file_order_service_orders_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Pagination.ProtoReflect.Descriptor instead. +func (*Pagination) Descriptor() ([]byte, []int) { + return file_order_service_orders_proto_rawDescGZIP(), []int{14} +} + +func (x *Pagination) GetTotalItems() int64 { + if x != nil { + return x.TotalItems + } + return 0 +} + +func (x *Pagination) GetTotalPages() int32 { + if x != nil { + return x.TotalPages + } + return 0 +} + +func (x *Pagination) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *Pagination) GetSize() int32 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *Pagination) GetHasMore() bool { + if x != nil { + return x.HasMore + } + return false +} + +var File_order_service_orders_proto protoreflect.FileDescriptor + +var file_order_service_orders_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x1a, 0x1f, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 0x0a, + 0x08, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x54, 0x69, 0x74, + 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, + 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x08, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x50, 0x72, + 0x69, 0x63, 0x65, 0x22, 0xab, 0x04, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, + 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x09, 0x53, 0x68, 0x6f, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x68, 0x6f, 0x70, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x50, 0x61, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x50, + 0x61, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, 0x65, + 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, + 0x1a, 0x0a, 0x08, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x54, + 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, + 0x22, 0x0a, 0x0c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x44, 0x65, + 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x40, 0x0a, + 0x0d, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0d, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x22, 0xcd, 0x04, 0x0a, 0x0e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x61, 0x64, 0x4d, + 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x3f, + 0x0a, 0x09, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x2e, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x61, 0x64, 0x4d, + 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x09, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x50, 0x61, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x50, + 0x61, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x74, 0x65, + 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, + 0x1a, 0x0a, 0x08, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x54, + 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, + 0x22, 0x0a, 0x0c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x44, 0x65, + 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x40, 0x0a, + 0x0d, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0d, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x22, 0x7d, 0x0a, 0x11, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x61, + 0x64, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, + 0x0a, 0x08, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, + 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, + 0x22, 0xd6, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x12, 0x22, 0x0a, 0x0c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x36, 0x0a, 0x09, 0x53, 0x68, 0x6f, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x68, 0x6f, 0x70, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, + 0x28, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, + 0x72, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x3e, 0x0a, 0x0c, 0x44, 0x65, 0x6c, + 0x69, 0x76, 0x65, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x44, 0x65, 0x6c, + 0x69, 0x76, 0x65, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x0e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0x2a, 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x18, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, + 0x64, 0x22, 0x2a, 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0x21, 0x0a, + 0x0f, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, + 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x64, + 0x22, 0x47, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x49, 0x44, + 0x52, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x61, 0x64, 0x4d, 0x6f, 0x64, + 0x65, 0x6c, 0x52, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x69, 0x0a, 0x15, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x72, 0x74, 0x52, + 0x65, 0x71, 0x12, 0x18, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x09, + 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x09, 0x53, 0x68, 0x6f, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, + 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x22, 0x56, 0x0a, + 0x0c, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x12, 0x1e, 0x0a, + 0x0a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x54, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x54, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x50, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x50, 0x61, 0x67, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x04, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x82, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x50, 0x61, 0x67, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x06, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x61, 0x64, 0x4d, 0x6f, 0x64, + 0x65, 0x6c, 0x52, 0x06, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x0a, 0x50, + 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, + 0x61, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x54, + 0x6f, 0x74, 0x61, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, + 0x61, 0x6c, 0x50, 0x61, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x54, + 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x61, 0x67, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x67, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x50, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x53, 0x69, 0x7a, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x48, 0x61, 0x73, 0x4d, 0x6f, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x48, 0x61, 0x73, 0x4d, 0x6f, 0x72, 0x65, 0x32, 0xac, 0x03, 0x0a, 0x0d, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4d, 0x0a, + 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x12, 0x4d, 0x0a, 0x0b, + 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x75, 0x62, + 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x75, 0x62, + 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x12, 0x62, 0x0a, 0x12, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x72, + 0x74, 0x12, 0x25, 0x2e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x6f, 0x70, 0x70, 0x69, 0x6e, + 0x67, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x25, 0x2e, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x53, 0x68, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x12, + 0x50, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x49, 0x44, 0x12, + 0x1f, 0x2e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, + 0x1a, 0x1f, 0x2e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, + 0x73, 0x12, 0x47, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1c, + 0x2e, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x65, + 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x42, 0x13, 0x5a, 0x11, 0x2e, 0x2f, + 0x3b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_order_service_orders_proto_rawDescOnce sync.Once + file_order_service_orders_proto_rawDescData = file_order_service_orders_proto_rawDesc +) + +func file_order_service_orders_proto_rawDescGZIP() []byte { + file_order_service_orders_proto_rawDescOnce.Do(func() { + file_order_service_orders_proto_rawDescData = protoimpl.X.CompressGZIP(file_order_service_orders_proto_rawDescData) + }) + return file_order_service_orders_proto_rawDescData +} + +var file_order_service_orders_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_order_service_orders_proto_goTypes = []interface{}{ + (*ShopItem)(nil), // 0: orders_service.ShopItem + (*Order)(nil), // 1: orders_service.Order + (*OrderReadModel)(nil), // 2: orders_service.OrderReadModel + (*ShopItemReadModel)(nil), // 3: orders_service.ShopItemReadModel + (*CreateOrderReq)(nil), // 4: orders_service.CreateOrderReq + (*CreateOrderRes)(nil), // 5: orders_service.CreateOrderRes + (*SubmitOrderReq)(nil), // 6: orders_service.SubmitOrderReq + (*SubmitOrderRes)(nil), // 7: orders_service.SubmitOrderRes + (*GetOrderByIDReq)(nil), // 8: orders_service.GetOrderByIDReq + (*GetOrderByIDRes)(nil), // 9: orders_service.GetOrderByIDRes + (*UpdateShoppingCartReq)(nil), // 10: orders_service.UpdateShoppingCartReq + (*UpdateShoppingCartRes)(nil), // 11: orders_service.UpdateShoppingCartRes + (*GetOrdersReq)(nil), // 12: orders_service.GetOrdersReq + (*GetOrdersRes)(nil), // 13: orders_service.GetOrdersRes + (*Pagination)(nil), // 14: orders_service.Pagination + (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp +} +var file_order_service_orders_proto_depIdxs = []int32{ + 0, // 0: orders_service.Order.ShopItems:type_name -> orders_service.ShopItem + 15, // 1: orders_service.Order.DeliveredTime:type_name -> google.protobuf.Timestamp + 15, // 2: orders_service.Order.CreatedAt:type_name -> google.protobuf.Timestamp + 15, // 3: orders_service.Order.UpdatedAt:type_name -> google.protobuf.Timestamp + 3, // 4: orders_service.OrderReadModel.ShopItems:type_name -> orders_service.ShopItemReadModel + 15, // 5: orders_service.OrderReadModel.DeliveredTime:type_name -> google.protobuf.Timestamp + 15, // 6: orders_service.OrderReadModel.CreatedAt:type_name -> google.protobuf.Timestamp + 15, // 7: orders_service.OrderReadModel.UpdatedAt:type_name -> google.protobuf.Timestamp + 0, // 8: orders_service.CreateOrderReq.ShopItems:type_name -> orders_service.ShopItem + 15, // 9: orders_service.CreateOrderReq.DeliveryTime:type_name -> google.protobuf.Timestamp + 2, // 10: orders_service.GetOrderByIDRes.Order:type_name -> orders_service.OrderReadModel + 0, // 11: orders_service.UpdateShoppingCartReq.ShopItems:type_name -> orders_service.ShopItem + 14, // 12: orders_service.GetOrdersRes.Pagination:type_name -> orders_service.Pagination + 2, // 13: orders_service.GetOrdersRes.Orders:type_name -> orders_service.OrderReadModel + 4, // 14: orders_service.OrdersService.CreateOrder:input_type -> orders_service.CreateOrderReq + 6, // 15: orders_service.OrdersService.SubmitOrder:input_type -> orders_service.SubmitOrderReq + 10, // 16: orders_service.OrdersService.UpdateShoppingCart:input_type -> orders_service.UpdateShoppingCartReq + 8, // 17: orders_service.OrdersService.GetOrderByID:input_type -> orders_service.GetOrderByIDReq + 12, // 18: orders_service.OrdersService.GetOrders:input_type -> orders_service.GetOrdersReq + 5, // 19: orders_service.OrdersService.CreateOrder:output_type -> orders_service.CreateOrderRes + 7, // 20: orders_service.OrdersService.SubmitOrder:output_type -> orders_service.SubmitOrderRes + 11, // 21: orders_service.OrdersService.UpdateShoppingCart:output_type -> orders_service.UpdateShoppingCartRes + 9, // 22: orders_service.OrdersService.GetOrderByID:output_type -> orders_service.GetOrderByIDRes + 13, // 23: orders_service.OrdersService.GetOrders:output_type -> orders_service.GetOrdersRes + 19, // [19:24] is the sub-list for method output_type + 14, // [14:19] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name +} + +func init() { file_order_service_orders_proto_init() } +func file_order_service_orders_proto_init() { + if File_order_service_orders_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_order_service_orders_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ShopItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Order); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OrderReadModel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ShopItemReadModel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateOrderReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateOrderRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubmitOrderReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubmitOrderRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOrderByIDReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOrderByIDRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateShoppingCartReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateShoppingCartRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOrdersReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOrdersRes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_order_service_orders_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Pagination); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_order_service_orders_proto_rawDesc, + NumEnums: 0, + NumMessages: 15, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_order_service_orders_proto_goTypes, + DependencyIndexes: file_order_service_orders_proto_depIdxs, + MessageInfos: file_order_service_orders_proto_msgTypes, + }.Build() + File_order_service_orders_proto = out.File + file_order_service_orders_proto_rawDesc = nil + file_order_service_orders_proto_goTypes = nil + file_order_service_orders_proto_depIdxs = nil +} diff --git a/internal/services/orderservice/internal/shared/grpc/genproto/orders_grpc.pb.go b/internal/services/orderservice/internal/shared/grpc/genproto/orders_grpc.pb.go new file mode 100644 index 00000000..ba46c936 --- /dev/null +++ b/internal/services/orderservice/internal/shared/grpc/genproto/orders_grpc.pb.go @@ -0,0 +1,255 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.4 +// source: orderservice/orders.proto + +package orders_service + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + OrdersService_CreateOrder_FullMethodName = "/orders_service.OrdersService/CreateOrder" + OrdersService_SubmitOrder_FullMethodName = "/orders_service.OrdersService/SubmitOrder" + OrdersService_UpdateShoppingCart_FullMethodName = "/orders_service.OrdersService/UpdateShoppingCart" + OrdersService_GetOrderByID_FullMethodName = "/orders_service.OrdersService/GetOrderByID" + OrdersService_GetOrders_FullMethodName = "/orders_service.OrdersService/GetOrders" +) + +// OrdersServiceClient is the client API for OrdersService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type OrdersServiceClient interface { + CreateOrder(ctx context.Context, in *CreateOrderReq, opts ...grpc.CallOption) (*CreateOrderRes, error) + SubmitOrder(ctx context.Context, in *SubmitOrderReq, opts ...grpc.CallOption) (*SubmitOrderRes, error) + UpdateShoppingCart(ctx context.Context, in *UpdateShoppingCartReq, opts ...grpc.CallOption) (*UpdateShoppingCartRes, error) + GetOrderByID(ctx context.Context, in *GetOrderByIDReq, opts ...grpc.CallOption) (*GetOrderByIDRes, error) + GetOrders(ctx context.Context, in *GetOrdersReq, opts ...grpc.CallOption) (*GetOrdersRes, error) +} + +type ordersServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewOrdersServiceClient(cc grpc.ClientConnInterface) OrdersServiceClient { + return &ordersServiceClient{cc} +} + +func (c *ordersServiceClient) CreateOrder(ctx context.Context, in *CreateOrderReq, opts ...grpc.CallOption) (*CreateOrderRes, error) { + out := new(CreateOrderRes) + err := c.cc.Invoke(ctx, OrdersService_CreateOrder_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *ordersServiceClient) SubmitOrder(ctx context.Context, in *SubmitOrderReq, opts ...grpc.CallOption) (*SubmitOrderRes, error) { + out := new(SubmitOrderRes) + err := c.cc.Invoke(ctx, OrdersService_SubmitOrder_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *ordersServiceClient) UpdateShoppingCart(ctx context.Context, in *UpdateShoppingCartReq, opts ...grpc.CallOption) (*UpdateShoppingCartRes, error) { + out := new(UpdateShoppingCartRes) + err := c.cc.Invoke(ctx, OrdersService_UpdateShoppingCart_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *ordersServiceClient) GetOrderByID(ctx context.Context, in *GetOrderByIDReq, opts ...grpc.CallOption) (*GetOrderByIDRes, error) { + out := new(GetOrderByIDRes) + err := c.cc.Invoke(ctx, OrdersService_GetOrderByID_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *ordersServiceClient) GetOrders(ctx context.Context, in *GetOrdersReq, opts ...grpc.CallOption) (*GetOrdersRes, error) { + out := new(GetOrdersRes) + err := c.cc.Invoke(ctx, OrdersService_GetOrders_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// OrdersServiceServer is the server API for OrdersService service. +// All implementations should embed UnimplementedOrdersServiceServer +// for forward compatibility +type OrdersServiceServer interface { + CreateOrder(context.Context, *CreateOrderReq) (*CreateOrderRes, error) + SubmitOrder(context.Context, *SubmitOrderReq) (*SubmitOrderRes, error) + UpdateShoppingCart(context.Context, *UpdateShoppingCartReq) (*UpdateShoppingCartRes, error) + GetOrderByID(context.Context, *GetOrderByIDReq) (*GetOrderByIDRes, error) + GetOrders(context.Context, *GetOrdersReq) (*GetOrdersRes, error) +} + +// UnimplementedOrdersServiceServer should be embedded to have forward compatible implementations. +type UnimplementedOrdersServiceServer struct { +} + +func (UnimplementedOrdersServiceServer) CreateOrder(context.Context, *CreateOrderReq) (*CreateOrderRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateOrder not implemented") +} +func (UnimplementedOrdersServiceServer) SubmitOrder(context.Context, *SubmitOrderReq) (*SubmitOrderRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitOrder not implemented") +} +func (UnimplementedOrdersServiceServer) UpdateShoppingCart(context.Context, *UpdateShoppingCartReq) (*UpdateShoppingCartRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateShoppingCart not implemented") +} +func (UnimplementedOrdersServiceServer) GetOrderByID(context.Context, *GetOrderByIDReq) (*GetOrderByIDRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrderByID not implemented") +} +func (UnimplementedOrdersServiceServer) GetOrders(context.Context, *GetOrdersReq) (*GetOrdersRes, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrders not implemented") +} + +// UnsafeOrdersServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to OrdersServiceServer will +// result in compilation errors. +type UnsafeOrdersServiceServer interface { + mustEmbedUnimplementedOrdersServiceServer() +} + +func RegisterOrdersServiceServer(s grpc.ServiceRegistrar, srv OrdersServiceServer) { + s.RegisterService(&OrdersService_ServiceDesc, srv) +} + +func _OrdersService_CreateOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateOrderReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrdersServiceServer).CreateOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OrdersService_CreateOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrdersServiceServer).CreateOrder(ctx, req.(*CreateOrderReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _OrdersService_SubmitOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitOrderReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrdersServiceServer).SubmitOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OrdersService_SubmitOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrdersServiceServer).SubmitOrder(ctx, req.(*SubmitOrderReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _OrdersService_UpdateShoppingCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateShoppingCartReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrdersServiceServer).UpdateShoppingCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OrdersService_UpdateShoppingCart_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrdersServiceServer).UpdateShoppingCart(ctx, req.(*UpdateShoppingCartReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _OrdersService_GetOrderByID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrderByIDReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrdersServiceServer).GetOrderByID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OrdersService_GetOrderByID_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrdersServiceServer).GetOrderByID(ctx, req.(*GetOrderByIDReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _OrdersService_GetOrders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrdersReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrdersServiceServer).GetOrders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OrdersService_GetOrders_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrdersServiceServer).GetOrders(ctx, req.(*GetOrdersReq)) + } + return interceptor(ctx, in, info, handler) +} + +// OrdersService_ServiceDesc is the grpc.ServiceDesc for OrdersService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var OrdersService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "orders_service.OrdersService", + HandlerType: (*OrdersServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateOrder", + Handler: _OrdersService_CreateOrder_Handler, + }, + { + MethodName: "SubmitOrder", + Handler: _OrdersService_SubmitOrder_Handler, + }, + { + MethodName: "UpdateShoppingCart", + Handler: _OrdersService_UpdateShoppingCart_Handler, + }, + { + MethodName: "GetOrderByID", + Handler: _OrdersService_GetOrderByID_Handler, + }, + { + MethodName: "GetOrders", + Handler: _OrdersService_GetOrders_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "orderservice/orders.proto", +} diff --git a/internal/services/orderservice/internal/shared/grpc/order_grpc_service_server.go b/internal/services/orderservice/internal/shared/grpc/order_grpc_service_server.go new file mode 100644 index 00000000..4d47f752 --- /dev/null +++ b/internal/services/orderservice/internal/shared/grpc/order_grpc_service_server.go @@ -0,0 +1,224 @@ +package grpc + +import ( + "context" + "fmt" + + customErrors "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/http/httperrors/customerrors" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mapper" + attribute2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/attribute" + utils2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/otel/tracing/utils" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + createOrderCommandV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/commands" + createOrderDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos" + getOrderByIdDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos" + getOrderByIdQueryV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries" + getOrdersDtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_orders/v1/dtos" + getOrdersQueryV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_orders/v1/queries" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/contracts" + grpcOrderService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/grpc/genproto" + + "emperror.dev/errors" + "github.com/go-playground/validator" + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + "go.opentelemetry.io/otel/attribute" + api "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" +) + +type OrderGrpcServiceServer struct { + ordersMetrics *contracts.OrdersMetrics + logger logger.Logger + validator *validator.Validate +} + +var grpcMetricsAttr = api.WithAttributes( + attribute.Key("MetricsType").String("Grpc"), +) + +func NewOrderGrpcService( + logger logger.Logger, + validator *validator.Validate, + ordersMetrics *contracts.OrdersMetrics, +) *OrderGrpcServiceServer { + return &OrderGrpcServiceServer{ + ordersMetrics: ordersMetrics, + logger: logger, + validator: validator, + } +} + +func (o OrderGrpcServiceServer) CreateOrder( + ctx context.Context, + req *grpcOrderService.CreateOrderReq, +) (*grpcOrderService.CreateOrderRes, error) { + span := trace.SpanFromContext(ctx) + span.SetAttributes(attribute2.Object("Request", req)) + o.ordersMetrics.CreateOrderGrpcRequests.Add(ctx, 1, grpcMetricsAttr) + + shopItemsDtos, err := mapper.Map[[]*dtosV1.ShopItemDto](req.GetShopItems()) + if err != nil { + return nil, err + } + + command, err := createOrderCommandV1.NewCreateOrder( + shopItemsDtos, + req.AccountEmail, + req.DeliveryAddress, + req.DeliveryTime.AsTime(), + ) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "[OrderGrpcServiceServer_CreateOrder.StructCtx] command validation failed", + ) + o.logger.Errorf( + fmt.Sprintf("[OrderGrpcServiceServer_CreateOrder.StructCtx] err: %v", validationErr), + ) + return nil, validationErr + } + + result, err := mediatr.Send[*createOrderCommandV1.CreateOrder, *createOrderDtosV1.CreateOrderResponseDto]( + ctx, + command, + ) + if err != nil { + err = errors.WithMessage( + err, + "[ProductGrpcServiceServer_CreateOrder.Send] error in sending CreateOrder", + ) + o.logger.Errorw( + fmt.Sprintf( + "[ProductGrpcServiceServer_CreateOrder.Send] id: {%s}, err: %v", + command.OrderId, + err, + ), + logger.Fields{"Id": command.OrderId}, + ) + return nil, err + } + + return &grpcOrderService.CreateOrderRes{OrderId: result.OrderId.String()}, nil +} + +func (o OrderGrpcServiceServer) GetOrderByID( + ctx context.Context, + req *grpcOrderService.GetOrderByIDReq, +) (*grpcOrderService.GetOrderByIDRes, error) { + span := trace.SpanFromContext(ctx) + span.SetAttributes(attribute2.Object("Request", req)) + o.ordersMetrics.GetOrderByIdGrpcRequests.Add(ctx, 1, grpcMetricsAttr) + + orderIdUUID, err := uuid.FromString(req.Id) + if err != nil { + badRequestErr := customErrors.NewBadRequestErrorWrap( + err, + "[OrderGrpcServiceServer_GetOrderByID.uuid.FromString] error in converting uuid", + ) + o.logger.Errorf( + fmt.Sprintf( + "[OrderGrpcServiceServer_GetOrderByID.uuid.FromString] err: %v", + badRequestErr, + ), + ) + return nil, badRequestErr + } + + query, err := getOrderByIdQueryV1.NewGetOrderById(orderIdUUID) + if err != nil { + validationErr := customErrors.NewValidationErrorWrap( + err, + "[OrderGrpcServiceServer_GetOrderByID.StructCtx] query validation failed", + ) + o.logger.Errorf( + fmt.Sprintf("[OrderGrpcServiceServer_GetOrderByID.StructCtx] err: %v", validationErr), + ) + return nil, validationErr + } + + queryResult, err := mediatr.Send[*getOrderByIdQueryV1.GetOrderById, *getOrderByIdDtosV1.GetOrderByIdResponseDto]( + ctx, + query, + ) + if err != nil { + err = errors.WithMessage( + err, + "[OrderGrpcServiceServer_GetOrderByID.Send] error in sending GetOrderById", + ) + o.logger.Errorw( + fmt.Sprintf( + "[OrderGrpcServiceServer_GetOrderByID.Send] id: {%s}, err: %v", + query.Id, + err, + ), + logger.Fields{"Id": query.Id}, + ) + return nil, err + } + + q := queryResult.Order + order, err := mapper.Map[*grpcOrderService.OrderReadModel](q) + if err != nil { + err = errors.WithMessage( + err, + "[OrderGrpcServiceServer_GetOrderByID.Map] error in mapping order", + ) + return nil, utils2.TraceStatusFromContext(ctx, err) + } + + return &grpcOrderService.GetOrderByIDRes{Order: order}, nil +} + +func (o OrderGrpcServiceServer) SubmitOrder( + ctx context.Context, + req *grpcOrderService.SubmitOrderReq, +) (*grpcOrderService.SubmitOrderRes, error) { + return nil, nil +} + +func (o OrderGrpcServiceServer) UpdateShoppingCart( + ctx context.Context, + req *grpcOrderService.UpdateShoppingCartReq, +) (*grpcOrderService.UpdateShoppingCartRes, error) { + return nil, nil +} + +func (o OrderGrpcServiceServer) GetOrders( + ctx context.Context, + req *grpcOrderService.GetOrdersReq, +) (*grpcOrderService.GetOrdersRes, error) { + o.ordersMetrics.GetOrdersGrpcRequests.Add(ctx, 1, grpcMetricsAttr) + span := trace.SpanFromContext(ctx) + span.SetAttributes(attribute2.Object("Request", req)) + + query := getOrdersQueryV1.NewGetOrders( + &utils.ListQuery{Page: int(req.Page), Size: int(req.Size)}, + ) + + queryResult, err := mediatr.Send[*getOrdersQueryV1.GetOrders, *getOrdersDtosV1.GetOrdersResponseDto]( + ctx, + query, + ) + if err != nil { + err = errors.WithMessage( + err, + "[OrderGrpcServiceServer_GetOrders.Send] error in sending GetOrders", + ) + o.logger.Error(fmt.Sprintf("[OrderGrpcServiceServer_GetOrders.Send] err: {%v}", err)) + return nil, err + } + + ordersResponse, err := mapper.Map[*grpcOrderService.GetOrdersRes](queryResult.Orders) + if err != nil { + err = errors.WithMessage( + err, + "[OrderGrpcServiceServer_GetOrders.Map] error in mapping orders", + ) + return nil, err + } + + return ordersResponse, nil +} diff --git a/internal/services/orderservice/internal/shared/test_fixtures/integration/integration_test_fixture.go b/internal/services/orderservice/internal/shared/test_fixtures/integration/integration_test_fixture.go new file mode 100644 index 00000000..f97d84bd --- /dev/null +++ b/internal/services/orderservice/internal/shared/test_fixtures/integration/integration_test_fixture.go @@ -0,0 +1,247 @@ +package integration + +import ( + "context" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/messaging/bus" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/es/contracts/store" + config3 "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/eventstroredb/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/fxapp/contracts" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/logger" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/mongodb" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/rabbitmq/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + config2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/config" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/repositories" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/aggregate" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/app/test" + contracts2 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/contracts" + ordersService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/grpc/genproto" + + "emperror.dev/errors" + "github.com/EventStore/EventStore-Client-Go/esdb" + "github.com/brianvoe/gofakeit/v6" + rabbithole "github.com/michaelklishin/rabbit-hole" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +const ( + orderCollection = "orders" +) + +type IntegrationTestSharedFixture struct { + OrderAggregateStore store.AggregateStore[*aggregate.Order] + OrderMongoRepository repositories.OrderMongoRepository + OrdersMetrics contracts2.OrdersMetrics + Cfg *config2.Config + Log logger.Logger + Bus bus.Bus + Container contracts.Container + RabbitmqCleaner *rabbithole.Client + rabbitmqOptions *config.RabbitmqOptions + BaseAddress string + mongoClient *mongo.Client + esdbClient *esdb.Client + MongoDbOptions *mongodb.MongoDbOptions + EventStoreDbOptions *config3.EventStoreDbOptions + Items []*read_models.OrderReadModel + OrdersServiceClient ordersService.OrdersServiceClient +} + +func NewIntegrationTestSharedFixture( + t *testing.T, +) *IntegrationTestSharedFixture { + result := test.NewTestApp().Run(t) + + // https://github.com/michaelklishin/rabbit-hole + rmqc, err := rabbithole.NewClient( + result.RabbitmqOptions.RabbitmqHostOptions.HttpEndPoint(), + result.RabbitmqOptions.RabbitmqHostOptions.UserName, + result.RabbitmqOptions.RabbitmqHostOptions.Password) + if err != nil { + result.Logger.Error( + errors.WrapIf(err, "error in creating rabbithole client"), + ) + } + shared := &IntegrationTestSharedFixture{ + Log: result.Logger, + Container: result.Container, + Cfg: result.Cfg, + RabbitmqCleaner: rmqc, + OrderMongoRepository: result.OrderMongoRepository, + OrderAggregateStore: result.OrderAggregateStore, + MongoDbOptions: result.MongoDbOptions, + EventStoreDbOptions: result.EventStoreDbOptions, + mongoClient: result.MongoClient, + Bus: result.Bus, + rabbitmqOptions: result.RabbitmqOptions, + BaseAddress: result.EchoHttpOptions.BasePathAddress(), + OrdersServiceClient: result.OrdersServiceClient, + } + + return shared +} + +func (i *IntegrationTestSharedFixture) SetupTest() { + i.Log.Info("SetupTest started") + + // seed data in each test + res, err := seedReadModelData(i.mongoClient, i.MongoDbOptions.Database) + if err != nil { + i.Log.Error(errors.WrapIf(err, "error in seeding mongodb data")) + } + i.Items = res +} + +func (i *IntegrationTestSharedFixture) TearDownTest() { + i.Log.Info("TearDownTest started") + + // cleanup test containers with their hooks + if err := i.cleanupRabbitmqData(); err != nil { + i.Log.Error(errors.WrapIf(err, "error in cleanup rabbitmq data")) + } + + if err := i.cleanupMongoData(); err != nil { + i.Log.Error(errors.WrapIf(err, "error in cleanup mongodb data")) + } +} + +func (i *IntegrationTestSharedFixture) cleanupRabbitmqData() error { + // https://github.com/michaelklishin/rabbit-hole + // Get all queues + queues, err := i.RabbitmqCleaner.ListQueuesIn( + i.rabbitmqOptions.RabbitmqHostOptions.VirtualHost, + ) + if err != nil { + return err + } + + // clear each queue + for _, queue := range queues { + _, err = i.RabbitmqCleaner.PurgeQueue( + i.rabbitmqOptions.RabbitmqHostOptions.VirtualHost, + queue.Name, + ) + return err + } + + return nil +} + +func (i *IntegrationTestSharedFixture) cleanupMongoData() error { + collections := []string{orderCollection} + err := cleanupCollections( + i.mongoClient, + collections, + i.MongoDbOptions.Database, + ) + return err +} + +func cleanupCollections( + db *mongo.Client, + collections []string, + databaseName string, +) error { + database := db.Database(databaseName) + ctx := context.Background() + + // Iterate over the collections and delete all collections + for _, collection := range collections { + collection := database.Collection(collection) + + err := collection.Drop(ctx) + if err != nil { + return err + } + } + return nil +} + +func seedReadModelData( + db *mongo.Client, + databaseName string, +) ([]*read_models.OrderReadModel, error) { + ctx := context.Background() + + orders := []*read_models.OrderReadModel{ + { + Id: gofakeit.UUID(), + OrderId: gofakeit.UUID(), + ShopItems: generateShopItems(), + AccountEmail: gofakeit.Email(), + DeliveryAddress: gofakeit.Address().Address, + CancelReason: gofakeit.Sentence(5), + TotalPrice: gofakeit.Float64Range(10, 100), + DeliveredTime: gofakeit.Date(), + Paid: gofakeit.Bool(), + Submitted: gofakeit.Bool(), + Completed: gofakeit.Bool(), + Canceled: gofakeit.Bool(), + PaymentId: gofakeit.UUID(), + CreatedAt: gofakeit.Date(), + UpdatedAt: gofakeit.Date(), + }, + { + Id: gofakeit.UUID(), + OrderId: gofakeit.UUID(), + ShopItems: generateShopItems(), + AccountEmail: gofakeit.Email(), + DeliveryAddress: gofakeit.Address().Address, + CancelReason: gofakeit.Sentence(5), + TotalPrice: gofakeit.Float64Range(10, 100), + DeliveredTime: gofakeit.Date(), + Paid: gofakeit.Bool(), + Submitted: gofakeit.Bool(), + Completed: gofakeit.Bool(), + Canceled: gofakeit.Bool(), + PaymentId: gofakeit.UUID(), + CreatedAt: gofakeit.Date(), + UpdatedAt: gofakeit.Date(), + }, + } + + //// https://go.dev/doc/faq#convert_slice_of_interface + data := make([]interface{}, len(orders)) + for i, v := range orders { + data[i] = v + } + + collection := db.Database(databaseName).Collection("orders") + _, err := collection.InsertMany( + context.Background(), + data, + &options.InsertManyOptions{}, + ) + if err != nil { + return nil, errors.WrapIf(err, "error in seed database") + } + + result, err := mongodb.Paginate[*read_models.OrderReadModel]( + ctx, + utils.NewListQuery(10, 1), + collection, + nil, + ) + return result.Items, nil +} + +func generateShopItems() []*read_models.ShopItemReadModel { + var shopItems []*read_models.ShopItemReadModel + + for i := 0; i < 3; i++ { + shopItem := &read_models.ShopItemReadModel{ + Title: gofakeit.Word(), + Description: gofakeit.Sentence(3), + Quantity: uint64(gofakeit.UintRange(1, 100)), + Price: gofakeit.Float64Range(1, 50), + } + + shopItems = append(shopItems, shopItem) + } + + return shopItems +} diff --git a/internal/services/orderservice/mocks/InvalidDeliveryAddressError.go b/internal/services/orderservice/mocks/InvalidDeliveryAddressError.go new file mode 100644 index 00000000..e4404169 --- /dev/null +++ b/internal/services/orderservice/mocks/InvalidDeliveryAddressError.go @@ -0,0 +1,398 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + fmt "fmt" + + mock "github.com/stretchr/testify/mock" +) + +// InvalidDeliveryAddressError is an autogenerated mock type for the InvalidDeliveryAddressError type +type InvalidDeliveryAddressError struct { + mock.Mock +} + +type InvalidDeliveryAddressError_Expecter struct { + mock *mock.Mock +} + +func (_m *InvalidDeliveryAddressError) EXPECT() *InvalidDeliveryAddressError_Expecter { + return &InvalidDeliveryAddressError_Expecter{mock: &_m.Mock} +} + +// Cause provides a mock function with given fields: +func (_m *InvalidDeliveryAddressError) Cause() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InvalidDeliveryAddressError_Cause_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Cause' +type InvalidDeliveryAddressError_Cause_Call struct { + *mock.Call +} + +// Cause is a helper method to define mock.On call +func (_e *InvalidDeliveryAddressError_Expecter) Cause() *InvalidDeliveryAddressError_Cause_Call { + return &InvalidDeliveryAddressError_Cause_Call{Call: _e.mock.On("Cause")} +} + +func (_c *InvalidDeliveryAddressError_Cause_Call) Run(run func()) *InvalidDeliveryAddressError_Cause_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidDeliveryAddressError_Cause_Call) Return(_a0 error) *InvalidDeliveryAddressError_Cause_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidDeliveryAddressError_Cause_Call) RunAndReturn(run func() error) *InvalidDeliveryAddressError_Cause_Call { + _c.Call.Return(run) + return _c +} + +// Error provides a mock function with given fields: +func (_m *InvalidDeliveryAddressError) Error() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// InvalidDeliveryAddressError_Error_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Error' +type InvalidDeliveryAddressError_Error_Call struct { + *mock.Call +} + +// Error is a helper method to define mock.On call +func (_e *InvalidDeliveryAddressError_Expecter) Error() *InvalidDeliveryAddressError_Error_Call { + return &InvalidDeliveryAddressError_Error_Call{Call: _e.mock.On("Error")} +} + +func (_c *InvalidDeliveryAddressError_Error_Call) Run(run func()) *InvalidDeliveryAddressError_Error_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidDeliveryAddressError_Error_Call) Return(_a0 string) *InvalidDeliveryAddressError_Error_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidDeliveryAddressError_Error_Call) RunAndReturn(run func() string) *InvalidDeliveryAddressError_Error_Call { + _c.Call.Return(run) + return _c +} + +// Format provides a mock function with given fields: f, verb +func (_m *InvalidDeliveryAddressError) Format(f fmt.State, verb rune) { + _m.Called(f, verb) +} + +// InvalidDeliveryAddressError_Format_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Format' +type InvalidDeliveryAddressError_Format_Call struct { + *mock.Call +} + +// Format is a helper method to define mock.On call +// - f fmt.State +// - verb rune +func (_e *InvalidDeliveryAddressError_Expecter) Format(f interface{}, verb interface{}) *InvalidDeliveryAddressError_Format_Call { + return &InvalidDeliveryAddressError_Format_Call{Call: _e.mock.On("Format", f, verb)} +} + +func (_c *InvalidDeliveryAddressError_Format_Call) Run(run func(f fmt.State, verb rune)) *InvalidDeliveryAddressError_Format_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(fmt.State), args[1].(rune)) + }) + return _c +} + +func (_c *InvalidDeliveryAddressError_Format_Call) Return() *InvalidDeliveryAddressError_Format_Call { + _c.Call.Return() + return _c +} + +func (_c *InvalidDeliveryAddressError_Format_Call) RunAndReturn(run func(fmt.State, rune)) *InvalidDeliveryAddressError_Format_Call { + _c.Call.Return(run) + return _c +} + +// IsBadRequestError provides a mock function with given fields: +func (_m *InvalidDeliveryAddressError) IsBadRequestError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// InvalidDeliveryAddressError_IsBadRequestError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsBadRequestError' +type InvalidDeliveryAddressError_IsBadRequestError_Call struct { + *mock.Call +} + +// IsBadRequestError is a helper method to define mock.On call +func (_e *InvalidDeliveryAddressError_Expecter) IsBadRequestError() *InvalidDeliveryAddressError_IsBadRequestError_Call { + return &InvalidDeliveryAddressError_IsBadRequestError_Call{Call: _e.mock.On("IsBadRequestError")} +} + +func (_c *InvalidDeliveryAddressError_IsBadRequestError_Call) Run(run func()) *InvalidDeliveryAddressError_IsBadRequestError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidDeliveryAddressError_IsBadRequestError_Call) Return(_a0 bool) *InvalidDeliveryAddressError_IsBadRequestError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidDeliveryAddressError_IsBadRequestError_Call) RunAndReturn(run func() bool) *InvalidDeliveryAddressError_IsBadRequestError_Call { + _c.Call.Return(run) + return _c +} + +// IsCustomError provides a mock function with given fields: +func (_m *InvalidDeliveryAddressError) IsCustomError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// InvalidDeliveryAddressError_IsCustomError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsCustomError' +type InvalidDeliveryAddressError_IsCustomError_Call struct { + *mock.Call +} + +// IsCustomError is a helper method to define mock.On call +func (_e *InvalidDeliveryAddressError_Expecter) IsCustomError() *InvalidDeliveryAddressError_IsCustomError_Call { + return &InvalidDeliveryAddressError_IsCustomError_Call{Call: _e.mock.On("IsCustomError")} +} + +func (_c *InvalidDeliveryAddressError_IsCustomError_Call) Run(run func()) *InvalidDeliveryAddressError_IsCustomError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidDeliveryAddressError_IsCustomError_Call) Return(_a0 bool) *InvalidDeliveryAddressError_IsCustomError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidDeliveryAddressError_IsCustomError_Call) RunAndReturn(run func() bool) *InvalidDeliveryAddressError_IsCustomError_Call { + _c.Call.Return(run) + return _c +} + +// IsInvalidDeliveryAddressError provides a mock function with given fields: +func (_m *InvalidDeliveryAddressError) IsInvalidDeliveryAddressError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsInvalidDeliveryAddressError' +type InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call struct { + *mock.Call +} + +// IsInvalidDeliveryAddressError is a helper method to define mock.On call +func (_e *InvalidDeliveryAddressError_Expecter) IsInvalidDeliveryAddressError() *InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call { + return &InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call{Call: _e.mock.On("IsInvalidDeliveryAddressError")} +} + +func (_c *InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call) Run(run func()) *InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call) Return(_a0 bool) *InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call) RunAndReturn(run func() bool) *InvalidDeliveryAddressError_IsInvalidDeliveryAddressError_Call { + _c.Call.Return(run) + return _c +} + +// Message provides a mock function with given fields: +func (_m *InvalidDeliveryAddressError) Message() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// InvalidDeliveryAddressError_Message_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Message' +type InvalidDeliveryAddressError_Message_Call struct { + *mock.Call +} + +// Message is a helper method to define mock.On call +func (_e *InvalidDeliveryAddressError_Expecter) Message() *InvalidDeliveryAddressError_Message_Call { + return &InvalidDeliveryAddressError_Message_Call{Call: _e.mock.On("Message")} +} + +func (_c *InvalidDeliveryAddressError_Message_Call) Run(run func()) *InvalidDeliveryAddressError_Message_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidDeliveryAddressError_Message_Call) Return(_a0 string) *InvalidDeliveryAddressError_Message_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidDeliveryAddressError_Message_Call) RunAndReturn(run func() string) *InvalidDeliveryAddressError_Message_Call { + _c.Call.Return(run) + return _c +} + +// Status provides a mock function with given fields: +func (_m *InvalidDeliveryAddressError) Status() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// InvalidDeliveryAddressError_Status_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Status' +type InvalidDeliveryAddressError_Status_Call struct { + *mock.Call +} + +// Status is a helper method to define mock.On call +func (_e *InvalidDeliveryAddressError_Expecter) Status() *InvalidDeliveryAddressError_Status_Call { + return &InvalidDeliveryAddressError_Status_Call{Call: _e.mock.On("Status")} +} + +func (_c *InvalidDeliveryAddressError_Status_Call) Run(run func()) *InvalidDeliveryAddressError_Status_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidDeliveryAddressError_Status_Call) Return(_a0 int) *InvalidDeliveryAddressError_Status_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidDeliveryAddressError_Status_Call) RunAndReturn(run func() int) *InvalidDeliveryAddressError_Status_Call { + _c.Call.Return(run) + return _c +} + +// Unwrap provides a mock function with given fields: +func (_m *InvalidDeliveryAddressError) Unwrap() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InvalidDeliveryAddressError_Unwrap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unwrap' +type InvalidDeliveryAddressError_Unwrap_Call struct { + *mock.Call +} + +// Unwrap is a helper method to define mock.On call +func (_e *InvalidDeliveryAddressError_Expecter) Unwrap() *InvalidDeliveryAddressError_Unwrap_Call { + return &InvalidDeliveryAddressError_Unwrap_Call{Call: _e.mock.On("Unwrap")} +} + +func (_c *InvalidDeliveryAddressError_Unwrap_Call) Run(run func()) *InvalidDeliveryAddressError_Unwrap_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidDeliveryAddressError_Unwrap_Call) Return(_a0 error) *InvalidDeliveryAddressError_Unwrap_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidDeliveryAddressError_Unwrap_Call) RunAndReturn(run func() error) *InvalidDeliveryAddressError_Unwrap_Call { + _c.Call.Return(run) + return _c +} + +// NewInvalidDeliveryAddressError creates a new instance of InvalidDeliveryAddressError. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInvalidDeliveryAddressError(t interface { + mock.TestingT + Cleanup(func()) +}) *InvalidDeliveryAddressError { + mock := &InvalidDeliveryAddressError{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/mocks/InvalidEmailAddressError.go b/internal/services/orderservice/mocks/InvalidEmailAddressError.go new file mode 100644 index 00000000..c7170aa3 --- /dev/null +++ b/internal/services/orderservice/mocks/InvalidEmailAddressError.go @@ -0,0 +1,398 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + fmt "fmt" + + mock "github.com/stretchr/testify/mock" +) + +// InvalidEmailAddressError is an autogenerated mock type for the InvalidEmailAddressError type +type InvalidEmailAddressError struct { + mock.Mock +} + +type InvalidEmailAddressError_Expecter struct { + mock *mock.Mock +} + +func (_m *InvalidEmailAddressError) EXPECT() *InvalidEmailAddressError_Expecter { + return &InvalidEmailAddressError_Expecter{mock: &_m.Mock} +} + +// Cause provides a mock function with given fields: +func (_m *InvalidEmailAddressError) Cause() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InvalidEmailAddressError_Cause_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Cause' +type InvalidEmailAddressError_Cause_Call struct { + *mock.Call +} + +// Cause is a helper method to define mock.On call +func (_e *InvalidEmailAddressError_Expecter) Cause() *InvalidEmailAddressError_Cause_Call { + return &InvalidEmailAddressError_Cause_Call{Call: _e.mock.On("Cause")} +} + +func (_c *InvalidEmailAddressError_Cause_Call) Run(run func()) *InvalidEmailAddressError_Cause_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidEmailAddressError_Cause_Call) Return(_a0 error) *InvalidEmailAddressError_Cause_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidEmailAddressError_Cause_Call) RunAndReturn(run func() error) *InvalidEmailAddressError_Cause_Call { + _c.Call.Return(run) + return _c +} + +// Error provides a mock function with given fields: +func (_m *InvalidEmailAddressError) Error() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// InvalidEmailAddressError_Error_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Error' +type InvalidEmailAddressError_Error_Call struct { + *mock.Call +} + +// Error is a helper method to define mock.On call +func (_e *InvalidEmailAddressError_Expecter) Error() *InvalidEmailAddressError_Error_Call { + return &InvalidEmailAddressError_Error_Call{Call: _e.mock.On("Error")} +} + +func (_c *InvalidEmailAddressError_Error_Call) Run(run func()) *InvalidEmailAddressError_Error_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidEmailAddressError_Error_Call) Return(_a0 string) *InvalidEmailAddressError_Error_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidEmailAddressError_Error_Call) RunAndReturn(run func() string) *InvalidEmailAddressError_Error_Call { + _c.Call.Return(run) + return _c +} + +// Format provides a mock function with given fields: f, verb +func (_m *InvalidEmailAddressError) Format(f fmt.State, verb rune) { + _m.Called(f, verb) +} + +// InvalidEmailAddressError_Format_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Format' +type InvalidEmailAddressError_Format_Call struct { + *mock.Call +} + +// Format is a helper method to define mock.On call +// - f fmt.State +// - verb rune +func (_e *InvalidEmailAddressError_Expecter) Format(f interface{}, verb interface{}) *InvalidEmailAddressError_Format_Call { + return &InvalidEmailAddressError_Format_Call{Call: _e.mock.On("Format", f, verb)} +} + +func (_c *InvalidEmailAddressError_Format_Call) Run(run func(f fmt.State, verb rune)) *InvalidEmailAddressError_Format_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(fmt.State), args[1].(rune)) + }) + return _c +} + +func (_c *InvalidEmailAddressError_Format_Call) Return() *InvalidEmailAddressError_Format_Call { + _c.Call.Return() + return _c +} + +func (_c *InvalidEmailAddressError_Format_Call) RunAndReturn(run func(fmt.State, rune)) *InvalidEmailAddressError_Format_Call { + _c.Call.Return(run) + return _c +} + +// IsBadRequestError provides a mock function with given fields: +func (_m *InvalidEmailAddressError) IsBadRequestError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// InvalidEmailAddressError_IsBadRequestError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsBadRequestError' +type InvalidEmailAddressError_IsBadRequestError_Call struct { + *mock.Call +} + +// IsBadRequestError is a helper method to define mock.On call +func (_e *InvalidEmailAddressError_Expecter) IsBadRequestError() *InvalidEmailAddressError_IsBadRequestError_Call { + return &InvalidEmailAddressError_IsBadRequestError_Call{Call: _e.mock.On("IsBadRequestError")} +} + +func (_c *InvalidEmailAddressError_IsBadRequestError_Call) Run(run func()) *InvalidEmailAddressError_IsBadRequestError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidEmailAddressError_IsBadRequestError_Call) Return(_a0 bool) *InvalidEmailAddressError_IsBadRequestError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidEmailAddressError_IsBadRequestError_Call) RunAndReturn(run func() bool) *InvalidEmailAddressError_IsBadRequestError_Call { + _c.Call.Return(run) + return _c +} + +// IsCustomError provides a mock function with given fields: +func (_m *InvalidEmailAddressError) IsCustomError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// InvalidEmailAddressError_IsCustomError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsCustomError' +type InvalidEmailAddressError_IsCustomError_Call struct { + *mock.Call +} + +// IsCustomError is a helper method to define mock.On call +func (_e *InvalidEmailAddressError_Expecter) IsCustomError() *InvalidEmailAddressError_IsCustomError_Call { + return &InvalidEmailAddressError_IsCustomError_Call{Call: _e.mock.On("IsCustomError")} +} + +func (_c *InvalidEmailAddressError_IsCustomError_Call) Run(run func()) *InvalidEmailAddressError_IsCustomError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidEmailAddressError_IsCustomError_Call) Return(_a0 bool) *InvalidEmailAddressError_IsCustomError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidEmailAddressError_IsCustomError_Call) RunAndReturn(run func() bool) *InvalidEmailAddressError_IsCustomError_Call { + _c.Call.Return(run) + return _c +} + +// IsInvalidEmailAddressError provides a mock function with given fields: +func (_m *InvalidEmailAddressError) IsInvalidEmailAddressError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// InvalidEmailAddressError_IsInvalidEmailAddressError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsInvalidEmailAddressError' +type InvalidEmailAddressError_IsInvalidEmailAddressError_Call struct { + *mock.Call +} + +// IsInvalidEmailAddressError is a helper method to define mock.On call +func (_e *InvalidEmailAddressError_Expecter) IsInvalidEmailAddressError() *InvalidEmailAddressError_IsInvalidEmailAddressError_Call { + return &InvalidEmailAddressError_IsInvalidEmailAddressError_Call{Call: _e.mock.On("IsInvalidEmailAddressError")} +} + +func (_c *InvalidEmailAddressError_IsInvalidEmailAddressError_Call) Run(run func()) *InvalidEmailAddressError_IsInvalidEmailAddressError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidEmailAddressError_IsInvalidEmailAddressError_Call) Return(_a0 bool) *InvalidEmailAddressError_IsInvalidEmailAddressError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidEmailAddressError_IsInvalidEmailAddressError_Call) RunAndReturn(run func() bool) *InvalidEmailAddressError_IsInvalidEmailAddressError_Call { + _c.Call.Return(run) + return _c +} + +// Message provides a mock function with given fields: +func (_m *InvalidEmailAddressError) Message() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// InvalidEmailAddressError_Message_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Message' +type InvalidEmailAddressError_Message_Call struct { + *mock.Call +} + +// Message is a helper method to define mock.On call +func (_e *InvalidEmailAddressError_Expecter) Message() *InvalidEmailAddressError_Message_Call { + return &InvalidEmailAddressError_Message_Call{Call: _e.mock.On("Message")} +} + +func (_c *InvalidEmailAddressError_Message_Call) Run(run func()) *InvalidEmailAddressError_Message_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidEmailAddressError_Message_Call) Return(_a0 string) *InvalidEmailAddressError_Message_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidEmailAddressError_Message_Call) RunAndReturn(run func() string) *InvalidEmailAddressError_Message_Call { + _c.Call.Return(run) + return _c +} + +// Status provides a mock function with given fields: +func (_m *InvalidEmailAddressError) Status() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// InvalidEmailAddressError_Status_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Status' +type InvalidEmailAddressError_Status_Call struct { + *mock.Call +} + +// Status is a helper method to define mock.On call +func (_e *InvalidEmailAddressError_Expecter) Status() *InvalidEmailAddressError_Status_Call { + return &InvalidEmailAddressError_Status_Call{Call: _e.mock.On("Status")} +} + +func (_c *InvalidEmailAddressError_Status_Call) Run(run func()) *InvalidEmailAddressError_Status_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidEmailAddressError_Status_Call) Return(_a0 int) *InvalidEmailAddressError_Status_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidEmailAddressError_Status_Call) RunAndReturn(run func() int) *InvalidEmailAddressError_Status_Call { + _c.Call.Return(run) + return _c +} + +// Unwrap provides a mock function with given fields: +func (_m *InvalidEmailAddressError) Unwrap() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InvalidEmailAddressError_Unwrap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unwrap' +type InvalidEmailAddressError_Unwrap_Call struct { + *mock.Call +} + +// Unwrap is a helper method to define mock.On call +func (_e *InvalidEmailAddressError_Expecter) Unwrap() *InvalidEmailAddressError_Unwrap_Call { + return &InvalidEmailAddressError_Unwrap_Call{Call: _e.mock.On("Unwrap")} +} + +func (_c *InvalidEmailAddressError_Unwrap_Call) Run(run func()) *InvalidEmailAddressError_Unwrap_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *InvalidEmailAddressError_Unwrap_Call) Return(_a0 error) *InvalidEmailAddressError_Unwrap_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InvalidEmailAddressError_Unwrap_Call) RunAndReturn(run func() error) *InvalidEmailAddressError_Unwrap_Call { + _c.Call.Return(run) + return _c +} + +// NewInvalidEmailAddressError creates a new instance of InvalidEmailAddressError. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInvalidEmailAddressError(t interface { + mock.TestingT + Cleanup(func()) +}) *InvalidEmailAddressError { + mock := &InvalidEmailAddressError{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/mocks/OrderElasticRepository.go b/internal/services/orderservice/mocks/OrderElasticRepository.go new file mode 100644 index 00000000..f123c2c1 --- /dev/null +++ b/internal/services/orderservice/mocks/OrderElasticRepository.go @@ -0,0 +1,415 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + read_models "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + mock "github.com/stretchr/testify/mock" + + utils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + + uuid "github.com/satori/go.uuid" +) + +// OrderElasticRepository is an autogenerated mock type for the OrderElasticRepository type +type OrderElasticRepository struct { + mock.Mock +} + +type OrderElasticRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *OrderElasticRepository) EXPECT() *OrderElasticRepository_Expecter { + return &OrderElasticRepository_Expecter{mock: &_m.Mock} +} + +// CreateOrder provides a mock function with given fields: ctx, order +func (_m *OrderElasticRepository) CreateOrder(ctx context.Context, order *read_models.OrderReadModel) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, order) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, order) + } + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) *read_models.OrderReadModel); ok { + r0 = rf(ctx, order) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *read_models.OrderReadModel) error); ok { + r1 = rf(ctx, order) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderElasticRepository_CreateOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateOrder' +type OrderElasticRepository_CreateOrder_Call struct { + *mock.Call +} + +// CreateOrder is a helper method to define mock.On call +// - ctx context.Context +// - order *read_models.OrderReadModel +func (_e *OrderElasticRepository_Expecter) CreateOrder(ctx interface{}, order interface{}) *OrderElasticRepository_CreateOrder_Call { + return &OrderElasticRepository_CreateOrder_Call{Call: _e.mock.On("CreateOrder", ctx, order)} +} + +func (_c *OrderElasticRepository_CreateOrder_Call) Run(run func(ctx context.Context, order *read_models.OrderReadModel)) *OrderElasticRepository_CreateOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*read_models.OrderReadModel)) + }) + return _c +} + +func (_c *OrderElasticRepository_CreateOrder_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *OrderElasticRepository_CreateOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderElasticRepository_CreateOrder_Call) RunAndReturn(run func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)) *OrderElasticRepository_CreateOrder_Call { + _c.Call.Return(run) + return _c +} + +// DeleteOrderByID provides a mock function with given fields: ctx, _a1 +func (_m *OrderElasticRepository) DeleteOrderByID(ctx context.Context, _a1 uuid.UUID) error { + ret := _m.Called(ctx, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok { + r0 = rf(ctx, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrderElasticRepository_DeleteOrderByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteOrderByID' +type OrderElasticRepository_DeleteOrderByID_Call struct { + *mock.Call +} + +// DeleteOrderByID is a helper method to define mock.On call +// - ctx context.Context +// - _a1 uuid.UUID +func (_e *OrderElasticRepository_Expecter) DeleteOrderByID(ctx interface{}, _a1 interface{}) *OrderElasticRepository_DeleteOrderByID_Call { + return &OrderElasticRepository_DeleteOrderByID_Call{Call: _e.mock.On("DeleteOrderByID", ctx, _a1)} +} + +func (_c *OrderElasticRepository_DeleteOrderByID_Call) Run(run func(ctx context.Context, _a1 uuid.UUID)) *OrderElasticRepository_DeleteOrderByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *OrderElasticRepository_DeleteOrderByID_Call) Return(_a0 error) *OrderElasticRepository_DeleteOrderByID_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderElasticRepository_DeleteOrderByID_Call) RunAndReturn(run func(context.Context, uuid.UUID) error) *OrderElasticRepository_DeleteOrderByID_Call { + _c.Call.Return(run) + return _c +} + +// GetAllOrders provides a mock function with given fields: ctx, listQuery +func (_m *OrderElasticRepository) GetAllOrders(ctx context.Context, listQuery *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error) { + ret := _m.Called(ctx, listQuery) + + var r0 *utils.ListResult[*read_models.OrderReadModel] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)); ok { + return rf(ctx, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) *utils.ListResult[*read_models.OrderReadModel]); ok { + r0 = rf(ctx, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*read_models.OrderReadModel]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *utils.ListQuery) error); ok { + r1 = rf(ctx, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderElasticRepository_GetAllOrders_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllOrders' +type OrderElasticRepository_GetAllOrders_Call struct { + *mock.Call +} + +// GetAllOrders is a helper method to define mock.On call +// - ctx context.Context +// - listQuery *utils.ListQuery +func (_e *OrderElasticRepository_Expecter) GetAllOrders(ctx interface{}, listQuery interface{}) *OrderElasticRepository_GetAllOrders_Call { + return &OrderElasticRepository_GetAllOrders_Call{Call: _e.mock.On("GetAllOrders", ctx, listQuery)} +} + +func (_c *OrderElasticRepository_GetAllOrders_Call) Run(run func(ctx context.Context, listQuery *utils.ListQuery)) *OrderElasticRepository_GetAllOrders_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*utils.ListQuery)) + }) + return _c +} + +func (_c *OrderElasticRepository_GetAllOrders_Call) Return(_a0 *utils.ListResult[*read_models.OrderReadModel], _a1 error) *OrderElasticRepository_GetAllOrders_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderElasticRepository_GetAllOrders_Call) RunAndReturn(run func(context.Context, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)) *OrderElasticRepository_GetAllOrders_Call { + _c.Call.Return(run) + return _c +} + +// GetOrderById provides a mock function with given fields: ctx, _a1 +func (_m *OrderElasticRepository) GetOrderById(ctx context.Context, _a1 uuid.UUID) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, _a1) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) *read_models.OrderReadModel); ok { + r0 = rf(ctx, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = rf(ctx, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderElasticRepository_GetOrderById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrderById' +type OrderElasticRepository_GetOrderById_Call struct { + *mock.Call +} + +// GetOrderById is a helper method to define mock.On call +// - ctx context.Context +// - _a1 uuid.UUID +func (_e *OrderElasticRepository_Expecter) GetOrderById(ctx interface{}, _a1 interface{}) *OrderElasticRepository_GetOrderById_Call { + return &OrderElasticRepository_GetOrderById_Call{Call: _e.mock.On("GetOrderById", ctx, _a1)} +} + +func (_c *OrderElasticRepository_GetOrderById_Call) Run(run func(ctx context.Context, _a1 uuid.UUID)) *OrderElasticRepository_GetOrderById_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *OrderElasticRepository_GetOrderById_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *OrderElasticRepository_GetOrderById_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderElasticRepository_GetOrderById_Call) RunAndReturn(run func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)) *OrderElasticRepository_GetOrderById_Call { + _c.Call.Return(run) + return _c +} + +// GetOrderByOrderId provides a mock function with given fields: ctx, orderId +func (_m *OrderElasticRepository) GetOrderByOrderId(ctx context.Context, orderId uuid.UUID) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, orderId) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, orderId) + } + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) *read_models.OrderReadModel); ok { + r0 = rf(ctx, orderId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = rf(ctx, orderId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderElasticRepository_GetOrderByOrderId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrderByOrderId' +type OrderElasticRepository_GetOrderByOrderId_Call struct { + *mock.Call +} + +// GetOrderByOrderId is a helper method to define mock.On call +// - ctx context.Context +// - orderId uuid.UUID +func (_e *OrderElasticRepository_Expecter) GetOrderByOrderId(ctx interface{}, orderId interface{}) *OrderElasticRepository_GetOrderByOrderId_Call { + return &OrderElasticRepository_GetOrderByOrderId_Call{Call: _e.mock.On("GetOrderByOrderId", ctx, orderId)} +} + +func (_c *OrderElasticRepository_GetOrderByOrderId_Call) Run(run func(ctx context.Context, orderId uuid.UUID)) *OrderElasticRepository_GetOrderByOrderId_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *OrderElasticRepository_GetOrderByOrderId_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *OrderElasticRepository_GetOrderByOrderId_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderElasticRepository_GetOrderByOrderId_Call) RunAndReturn(run func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)) *OrderElasticRepository_GetOrderByOrderId_Call { + _c.Call.Return(run) + return _c +} + +// SearchOrders provides a mock function with given fields: ctx, searchText, listQuery +func (_m *OrderElasticRepository) SearchOrders(ctx context.Context, searchText string, listQuery *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error) { + ret := _m.Called(ctx, searchText, listQuery) + + var r0 *utils.ListResult[*read_models.OrderReadModel] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)); ok { + return rf(ctx, searchText, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) *utils.ListResult[*read_models.OrderReadModel]); ok { + r0 = rf(ctx, searchText, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*read_models.OrderReadModel]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *utils.ListQuery) error); ok { + r1 = rf(ctx, searchText, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderElasticRepository_SearchOrders_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SearchOrders' +type OrderElasticRepository_SearchOrders_Call struct { + *mock.Call +} + +// SearchOrders is a helper method to define mock.On call +// - ctx context.Context +// - searchText string +// - listQuery *utils.ListQuery +func (_e *OrderElasticRepository_Expecter) SearchOrders(ctx interface{}, searchText interface{}, listQuery interface{}) *OrderElasticRepository_SearchOrders_Call { + return &OrderElasticRepository_SearchOrders_Call{Call: _e.mock.On("SearchOrders", ctx, searchText, listQuery)} +} + +func (_c *OrderElasticRepository_SearchOrders_Call) Run(run func(ctx context.Context, searchText string, listQuery *utils.ListQuery)) *OrderElasticRepository_SearchOrders_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*utils.ListQuery)) + }) + return _c +} + +func (_c *OrderElasticRepository_SearchOrders_Call) Return(_a0 *utils.ListResult[*read_models.OrderReadModel], _a1 error) *OrderElasticRepository_SearchOrders_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderElasticRepository_SearchOrders_Call) RunAndReturn(run func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)) *OrderElasticRepository_SearchOrders_Call { + _c.Call.Return(run) + return _c +} + +// UpdateOrder provides a mock function with given fields: ctx, order +func (_m *OrderElasticRepository) UpdateOrder(ctx context.Context, order *read_models.OrderReadModel) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, order) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, order) + } + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) *read_models.OrderReadModel); ok { + r0 = rf(ctx, order) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *read_models.OrderReadModel) error); ok { + r1 = rf(ctx, order) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderElasticRepository_UpdateOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateOrder' +type OrderElasticRepository_UpdateOrder_Call struct { + *mock.Call +} + +// UpdateOrder is a helper method to define mock.On call +// - ctx context.Context +// - order *read_models.OrderReadModel +func (_e *OrderElasticRepository_Expecter) UpdateOrder(ctx interface{}, order interface{}) *OrderElasticRepository_UpdateOrder_Call { + return &OrderElasticRepository_UpdateOrder_Call{Call: _e.mock.On("UpdateOrder", ctx, order)} +} + +func (_c *OrderElasticRepository_UpdateOrder_Call) Run(run func(ctx context.Context, order *read_models.OrderReadModel)) *OrderElasticRepository_UpdateOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*read_models.OrderReadModel)) + }) + return _c +} + +func (_c *OrderElasticRepository_UpdateOrder_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *OrderElasticRepository_UpdateOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderElasticRepository_UpdateOrder_Call) RunAndReturn(run func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)) *OrderElasticRepository_UpdateOrder_Call { + _c.Call.Return(run) + return _c +} + +// NewOrderElasticRepository creates a new instance of OrderElasticRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOrderElasticRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *OrderElasticRepository { + mock := &OrderElasticRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/mocks/OrderMongoRepository.go b/internal/services/orderservice/mocks/OrderMongoRepository.go new file mode 100644 index 00000000..afb35bcb --- /dev/null +++ b/internal/services/orderservice/mocks/OrderMongoRepository.go @@ -0,0 +1,415 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + read_models "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + mock "github.com/stretchr/testify/mock" + + utils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + + uuid "github.com/satori/go.uuid" +) + +// OrderMongoRepository is an autogenerated mock type for the OrderMongoRepository type +type OrderMongoRepository struct { + mock.Mock +} + +type OrderMongoRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *OrderMongoRepository) EXPECT() *OrderMongoRepository_Expecter { + return &OrderMongoRepository_Expecter{mock: &_m.Mock} +} + +// CreateOrder provides a mock function with given fields: ctx, order +func (_m *OrderMongoRepository) CreateOrder(ctx context.Context, order *read_models.OrderReadModel) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, order) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, order) + } + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) *read_models.OrderReadModel); ok { + r0 = rf(ctx, order) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *read_models.OrderReadModel) error); ok { + r1 = rf(ctx, order) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderMongoRepository_CreateOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateOrder' +type OrderMongoRepository_CreateOrder_Call struct { + *mock.Call +} + +// CreateOrder is a helper method to define mock.On call +// - ctx context.Context +// - order *read_models.OrderReadModel +func (_e *OrderMongoRepository_Expecter) CreateOrder(ctx interface{}, order interface{}) *OrderMongoRepository_CreateOrder_Call { + return &OrderMongoRepository_CreateOrder_Call{Call: _e.mock.On("CreateOrder", ctx, order)} +} + +func (_c *OrderMongoRepository_CreateOrder_Call) Run(run func(ctx context.Context, order *read_models.OrderReadModel)) *OrderMongoRepository_CreateOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*read_models.OrderReadModel)) + }) + return _c +} + +func (_c *OrderMongoRepository_CreateOrder_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *OrderMongoRepository_CreateOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderMongoRepository_CreateOrder_Call) RunAndReturn(run func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)) *OrderMongoRepository_CreateOrder_Call { + _c.Call.Return(run) + return _c +} + +// DeleteOrderByID provides a mock function with given fields: ctx, _a1 +func (_m *OrderMongoRepository) DeleteOrderByID(ctx context.Context, _a1 uuid.UUID) error { + ret := _m.Called(ctx, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok { + r0 = rf(ctx, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrderMongoRepository_DeleteOrderByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteOrderByID' +type OrderMongoRepository_DeleteOrderByID_Call struct { + *mock.Call +} + +// DeleteOrderByID is a helper method to define mock.On call +// - ctx context.Context +// - _a1 uuid.UUID +func (_e *OrderMongoRepository_Expecter) DeleteOrderByID(ctx interface{}, _a1 interface{}) *OrderMongoRepository_DeleteOrderByID_Call { + return &OrderMongoRepository_DeleteOrderByID_Call{Call: _e.mock.On("DeleteOrderByID", ctx, _a1)} +} + +func (_c *OrderMongoRepository_DeleteOrderByID_Call) Run(run func(ctx context.Context, _a1 uuid.UUID)) *OrderMongoRepository_DeleteOrderByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *OrderMongoRepository_DeleteOrderByID_Call) Return(_a0 error) *OrderMongoRepository_DeleteOrderByID_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderMongoRepository_DeleteOrderByID_Call) RunAndReturn(run func(context.Context, uuid.UUID) error) *OrderMongoRepository_DeleteOrderByID_Call { + _c.Call.Return(run) + return _c +} + +// GetAllOrders provides a mock function with given fields: ctx, listQuery +func (_m *OrderMongoRepository) GetAllOrders(ctx context.Context, listQuery *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error) { + ret := _m.Called(ctx, listQuery) + + var r0 *utils.ListResult[*read_models.OrderReadModel] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)); ok { + return rf(ctx, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) *utils.ListResult[*read_models.OrderReadModel]); ok { + r0 = rf(ctx, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*read_models.OrderReadModel]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *utils.ListQuery) error); ok { + r1 = rf(ctx, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderMongoRepository_GetAllOrders_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllOrders' +type OrderMongoRepository_GetAllOrders_Call struct { + *mock.Call +} + +// GetAllOrders is a helper method to define mock.On call +// - ctx context.Context +// - listQuery *utils.ListQuery +func (_e *OrderMongoRepository_Expecter) GetAllOrders(ctx interface{}, listQuery interface{}) *OrderMongoRepository_GetAllOrders_Call { + return &OrderMongoRepository_GetAllOrders_Call{Call: _e.mock.On("GetAllOrders", ctx, listQuery)} +} + +func (_c *OrderMongoRepository_GetAllOrders_Call) Run(run func(ctx context.Context, listQuery *utils.ListQuery)) *OrderMongoRepository_GetAllOrders_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*utils.ListQuery)) + }) + return _c +} + +func (_c *OrderMongoRepository_GetAllOrders_Call) Return(_a0 *utils.ListResult[*read_models.OrderReadModel], _a1 error) *OrderMongoRepository_GetAllOrders_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderMongoRepository_GetAllOrders_Call) RunAndReturn(run func(context.Context, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)) *OrderMongoRepository_GetAllOrders_Call { + _c.Call.Return(run) + return _c +} + +// GetOrderById provides a mock function with given fields: ctx, _a1 +func (_m *OrderMongoRepository) GetOrderById(ctx context.Context, _a1 uuid.UUID) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, _a1) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) *read_models.OrderReadModel); ok { + r0 = rf(ctx, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = rf(ctx, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderMongoRepository_GetOrderById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrderById' +type OrderMongoRepository_GetOrderById_Call struct { + *mock.Call +} + +// GetOrderById is a helper method to define mock.On call +// - ctx context.Context +// - _a1 uuid.UUID +func (_e *OrderMongoRepository_Expecter) GetOrderById(ctx interface{}, _a1 interface{}) *OrderMongoRepository_GetOrderById_Call { + return &OrderMongoRepository_GetOrderById_Call{Call: _e.mock.On("GetOrderById", ctx, _a1)} +} + +func (_c *OrderMongoRepository_GetOrderById_Call) Run(run func(ctx context.Context, _a1 uuid.UUID)) *OrderMongoRepository_GetOrderById_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *OrderMongoRepository_GetOrderById_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *OrderMongoRepository_GetOrderById_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderMongoRepository_GetOrderById_Call) RunAndReturn(run func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)) *OrderMongoRepository_GetOrderById_Call { + _c.Call.Return(run) + return _c +} + +// GetOrderByOrderId provides a mock function with given fields: ctx, orderId +func (_m *OrderMongoRepository) GetOrderByOrderId(ctx context.Context, orderId uuid.UUID) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, orderId) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, orderId) + } + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) *read_models.OrderReadModel); ok { + r0 = rf(ctx, orderId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = rf(ctx, orderId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderMongoRepository_GetOrderByOrderId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrderByOrderId' +type OrderMongoRepository_GetOrderByOrderId_Call struct { + *mock.Call +} + +// GetOrderByOrderId is a helper method to define mock.On call +// - ctx context.Context +// - orderId uuid.UUID +func (_e *OrderMongoRepository_Expecter) GetOrderByOrderId(ctx interface{}, orderId interface{}) *OrderMongoRepository_GetOrderByOrderId_Call { + return &OrderMongoRepository_GetOrderByOrderId_Call{Call: _e.mock.On("GetOrderByOrderId", ctx, orderId)} +} + +func (_c *OrderMongoRepository_GetOrderByOrderId_Call) Run(run func(ctx context.Context, orderId uuid.UUID)) *OrderMongoRepository_GetOrderByOrderId_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *OrderMongoRepository_GetOrderByOrderId_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *OrderMongoRepository_GetOrderByOrderId_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderMongoRepository_GetOrderByOrderId_Call) RunAndReturn(run func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)) *OrderMongoRepository_GetOrderByOrderId_Call { + _c.Call.Return(run) + return _c +} + +// SearchOrders provides a mock function with given fields: ctx, searchText, listQuery +func (_m *OrderMongoRepository) SearchOrders(ctx context.Context, searchText string, listQuery *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error) { + ret := _m.Called(ctx, searchText, listQuery) + + var r0 *utils.ListResult[*read_models.OrderReadModel] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)); ok { + return rf(ctx, searchText, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) *utils.ListResult[*read_models.OrderReadModel]); ok { + r0 = rf(ctx, searchText, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*read_models.OrderReadModel]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *utils.ListQuery) error); ok { + r1 = rf(ctx, searchText, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderMongoRepository_SearchOrders_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SearchOrders' +type OrderMongoRepository_SearchOrders_Call struct { + *mock.Call +} + +// SearchOrders is a helper method to define mock.On call +// - ctx context.Context +// - searchText string +// - listQuery *utils.ListQuery +func (_e *OrderMongoRepository_Expecter) SearchOrders(ctx interface{}, searchText interface{}, listQuery interface{}) *OrderMongoRepository_SearchOrders_Call { + return &OrderMongoRepository_SearchOrders_Call{Call: _e.mock.On("SearchOrders", ctx, searchText, listQuery)} +} + +func (_c *OrderMongoRepository_SearchOrders_Call) Run(run func(ctx context.Context, searchText string, listQuery *utils.ListQuery)) *OrderMongoRepository_SearchOrders_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*utils.ListQuery)) + }) + return _c +} + +func (_c *OrderMongoRepository_SearchOrders_Call) Return(_a0 *utils.ListResult[*read_models.OrderReadModel], _a1 error) *OrderMongoRepository_SearchOrders_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderMongoRepository_SearchOrders_Call) RunAndReturn(run func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)) *OrderMongoRepository_SearchOrders_Call { + _c.Call.Return(run) + return _c +} + +// UpdateOrder provides a mock function with given fields: ctx, order +func (_m *OrderMongoRepository) UpdateOrder(ctx context.Context, order *read_models.OrderReadModel) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, order) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, order) + } + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) *read_models.OrderReadModel); ok { + r0 = rf(ctx, order) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *read_models.OrderReadModel) error); ok { + r1 = rf(ctx, order) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrderMongoRepository_UpdateOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateOrder' +type OrderMongoRepository_UpdateOrder_Call struct { + *mock.Call +} + +// UpdateOrder is a helper method to define mock.On call +// - ctx context.Context +// - order *read_models.OrderReadModel +func (_e *OrderMongoRepository_Expecter) UpdateOrder(ctx interface{}, order interface{}) *OrderMongoRepository_UpdateOrder_Call { + return &OrderMongoRepository_UpdateOrder_Call{Call: _e.mock.On("UpdateOrder", ctx, order)} +} + +func (_c *OrderMongoRepository_UpdateOrder_Call) Run(run func(ctx context.Context, order *read_models.OrderReadModel)) *OrderMongoRepository_UpdateOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*read_models.OrderReadModel)) + }) + return _c +} + +func (_c *OrderMongoRepository_UpdateOrder_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *OrderMongoRepository_UpdateOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrderMongoRepository_UpdateOrder_Call) RunAndReturn(run func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)) *OrderMongoRepository_UpdateOrder_Call { + _c.Call.Return(run) + return _c +} + +// NewOrderMongoRepository creates a new instance of OrderMongoRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOrderMongoRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *OrderMongoRepository { + mock := &OrderMongoRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/mocks/OrderNotFoundError.go b/internal/services/orderservice/mocks/OrderNotFoundError.go new file mode 100644 index 00000000..dd8f07c9 --- /dev/null +++ b/internal/services/orderservice/mocks/OrderNotFoundError.go @@ -0,0 +1,398 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + fmt "fmt" + + mock "github.com/stretchr/testify/mock" +) + +// OrderNotFoundError is an autogenerated mock type for the OrderNotFoundError type +type OrderNotFoundError struct { + mock.Mock +} + +type OrderNotFoundError_Expecter struct { + mock *mock.Mock +} + +func (_m *OrderNotFoundError) EXPECT() *OrderNotFoundError_Expecter { + return &OrderNotFoundError_Expecter{mock: &_m.Mock} +} + +// Cause provides a mock function with given fields: +func (_m *OrderNotFoundError) Cause() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrderNotFoundError_Cause_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Cause' +type OrderNotFoundError_Cause_Call struct { + *mock.Call +} + +// Cause is a helper method to define mock.On call +func (_e *OrderNotFoundError_Expecter) Cause() *OrderNotFoundError_Cause_Call { + return &OrderNotFoundError_Cause_Call{Call: _e.mock.On("Cause")} +} + +func (_c *OrderNotFoundError_Cause_Call) Run(run func()) *OrderNotFoundError_Cause_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderNotFoundError_Cause_Call) Return(_a0 error) *OrderNotFoundError_Cause_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderNotFoundError_Cause_Call) RunAndReturn(run func() error) *OrderNotFoundError_Cause_Call { + _c.Call.Return(run) + return _c +} + +// Error provides a mock function with given fields: +func (_m *OrderNotFoundError) Error() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// OrderNotFoundError_Error_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Error' +type OrderNotFoundError_Error_Call struct { + *mock.Call +} + +// Error is a helper method to define mock.On call +func (_e *OrderNotFoundError_Expecter) Error() *OrderNotFoundError_Error_Call { + return &OrderNotFoundError_Error_Call{Call: _e.mock.On("Error")} +} + +func (_c *OrderNotFoundError_Error_Call) Run(run func()) *OrderNotFoundError_Error_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderNotFoundError_Error_Call) Return(_a0 string) *OrderNotFoundError_Error_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderNotFoundError_Error_Call) RunAndReturn(run func() string) *OrderNotFoundError_Error_Call { + _c.Call.Return(run) + return _c +} + +// Format provides a mock function with given fields: f, verb +func (_m *OrderNotFoundError) Format(f fmt.State, verb rune) { + _m.Called(f, verb) +} + +// OrderNotFoundError_Format_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Format' +type OrderNotFoundError_Format_Call struct { + *mock.Call +} + +// Format is a helper method to define mock.On call +// - f fmt.State +// - verb rune +func (_e *OrderNotFoundError_Expecter) Format(f interface{}, verb interface{}) *OrderNotFoundError_Format_Call { + return &OrderNotFoundError_Format_Call{Call: _e.mock.On("Format", f, verb)} +} + +func (_c *OrderNotFoundError_Format_Call) Run(run func(f fmt.State, verb rune)) *OrderNotFoundError_Format_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(fmt.State), args[1].(rune)) + }) + return _c +} + +func (_c *OrderNotFoundError_Format_Call) Return() *OrderNotFoundError_Format_Call { + _c.Call.Return() + return _c +} + +func (_c *OrderNotFoundError_Format_Call) RunAndReturn(run func(fmt.State, rune)) *OrderNotFoundError_Format_Call { + _c.Call.Return(run) + return _c +} + +// IsCustomError provides a mock function with given fields: +func (_m *OrderNotFoundError) IsCustomError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// OrderNotFoundError_IsCustomError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsCustomError' +type OrderNotFoundError_IsCustomError_Call struct { + *mock.Call +} + +// IsCustomError is a helper method to define mock.On call +func (_e *OrderNotFoundError_Expecter) IsCustomError() *OrderNotFoundError_IsCustomError_Call { + return &OrderNotFoundError_IsCustomError_Call{Call: _e.mock.On("IsCustomError")} +} + +func (_c *OrderNotFoundError_IsCustomError_Call) Run(run func()) *OrderNotFoundError_IsCustomError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderNotFoundError_IsCustomError_Call) Return(_a0 bool) *OrderNotFoundError_IsCustomError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderNotFoundError_IsCustomError_Call) RunAndReturn(run func() bool) *OrderNotFoundError_IsCustomError_Call { + _c.Call.Return(run) + return _c +} + +// IsNotFoundError provides a mock function with given fields: +func (_m *OrderNotFoundError) IsNotFoundError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// OrderNotFoundError_IsNotFoundError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsNotFoundError' +type OrderNotFoundError_IsNotFoundError_Call struct { + *mock.Call +} + +// IsNotFoundError is a helper method to define mock.On call +func (_e *OrderNotFoundError_Expecter) IsNotFoundError() *OrderNotFoundError_IsNotFoundError_Call { + return &OrderNotFoundError_IsNotFoundError_Call{Call: _e.mock.On("IsNotFoundError")} +} + +func (_c *OrderNotFoundError_IsNotFoundError_Call) Run(run func()) *OrderNotFoundError_IsNotFoundError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderNotFoundError_IsNotFoundError_Call) Return(_a0 bool) *OrderNotFoundError_IsNotFoundError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderNotFoundError_IsNotFoundError_Call) RunAndReturn(run func() bool) *OrderNotFoundError_IsNotFoundError_Call { + _c.Call.Return(run) + return _c +} + +// IsOrderNotFoundError provides a mock function with given fields: +func (_m *OrderNotFoundError) IsOrderNotFoundError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// OrderNotFoundError_IsOrderNotFoundError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsOrderNotFoundError' +type OrderNotFoundError_IsOrderNotFoundError_Call struct { + *mock.Call +} + +// IsOrderNotFoundError is a helper method to define mock.On call +func (_e *OrderNotFoundError_Expecter) IsOrderNotFoundError() *OrderNotFoundError_IsOrderNotFoundError_Call { + return &OrderNotFoundError_IsOrderNotFoundError_Call{Call: _e.mock.On("IsOrderNotFoundError")} +} + +func (_c *OrderNotFoundError_IsOrderNotFoundError_Call) Run(run func()) *OrderNotFoundError_IsOrderNotFoundError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderNotFoundError_IsOrderNotFoundError_Call) Return(_a0 bool) *OrderNotFoundError_IsOrderNotFoundError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderNotFoundError_IsOrderNotFoundError_Call) RunAndReturn(run func() bool) *OrderNotFoundError_IsOrderNotFoundError_Call { + _c.Call.Return(run) + return _c +} + +// Message provides a mock function with given fields: +func (_m *OrderNotFoundError) Message() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// OrderNotFoundError_Message_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Message' +type OrderNotFoundError_Message_Call struct { + *mock.Call +} + +// Message is a helper method to define mock.On call +func (_e *OrderNotFoundError_Expecter) Message() *OrderNotFoundError_Message_Call { + return &OrderNotFoundError_Message_Call{Call: _e.mock.On("Message")} +} + +func (_c *OrderNotFoundError_Message_Call) Run(run func()) *OrderNotFoundError_Message_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderNotFoundError_Message_Call) Return(_a0 string) *OrderNotFoundError_Message_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderNotFoundError_Message_Call) RunAndReturn(run func() string) *OrderNotFoundError_Message_Call { + _c.Call.Return(run) + return _c +} + +// Status provides a mock function with given fields: +func (_m *OrderNotFoundError) Status() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// OrderNotFoundError_Status_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Status' +type OrderNotFoundError_Status_Call struct { + *mock.Call +} + +// Status is a helper method to define mock.On call +func (_e *OrderNotFoundError_Expecter) Status() *OrderNotFoundError_Status_Call { + return &OrderNotFoundError_Status_Call{Call: _e.mock.On("Status")} +} + +func (_c *OrderNotFoundError_Status_Call) Run(run func()) *OrderNotFoundError_Status_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderNotFoundError_Status_Call) Return(_a0 int) *OrderNotFoundError_Status_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderNotFoundError_Status_Call) RunAndReturn(run func() int) *OrderNotFoundError_Status_Call { + _c.Call.Return(run) + return _c +} + +// Unwrap provides a mock function with given fields: +func (_m *OrderNotFoundError) Unwrap() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrderNotFoundError_Unwrap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unwrap' +type OrderNotFoundError_Unwrap_Call struct { + *mock.Call +} + +// Unwrap is a helper method to define mock.On call +func (_e *OrderNotFoundError_Expecter) Unwrap() *OrderNotFoundError_Unwrap_Call { + return &OrderNotFoundError_Unwrap_Call{Call: _e.mock.On("Unwrap")} +} + +func (_c *OrderNotFoundError_Unwrap_Call) Run(run func()) *OrderNotFoundError_Unwrap_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderNotFoundError_Unwrap_Call) Return(_a0 error) *OrderNotFoundError_Unwrap_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderNotFoundError_Unwrap_Call) RunAndReturn(run func() error) *OrderNotFoundError_Unwrap_Call { + _c.Call.Return(run) + return _c +} + +// NewOrderNotFoundError creates a new instance of OrderNotFoundError. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOrderNotFoundError(t interface { + mock.TestingT + Cleanup(func()) +}) *OrderNotFoundError { + mock := &OrderNotFoundError{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/mocks/OrderShopItemsRequiredError.go b/internal/services/orderservice/mocks/OrderShopItemsRequiredError.go new file mode 100644 index 00000000..ef9f05b5 --- /dev/null +++ b/internal/services/orderservice/mocks/OrderShopItemsRequiredError.go @@ -0,0 +1,398 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + fmt "fmt" + + mock "github.com/stretchr/testify/mock" +) + +// OrderShopItemsRequiredError is an autogenerated mock type for the OrderShopItemsRequiredError type +type OrderShopItemsRequiredError struct { + mock.Mock +} + +type OrderShopItemsRequiredError_Expecter struct { + mock *mock.Mock +} + +func (_m *OrderShopItemsRequiredError) EXPECT() *OrderShopItemsRequiredError_Expecter { + return &OrderShopItemsRequiredError_Expecter{mock: &_m.Mock} +} + +// Cause provides a mock function with given fields: +func (_m *OrderShopItemsRequiredError) Cause() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrderShopItemsRequiredError_Cause_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Cause' +type OrderShopItemsRequiredError_Cause_Call struct { + *mock.Call +} + +// Cause is a helper method to define mock.On call +func (_e *OrderShopItemsRequiredError_Expecter) Cause() *OrderShopItemsRequiredError_Cause_Call { + return &OrderShopItemsRequiredError_Cause_Call{Call: _e.mock.On("Cause")} +} + +func (_c *OrderShopItemsRequiredError_Cause_Call) Run(run func()) *OrderShopItemsRequiredError_Cause_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderShopItemsRequiredError_Cause_Call) Return(_a0 error) *OrderShopItemsRequiredError_Cause_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderShopItemsRequiredError_Cause_Call) RunAndReturn(run func() error) *OrderShopItemsRequiredError_Cause_Call { + _c.Call.Return(run) + return _c +} + +// Error provides a mock function with given fields: +func (_m *OrderShopItemsRequiredError) Error() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// OrderShopItemsRequiredError_Error_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Error' +type OrderShopItemsRequiredError_Error_Call struct { + *mock.Call +} + +// Error is a helper method to define mock.On call +func (_e *OrderShopItemsRequiredError_Expecter) Error() *OrderShopItemsRequiredError_Error_Call { + return &OrderShopItemsRequiredError_Error_Call{Call: _e.mock.On("Error")} +} + +func (_c *OrderShopItemsRequiredError_Error_Call) Run(run func()) *OrderShopItemsRequiredError_Error_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderShopItemsRequiredError_Error_Call) Return(_a0 string) *OrderShopItemsRequiredError_Error_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderShopItemsRequiredError_Error_Call) RunAndReturn(run func() string) *OrderShopItemsRequiredError_Error_Call { + _c.Call.Return(run) + return _c +} + +// Format provides a mock function with given fields: f, verb +func (_m *OrderShopItemsRequiredError) Format(f fmt.State, verb rune) { + _m.Called(f, verb) +} + +// OrderShopItemsRequiredError_Format_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Format' +type OrderShopItemsRequiredError_Format_Call struct { + *mock.Call +} + +// Format is a helper method to define mock.On call +// - f fmt.State +// - verb rune +func (_e *OrderShopItemsRequiredError_Expecter) Format(f interface{}, verb interface{}) *OrderShopItemsRequiredError_Format_Call { + return &OrderShopItemsRequiredError_Format_Call{Call: _e.mock.On("Format", f, verb)} +} + +func (_c *OrderShopItemsRequiredError_Format_Call) Run(run func(f fmt.State, verb rune)) *OrderShopItemsRequiredError_Format_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(fmt.State), args[1].(rune)) + }) + return _c +} + +func (_c *OrderShopItemsRequiredError_Format_Call) Return() *OrderShopItemsRequiredError_Format_Call { + _c.Call.Return() + return _c +} + +func (_c *OrderShopItemsRequiredError_Format_Call) RunAndReturn(run func(fmt.State, rune)) *OrderShopItemsRequiredError_Format_Call { + _c.Call.Return(run) + return _c +} + +// IsBadRequestError provides a mock function with given fields: +func (_m *OrderShopItemsRequiredError) IsBadRequestError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// OrderShopItemsRequiredError_IsBadRequestError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsBadRequestError' +type OrderShopItemsRequiredError_IsBadRequestError_Call struct { + *mock.Call +} + +// IsBadRequestError is a helper method to define mock.On call +func (_e *OrderShopItemsRequiredError_Expecter) IsBadRequestError() *OrderShopItemsRequiredError_IsBadRequestError_Call { + return &OrderShopItemsRequiredError_IsBadRequestError_Call{Call: _e.mock.On("IsBadRequestError")} +} + +func (_c *OrderShopItemsRequiredError_IsBadRequestError_Call) Run(run func()) *OrderShopItemsRequiredError_IsBadRequestError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderShopItemsRequiredError_IsBadRequestError_Call) Return(_a0 bool) *OrderShopItemsRequiredError_IsBadRequestError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderShopItemsRequiredError_IsBadRequestError_Call) RunAndReturn(run func() bool) *OrderShopItemsRequiredError_IsBadRequestError_Call { + _c.Call.Return(run) + return _c +} + +// IsCustomError provides a mock function with given fields: +func (_m *OrderShopItemsRequiredError) IsCustomError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// OrderShopItemsRequiredError_IsCustomError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsCustomError' +type OrderShopItemsRequiredError_IsCustomError_Call struct { + *mock.Call +} + +// IsCustomError is a helper method to define mock.On call +func (_e *OrderShopItemsRequiredError_Expecter) IsCustomError() *OrderShopItemsRequiredError_IsCustomError_Call { + return &OrderShopItemsRequiredError_IsCustomError_Call{Call: _e.mock.On("IsCustomError")} +} + +func (_c *OrderShopItemsRequiredError_IsCustomError_Call) Run(run func()) *OrderShopItemsRequiredError_IsCustomError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderShopItemsRequiredError_IsCustomError_Call) Return(_a0 bool) *OrderShopItemsRequiredError_IsCustomError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderShopItemsRequiredError_IsCustomError_Call) RunAndReturn(run func() bool) *OrderShopItemsRequiredError_IsCustomError_Call { + _c.Call.Return(run) + return _c +} + +// IsOrderShopItemsRequiredError provides a mock function with given fields: +func (_m *OrderShopItemsRequiredError) IsOrderShopItemsRequiredError() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsOrderShopItemsRequiredError' +type OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call struct { + *mock.Call +} + +// IsOrderShopItemsRequiredError is a helper method to define mock.On call +func (_e *OrderShopItemsRequiredError_Expecter) IsOrderShopItemsRequiredError() *OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call { + return &OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call{Call: _e.mock.On("IsOrderShopItemsRequiredError")} +} + +func (_c *OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call) Run(run func()) *OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call) Return(_a0 bool) *OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call) RunAndReturn(run func() bool) *OrderShopItemsRequiredError_IsOrderShopItemsRequiredError_Call { + _c.Call.Return(run) + return _c +} + +// Message provides a mock function with given fields: +func (_m *OrderShopItemsRequiredError) Message() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// OrderShopItemsRequiredError_Message_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Message' +type OrderShopItemsRequiredError_Message_Call struct { + *mock.Call +} + +// Message is a helper method to define mock.On call +func (_e *OrderShopItemsRequiredError_Expecter) Message() *OrderShopItemsRequiredError_Message_Call { + return &OrderShopItemsRequiredError_Message_Call{Call: _e.mock.On("Message")} +} + +func (_c *OrderShopItemsRequiredError_Message_Call) Run(run func()) *OrderShopItemsRequiredError_Message_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderShopItemsRequiredError_Message_Call) Return(_a0 string) *OrderShopItemsRequiredError_Message_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderShopItemsRequiredError_Message_Call) RunAndReturn(run func() string) *OrderShopItemsRequiredError_Message_Call { + _c.Call.Return(run) + return _c +} + +// Status provides a mock function with given fields: +func (_m *OrderShopItemsRequiredError) Status() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// OrderShopItemsRequiredError_Status_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Status' +type OrderShopItemsRequiredError_Status_Call struct { + *mock.Call +} + +// Status is a helper method to define mock.On call +func (_e *OrderShopItemsRequiredError_Expecter) Status() *OrderShopItemsRequiredError_Status_Call { + return &OrderShopItemsRequiredError_Status_Call{Call: _e.mock.On("Status")} +} + +func (_c *OrderShopItemsRequiredError_Status_Call) Run(run func()) *OrderShopItemsRequiredError_Status_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderShopItemsRequiredError_Status_Call) Return(_a0 int) *OrderShopItemsRequiredError_Status_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderShopItemsRequiredError_Status_Call) RunAndReturn(run func() int) *OrderShopItemsRequiredError_Status_Call { + _c.Call.Return(run) + return _c +} + +// Unwrap provides a mock function with given fields: +func (_m *OrderShopItemsRequiredError) Unwrap() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// OrderShopItemsRequiredError_Unwrap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unwrap' +type OrderShopItemsRequiredError_Unwrap_Call struct { + *mock.Call +} + +// Unwrap is a helper method to define mock.On call +func (_e *OrderShopItemsRequiredError_Expecter) Unwrap() *OrderShopItemsRequiredError_Unwrap_Call { + return &OrderShopItemsRequiredError_Unwrap_Call{Call: _e.mock.On("Unwrap")} +} + +func (_c *OrderShopItemsRequiredError_Unwrap_Call) Run(run func()) *OrderShopItemsRequiredError_Unwrap_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *OrderShopItemsRequiredError_Unwrap_Call) Return(_a0 error) *OrderShopItemsRequiredError_Unwrap_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *OrderShopItemsRequiredError_Unwrap_Call) RunAndReturn(run func() error) *OrderShopItemsRequiredError_Unwrap_Call { + _c.Call.Return(run) + return _c +} + +// NewOrderShopItemsRequiredError creates a new instance of OrderShopItemsRequiredError. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOrderShopItemsRequiredError(t interface { + mock.TestingT + Cleanup(func()) +}) *OrderShopItemsRequiredError { + mock := &OrderShopItemsRequiredError{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/mocks/OrdersServiceClient.go b/internal/services/orderservice/mocks/OrdersServiceClient.go new file mode 100644 index 00000000..c8757b70 --- /dev/null +++ b/internal/services/orderservice/mocks/OrdersServiceClient.go @@ -0,0 +1,390 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/grpc/genproto" +) + +// OrdersServiceClient is an autogenerated mock type for the OrdersServiceClient type +type OrdersServiceClient struct { + mock.Mock +} + +type OrdersServiceClient_Expecter struct { + mock *mock.Mock +} + +func (_m *OrdersServiceClient) EXPECT() *OrdersServiceClient_Expecter { + return &OrdersServiceClient_Expecter{mock: &_m.Mock} +} + +// CreateOrder provides a mock function with given fields: ctx, in, opts +func (_m *OrdersServiceClient) CreateOrder(ctx context.Context, in *orders_service.CreateOrderReq, opts ...grpc.CallOption) (*orders_service.CreateOrderRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *orders_service.CreateOrderRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.CreateOrderReq, ...grpc.CallOption) (*orders_service.CreateOrderRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.CreateOrderReq, ...grpc.CallOption) *orders_service.CreateOrderRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.CreateOrderRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.CreateOrderReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceClient_CreateOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateOrder' +type OrdersServiceClient_CreateOrder_Call struct { + *mock.Call +} + +// CreateOrder is a helper method to define mock.On call +// - ctx context.Context +// - in *orders_service.CreateOrderReq +// - opts ...grpc.CallOption +func (_e *OrdersServiceClient_Expecter) CreateOrder(ctx interface{}, in interface{}, opts ...interface{}) *OrdersServiceClient_CreateOrder_Call { + return &OrdersServiceClient_CreateOrder_Call{Call: _e.mock.On("CreateOrder", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *OrdersServiceClient_CreateOrder_Call) Run(run func(ctx context.Context, in *orders_service.CreateOrderReq, opts ...grpc.CallOption)) *OrdersServiceClient_CreateOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*orders_service.CreateOrderReq), variadicArgs...) + }) + return _c +} + +func (_c *OrdersServiceClient_CreateOrder_Call) Return(_a0 *orders_service.CreateOrderRes, _a1 error) *OrdersServiceClient_CreateOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceClient_CreateOrder_Call) RunAndReturn(run func(context.Context, *orders_service.CreateOrderReq, ...grpc.CallOption) (*orders_service.CreateOrderRes, error)) *OrdersServiceClient_CreateOrder_Call { + _c.Call.Return(run) + return _c +} + +// GetOrderByID provides a mock function with given fields: ctx, in, opts +func (_m *OrdersServiceClient) GetOrderByID(ctx context.Context, in *orders_service.GetOrderByIDReq, opts ...grpc.CallOption) (*orders_service.GetOrderByIDRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *orders_service.GetOrderByIDRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.GetOrderByIDReq, ...grpc.CallOption) (*orders_service.GetOrderByIDRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.GetOrderByIDReq, ...grpc.CallOption) *orders_service.GetOrderByIDRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.GetOrderByIDRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.GetOrderByIDReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceClient_GetOrderByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrderByID' +type OrdersServiceClient_GetOrderByID_Call struct { + *mock.Call +} + +// GetOrderByID is a helper method to define mock.On call +// - ctx context.Context +// - in *orders_service.GetOrderByIDReq +// - opts ...grpc.CallOption +func (_e *OrdersServiceClient_Expecter) GetOrderByID(ctx interface{}, in interface{}, opts ...interface{}) *OrdersServiceClient_GetOrderByID_Call { + return &OrdersServiceClient_GetOrderByID_Call{Call: _e.mock.On("GetOrderByID", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *OrdersServiceClient_GetOrderByID_Call) Run(run func(ctx context.Context, in *orders_service.GetOrderByIDReq, opts ...grpc.CallOption)) *OrdersServiceClient_GetOrderByID_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*orders_service.GetOrderByIDReq), variadicArgs...) + }) + return _c +} + +func (_c *OrdersServiceClient_GetOrderByID_Call) Return(_a0 *orders_service.GetOrderByIDRes, _a1 error) *OrdersServiceClient_GetOrderByID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceClient_GetOrderByID_Call) RunAndReturn(run func(context.Context, *orders_service.GetOrderByIDReq, ...grpc.CallOption) (*orders_service.GetOrderByIDRes, error)) *OrdersServiceClient_GetOrderByID_Call { + _c.Call.Return(run) + return _c +} + +// GetOrders provides a mock function with given fields: ctx, in, opts +func (_m *OrdersServiceClient) GetOrders(ctx context.Context, in *orders_service.GetOrdersReq, opts ...grpc.CallOption) (*orders_service.GetOrdersRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *orders_service.GetOrdersRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.GetOrdersReq, ...grpc.CallOption) (*orders_service.GetOrdersRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.GetOrdersReq, ...grpc.CallOption) *orders_service.GetOrdersRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.GetOrdersRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.GetOrdersReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceClient_GetOrders_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrders' +type OrdersServiceClient_GetOrders_Call struct { + *mock.Call +} + +// GetOrders is a helper method to define mock.On call +// - ctx context.Context +// - in *orders_service.GetOrdersReq +// - opts ...grpc.CallOption +func (_e *OrdersServiceClient_Expecter) GetOrders(ctx interface{}, in interface{}, opts ...interface{}) *OrdersServiceClient_GetOrders_Call { + return &OrdersServiceClient_GetOrders_Call{Call: _e.mock.On("GetOrders", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *OrdersServiceClient_GetOrders_Call) Run(run func(ctx context.Context, in *orders_service.GetOrdersReq, opts ...grpc.CallOption)) *OrdersServiceClient_GetOrders_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*orders_service.GetOrdersReq), variadicArgs...) + }) + return _c +} + +func (_c *OrdersServiceClient_GetOrders_Call) Return(_a0 *orders_service.GetOrdersRes, _a1 error) *OrdersServiceClient_GetOrders_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceClient_GetOrders_Call) RunAndReturn(run func(context.Context, *orders_service.GetOrdersReq, ...grpc.CallOption) (*orders_service.GetOrdersRes, error)) *OrdersServiceClient_GetOrders_Call { + _c.Call.Return(run) + return _c +} + +// SubmitOrder provides a mock function with given fields: ctx, in, opts +func (_m *OrdersServiceClient) SubmitOrder(ctx context.Context, in *orders_service.SubmitOrderReq, opts ...grpc.CallOption) (*orders_service.SubmitOrderRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *orders_service.SubmitOrderRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.SubmitOrderReq, ...grpc.CallOption) (*orders_service.SubmitOrderRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.SubmitOrderReq, ...grpc.CallOption) *orders_service.SubmitOrderRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.SubmitOrderRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.SubmitOrderReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceClient_SubmitOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubmitOrder' +type OrdersServiceClient_SubmitOrder_Call struct { + *mock.Call +} + +// SubmitOrder is a helper method to define mock.On call +// - ctx context.Context +// - in *orders_service.SubmitOrderReq +// - opts ...grpc.CallOption +func (_e *OrdersServiceClient_Expecter) SubmitOrder(ctx interface{}, in interface{}, opts ...interface{}) *OrdersServiceClient_SubmitOrder_Call { + return &OrdersServiceClient_SubmitOrder_Call{Call: _e.mock.On("SubmitOrder", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *OrdersServiceClient_SubmitOrder_Call) Run(run func(ctx context.Context, in *orders_service.SubmitOrderReq, opts ...grpc.CallOption)) *OrdersServiceClient_SubmitOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*orders_service.SubmitOrderReq), variadicArgs...) + }) + return _c +} + +func (_c *OrdersServiceClient_SubmitOrder_Call) Return(_a0 *orders_service.SubmitOrderRes, _a1 error) *OrdersServiceClient_SubmitOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceClient_SubmitOrder_Call) RunAndReturn(run func(context.Context, *orders_service.SubmitOrderReq, ...grpc.CallOption) (*orders_service.SubmitOrderRes, error)) *OrdersServiceClient_SubmitOrder_Call { + _c.Call.Return(run) + return _c +} + +// UpdateShoppingCart provides a mock function with given fields: ctx, in, opts +func (_m *OrdersServiceClient) UpdateShoppingCart(ctx context.Context, in *orders_service.UpdateShoppingCartReq, opts ...grpc.CallOption) (*orders_service.UpdateShoppingCartRes, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *orders_service.UpdateShoppingCartRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.UpdateShoppingCartReq, ...grpc.CallOption) (*orders_service.UpdateShoppingCartRes, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.UpdateShoppingCartReq, ...grpc.CallOption) *orders_service.UpdateShoppingCartRes); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.UpdateShoppingCartRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.UpdateShoppingCartReq, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceClient_UpdateShoppingCart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateShoppingCart' +type OrdersServiceClient_UpdateShoppingCart_Call struct { + *mock.Call +} + +// UpdateShoppingCart is a helper method to define mock.On call +// - ctx context.Context +// - in *orders_service.UpdateShoppingCartReq +// - opts ...grpc.CallOption +func (_e *OrdersServiceClient_Expecter) UpdateShoppingCart(ctx interface{}, in interface{}, opts ...interface{}) *OrdersServiceClient_UpdateShoppingCart_Call { + return &OrdersServiceClient_UpdateShoppingCart_Call{Call: _e.mock.On("UpdateShoppingCart", + append([]interface{}{ctx, in}, opts...)...)} +} + +func (_c *OrdersServiceClient_UpdateShoppingCart_Call) Run(run func(ctx context.Context, in *orders_service.UpdateShoppingCartReq, opts ...grpc.CallOption)) *OrdersServiceClient_UpdateShoppingCart_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]grpc.CallOption, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(grpc.CallOption) + } + } + run(args[0].(context.Context), args[1].(*orders_service.UpdateShoppingCartReq), variadicArgs...) + }) + return _c +} + +func (_c *OrdersServiceClient_UpdateShoppingCart_Call) Return(_a0 *orders_service.UpdateShoppingCartRes, _a1 error) *OrdersServiceClient_UpdateShoppingCart_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceClient_UpdateShoppingCart_Call) RunAndReturn(run func(context.Context, *orders_service.UpdateShoppingCartReq, ...grpc.CallOption) (*orders_service.UpdateShoppingCartRes, error)) *OrdersServiceClient_UpdateShoppingCart_Call { + _c.Call.Return(run) + return _c +} + +// NewOrdersServiceClient creates a new instance of OrdersServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOrdersServiceClient(t interface { + mock.TestingT + Cleanup(func()) +}) *OrdersServiceClient { + mock := &OrdersServiceClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/mocks/OrdersServiceServer.go b/internal/services/orderservice/mocks/OrdersServiceServer.go new file mode 100644 index 00000000..de8fb363 --- /dev/null +++ b/internal/services/orderservice/mocks/OrdersServiceServer.go @@ -0,0 +1,313 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + orders_service "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/grpc/genproto" +) + +// OrdersServiceServer is an autogenerated mock type for the OrdersServiceServer type +type OrdersServiceServer struct { + mock.Mock +} + +type OrdersServiceServer_Expecter struct { + mock *mock.Mock +} + +func (_m *OrdersServiceServer) EXPECT() *OrdersServiceServer_Expecter { + return &OrdersServiceServer_Expecter{mock: &_m.Mock} +} + +// CreateOrder provides a mock function with given fields: _a0, _a1 +func (_m *OrdersServiceServer) CreateOrder(_a0 context.Context, _a1 *orders_service.CreateOrderReq) (*orders_service.CreateOrderRes, error) { + ret := _m.Called(_a0, _a1) + + var r0 *orders_service.CreateOrderRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.CreateOrderReq) (*orders_service.CreateOrderRes, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.CreateOrderReq) *orders_service.CreateOrderRes); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.CreateOrderRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.CreateOrderReq) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceServer_CreateOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateOrder' +type OrdersServiceServer_CreateOrder_Call struct { + *mock.Call +} + +// CreateOrder is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *orders_service.CreateOrderReq +func (_e *OrdersServiceServer_Expecter) CreateOrder(_a0 interface{}, _a1 interface{}) *OrdersServiceServer_CreateOrder_Call { + return &OrdersServiceServer_CreateOrder_Call{Call: _e.mock.On("CreateOrder", _a0, _a1)} +} + +func (_c *OrdersServiceServer_CreateOrder_Call) Run(run func(_a0 context.Context, _a1 *orders_service.CreateOrderReq)) *OrdersServiceServer_CreateOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*orders_service.CreateOrderReq)) + }) + return _c +} + +func (_c *OrdersServiceServer_CreateOrder_Call) Return(_a0 *orders_service.CreateOrderRes, _a1 error) *OrdersServiceServer_CreateOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceServer_CreateOrder_Call) RunAndReturn(run func(context.Context, *orders_service.CreateOrderReq) (*orders_service.CreateOrderRes, error)) *OrdersServiceServer_CreateOrder_Call { + _c.Call.Return(run) + return _c +} + +// GetOrderByID provides a mock function with given fields: _a0, _a1 +func (_m *OrdersServiceServer) GetOrderByID(_a0 context.Context, _a1 *orders_service.GetOrderByIDReq) (*orders_service.GetOrderByIDRes, error) { + ret := _m.Called(_a0, _a1) + + var r0 *orders_service.GetOrderByIDRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.GetOrderByIDReq) (*orders_service.GetOrderByIDRes, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.GetOrderByIDReq) *orders_service.GetOrderByIDRes); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.GetOrderByIDRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.GetOrderByIDReq) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceServer_GetOrderByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrderByID' +type OrdersServiceServer_GetOrderByID_Call struct { + *mock.Call +} + +// GetOrderByID is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *orders_service.GetOrderByIDReq +func (_e *OrdersServiceServer_Expecter) GetOrderByID(_a0 interface{}, _a1 interface{}) *OrdersServiceServer_GetOrderByID_Call { + return &OrdersServiceServer_GetOrderByID_Call{Call: _e.mock.On("GetOrderByID", _a0, _a1)} +} + +func (_c *OrdersServiceServer_GetOrderByID_Call) Run(run func(_a0 context.Context, _a1 *orders_service.GetOrderByIDReq)) *OrdersServiceServer_GetOrderByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*orders_service.GetOrderByIDReq)) + }) + return _c +} + +func (_c *OrdersServiceServer_GetOrderByID_Call) Return(_a0 *orders_service.GetOrderByIDRes, _a1 error) *OrdersServiceServer_GetOrderByID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceServer_GetOrderByID_Call) RunAndReturn(run func(context.Context, *orders_service.GetOrderByIDReq) (*orders_service.GetOrderByIDRes, error)) *OrdersServiceServer_GetOrderByID_Call { + _c.Call.Return(run) + return _c +} + +// GetOrders provides a mock function with given fields: _a0, _a1 +func (_m *OrdersServiceServer) GetOrders(_a0 context.Context, _a1 *orders_service.GetOrdersReq) (*orders_service.GetOrdersRes, error) { + ret := _m.Called(_a0, _a1) + + var r0 *orders_service.GetOrdersRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.GetOrdersReq) (*orders_service.GetOrdersRes, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.GetOrdersReq) *orders_service.GetOrdersRes); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.GetOrdersRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.GetOrdersReq) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceServer_GetOrders_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrders' +type OrdersServiceServer_GetOrders_Call struct { + *mock.Call +} + +// GetOrders is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *orders_service.GetOrdersReq +func (_e *OrdersServiceServer_Expecter) GetOrders(_a0 interface{}, _a1 interface{}) *OrdersServiceServer_GetOrders_Call { + return &OrdersServiceServer_GetOrders_Call{Call: _e.mock.On("GetOrders", _a0, _a1)} +} + +func (_c *OrdersServiceServer_GetOrders_Call) Run(run func(_a0 context.Context, _a1 *orders_service.GetOrdersReq)) *OrdersServiceServer_GetOrders_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*orders_service.GetOrdersReq)) + }) + return _c +} + +func (_c *OrdersServiceServer_GetOrders_Call) Return(_a0 *orders_service.GetOrdersRes, _a1 error) *OrdersServiceServer_GetOrders_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceServer_GetOrders_Call) RunAndReturn(run func(context.Context, *orders_service.GetOrdersReq) (*orders_service.GetOrdersRes, error)) *OrdersServiceServer_GetOrders_Call { + _c.Call.Return(run) + return _c +} + +// SubmitOrder provides a mock function with given fields: _a0, _a1 +func (_m *OrdersServiceServer) SubmitOrder(_a0 context.Context, _a1 *orders_service.SubmitOrderReq) (*orders_service.SubmitOrderRes, error) { + ret := _m.Called(_a0, _a1) + + var r0 *orders_service.SubmitOrderRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.SubmitOrderReq) (*orders_service.SubmitOrderRes, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.SubmitOrderReq) *orders_service.SubmitOrderRes); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.SubmitOrderRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.SubmitOrderReq) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceServer_SubmitOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SubmitOrder' +type OrdersServiceServer_SubmitOrder_Call struct { + *mock.Call +} + +// SubmitOrder is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *orders_service.SubmitOrderReq +func (_e *OrdersServiceServer_Expecter) SubmitOrder(_a0 interface{}, _a1 interface{}) *OrdersServiceServer_SubmitOrder_Call { + return &OrdersServiceServer_SubmitOrder_Call{Call: _e.mock.On("SubmitOrder", _a0, _a1)} +} + +func (_c *OrdersServiceServer_SubmitOrder_Call) Run(run func(_a0 context.Context, _a1 *orders_service.SubmitOrderReq)) *OrdersServiceServer_SubmitOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*orders_service.SubmitOrderReq)) + }) + return _c +} + +func (_c *OrdersServiceServer_SubmitOrder_Call) Return(_a0 *orders_service.SubmitOrderRes, _a1 error) *OrdersServiceServer_SubmitOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceServer_SubmitOrder_Call) RunAndReturn(run func(context.Context, *orders_service.SubmitOrderReq) (*orders_service.SubmitOrderRes, error)) *OrdersServiceServer_SubmitOrder_Call { + _c.Call.Return(run) + return _c +} + +// UpdateShoppingCart provides a mock function with given fields: _a0, _a1 +func (_m *OrdersServiceServer) UpdateShoppingCart(_a0 context.Context, _a1 *orders_service.UpdateShoppingCartReq) (*orders_service.UpdateShoppingCartRes, error) { + ret := _m.Called(_a0, _a1) + + var r0 *orders_service.UpdateShoppingCartRes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.UpdateShoppingCartReq) (*orders_service.UpdateShoppingCartRes, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *orders_service.UpdateShoppingCartReq) *orders_service.UpdateShoppingCartRes); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*orders_service.UpdateShoppingCartRes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *orders_service.UpdateShoppingCartReq) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OrdersServiceServer_UpdateShoppingCart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateShoppingCart' +type OrdersServiceServer_UpdateShoppingCart_Call struct { + *mock.Call +} + +// UpdateShoppingCart is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *orders_service.UpdateShoppingCartReq +func (_e *OrdersServiceServer_Expecter) UpdateShoppingCart(_a0 interface{}, _a1 interface{}) *OrdersServiceServer_UpdateShoppingCart_Call { + return &OrdersServiceServer_UpdateShoppingCart_Call{Call: _e.mock.On("UpdateShoppingCart", _a0, _a1)} +} + +func (_c *OrdersServiceServer_UpdateShoppingCart_Call) Run(run func(_a0 context.Context, _a1 *orders_service.UpdateShoppingCartReq)) *OrdersServiceServer_UpdateShoppingCart_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*orders_service.UpdateShoppingCartReq)) + }) + return _c +} + +func (_c *OrdersServiceServer_UpdateShoppingCart_Call) Return(_a0 *orders_service.UpdateShoppingCartRes, _a1 error) *OrdersServiceServer_UpdateShoppingCart_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *OrdersServiceServer_UpdateShoppingCart_Call) RunAndReturn(run func(context.Context, *orders_service.UpdateShoppingCartReq) (*orders_service.UpdateShoppingCartRes, error)) *OrdersServiceServer_UpdateShoppingCart_Call { + _c.Call.Return(run) + return _c +} + +// NewOrdersServiceServer creates a new instance of OrdersServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOrdersServiceServer(t interface { + mock.TestingT + Cleanup(func()) +}) *OrdersServiceServer { + mock := &OrdersServiceServer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/mocks/UnsafeOrdersServiceServer.go b/internal/services/orderservice/mocks/UnsafeOrdersServiceServer.go new file mode 100644 index 00000000..dcda4bc8 --- /dev/null +++ b/internal/services/orderservice/mocks/UnsafeOrdersServiceServer.go @@ -0,0 +1,64 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// UnsafeOrdersServiceServer is an autogenerated mock type for the UnsafeOrdersServiceServer type +type UnsafeOrdersServiceServer struct { + mock.Mock +} + +type UnsafeOrdersServiceServer_Expecter struct { + mock *mock.Mock +} + +func (_m *UnsafeOrdersServiceServer) EXPECT() *UnsafeOrdersServiceServer_Expecter { + return &UnsafeOrdersServiceServer_Expecter{mock: &_m.Mock} +} + +// mustEmbedUnimplementedOrdersServiceServer provides a mock function with given fields: +func (_m *UnsafeOrdersServiceServer) mustEmbedUnimplementedOrdersServiceServer() { + _m.Called() +} + +// UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'mustEmbedUnimplementedOrdersServiceServer' +type UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call struct { + *mock.Call +} + +// mustEmbedUnimplementedOrdersServiceServer is a helper method to define mock.On call +func (_e *UnsafeOrdersServiceServer_Expecter) mustEmbedUnimplementedOrdersServiceServer() *UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call { + return &UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call{Call: _e.mock.On("mustEmbedUnimplementedOrdersServiceServer")} +} + +func (_c *UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call) Run(run func()) *UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call) Return() *UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call { + _c.Call.Return() + return _c +} + +func (_c *UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call) RunAndReturn(run func()) *UnsafeOrdersServiceServer_mustEmbedUnimplementedOrdersServiceServer_Call { + _c.Call.Return(run) + return _c +} + +// NewUnsafeOrdersServiceServer creates a new instance of UnsafeOrdersServiceServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewUnsafeOrdersServiceServer(t interface { + mock.TestingT + Cleanup(func()) +}) *UnsafeOrdersServiceServer { + mock := &UnsafeOrdersServiceServer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/mocks/orderReadRepository.go b/internal/services/orderservice/mocks/orderReadRepository.go new file mode 100644 index 00000000..56fc38b9 --- /dev/null +++ b/internal/services/orderservice/mocks/orderReadRepository.go @@ -0,0 +1,415 @@ +// Code generated by mockery v2.30.16. DO NOT EDIT. + +package mocks + +import ( + context "context" + + read_models "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + mock "github.com/stretchr/testify/mock" + + utils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/utils" + + uuid "github.com/satori/go.uuid" +) + +// orderReadRepository is an autogenerated mock type for the orderReadRepository type +type orderReadRepository struct { + mock.Mock +} + +type orderReadRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *orderReadRepository) EXPECT() *orderReadRepository_Expecter { + return &orderReadRepository_Expecter{mock: &_m.Mock} +} + +// CreateOrder provides a mock function with given fields: ctx, order +func (_m *orderReadRepository) CreateOrder(ctx context.Context, order *read_models.OrderReadModel) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, order) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, order) + } + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) *read_models.OrderReadModel); ok { + r0 = rf(ctx, order) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *read_models.OrderReadModel) error); ok { + r1 = rf(ctx, order) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// orderReadRepository_CreateOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateOrder' +type orderReadRepository_CreateOrder_Call struct { + *mock.Call +} + +// CreateOrder is a helper method to define mock.On call +// - ctx context.Context +// - order *read_models.OrderReadModel +func (_e *orderReadRepository_Expecter) CreateOrder(ctx interface{}, order interface{}) *orderReadRepository_CreateOrder_Call { + return &orderReadRepository_CreateOrder_Call{Call: _e.mock.On("CreateOrder", ctx, order)} +} + +func (_c *orderReadRepository_CreateOrder_Call) Run(run func(ctx context.Context, order *read_models.OrderReadModel)) *orderReadRepository_CreateOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*read_models.OrderReadModel)) + }) + return _c +} + +func (_c *orderReadRepository_CreateOrder_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *orderReadRepository_CreateOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *orderReadRepository_CreateOrder_Call) RunAndReturn(run func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)) *orderReadRepository_CreateOrder_Call { + _c.Call.Return(run) + return _c +} + +// DeleteOrderByID provides a mock function with given fields: ctx, _a1 +func (_m *orderReadRepository) DeleteOrderByID(ctx context.Context, _a1 uuid.UUID) error { + ret := _m.Called(ctx, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok { + r0 = rf(ctx, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// orderReadRepository_DeleteOrderByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteOrderByID' +type orderReadRepository_DeleteOrderByID_Call struct { + *mock.Call +} + +// DeleteOrderByID is a helper method to define mock.On call +// - ctx context.Context +// - _a1 uuid.UUID +func (_e *orderReadRepository_Expecter) DeleteOrderByID(ctx interface{}, _a1 interface{}) *orderReadRepository_DeleteOrderByID_Call { + return &orderReadRepository_DeleteOrderByID_Call{Call: _e.mock.On("DeleteOrderByID", ctx, _a1)} +} + +func (_c *orderReadRepository_DeleteOrderByID_Call) Run(run func(ctx context.Context, _a1 uuid.UUID)) *orderReadRepository_DeleteOrderByID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *orderReadRepository_DeleteOrderByID_Call) Return(_a0 error) *orderReadRepository_DeleteOrderByID_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *orderReadRepository_DeleteOrderByID_Call) RunAndReturn(run func(context.Context, uuid.UUID) error) *orderReadRepository_DeleteOrderByID_Call { + _c.Call.Return(run) + return _c +} + +// GetAllOrders provides a mock function with given fields: ctx, listQuery +func (_m *orderReadRepository) GetAllOrders(ctx context.Context, listQuery *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error) { + ret := _m.Called(ctx, listQuery) + + var r0 *utils.ListResult[*read_models.OrderReadModel] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)); ok { + return rf(ctx, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, *utils.ListQuery) *utils.ListResult[*read_models.OrderReadModel]); ok { + r0 = rf(ctx, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*read_models.OrderReadModel]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *utils.ListQuery) error); ok { + r1 = rf(ctx, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// orderReadRepository_GetAllOrders_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllOrders' +type orderReadRepository_GetAllOrders_Call struct { + *mock.Call +} + +// GetAllOrders is a helper method to define mock.On call +// - ctx context.Context +// - listQuery *utils.ListQuery +func (_e *orderReadRepository_Expecter) GetAllOrders(ctx interface{}, listQuery interface{}) *orderReadRepository_GetAllOrders_Call { + return &orderReadRepository_GetAllOrders_Call{Call: _e.mock.On("GetAllOrders", ctx, listQuery)} +} + +func (_c *orderReadRepository_GetAllOrders_Call) Run(run func(ctx context.Context, listQuery *utils.ListQuery)) *orderReadRepository_GetAllOrders_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*utils.ListQuery)) + }) + return _c +} + +func (_c *orderReadRepository_GetAllOrders_Call) Return(_a0 *utils.ListResult[*read_models.OrderReadModel], _a1 error) *orderReadRepository_GetAllOrders_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *orderReadRepository_GetAllOrders_Call) RunAndReturn(run func(context.Context, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)) *orderReadRepository_GetAllOrders_Call { + _c.Call.Return(run) + return _c +} + +// GetOrderById provides a mock function with given fields: ctx, _a1 +func (_m *orderReadRepository) GetOrderById(ctx context.Context, _a1 uuid.UUID) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, _a1) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) *read_models.OrderReadModel); ok { + r0 = rf(ctx, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = rf(ctx, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// orderReadRepository_GetOrderById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrderById' +type orderReadRepository_GetOrderById_Call struct { + *mock.Call +} + +// GetOrderById is a helper method to define mock.On call +// - ctx context.Context +// - _a1 uuid.UUID +func (_e *orderReadRepository_Expecter) GetOrderById(ctx interface{}, _a1 interface{}) *orderReadRepository_GetOrderById_Call { + return &orderReadRepository_GetOrderById_Call{Call: _e.mock.On("GetOrderById", ctx, _a1)} +} + +func (_c *orderReadRepository_GetOrderById_Call) Run(run func(ctx context.Context, _a1 uuid.UUID)) *orderReadRepository_GetOrderById_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *orderReadRepository_GetOrderById_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *orderReadRepository_GetOrderById_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *orderReadRepository_GetOrderById_Call) RunAndReturn(run func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)) *orderReadRepository_GetOrderById_Call { + _c.Call.Return(run) + return _c +} + +// GetOrderByOrderId provides a mock function with given fields: ctx, orderId +func (_m *orderReadRepository) GetOrderByOrderId(ctx context.Context, orderId uuid.UUID) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, orderId) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, orderId) + } + if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) *read_models.OrderReadModel); ok { + r0 = rf(ctx, orderId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok { + r1 = rf(ctx, orderId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// orderReadRepository_GetOrderByOrderId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrderByOrderId' +type orderReadRepository_GetOrderByOrderId_Call struct { + *mock.Call +} + +// GetOrderByOrderId is a helper method to define mock.On call +// - ctx context.Context +// - orderId uuid.UUID +func (_e *orderReadRepository_Expecter) GetOrderByOrderId(ctx interface{}, orderId interface{}) *orderReadRepository_GetOrderByOrderId_Call { + return &orderReadRepository_GetOrderByOrderId_Call{Call: _e.mock.On("GetOrderByOrderId", ctx, orderId)} +} + +func (_c *orderReadRepository_GetOrderByOrderId_Call) Run(run func(ctx context.Context, orderId uuid.UUID)) *orderReadRepository_GetOrderByOrderId_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uuid.UUID)) + }) + return _c +} + +func (_c *orderReadRepository_GetOrderByOrderId_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *orderReadRepository_GetOrderByOrderId_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *orderReadRepository_GetOrderByOrderId_Call) RunAndReturn(run func(context.Context, uuid.UUID) (*read_models.OrderReadModel, error)) *orderReadRepository_GetOrderByOrderId_Call { + _c.Call.Return(run) + return _c +} + +// SearchOrders provides a mock function with given fields: ctx, searchText, listQuery +func (_m *orderReadRepository) SearchOrders(ctx context.Context, searchText string, listQuery *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error) { + ret := _m.Called(ctx, searchText, listQuery) + + var r0 *utils.ListResult[*read_models.OrderReadModel] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)); ok { + return rf(ctx, searchText, listQuery) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *utils.ListQuery) *utils.ListResult[*read_models.OrderReadModel]); ok { + r0 = rf(ctx, searchText, listQuery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*utils.ListResult[*read_models.OrderReadModel]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *utils.ListQuery) error); ok { + r1 = rf(ctx, searchText, listQuery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// orderReadRepository_SearchOrders_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SearchOrders' +type orderReadRepository_SearchOrders_Call struct { + *mock.Call +} + +// SearchOrders is a helper method to define mock.On call +// - ctx context.Context +// - searchText string +// - listQuery *utils.ListQuery +func (_e *orderReadRepository_Expecter) SearchOrders(ctx interface{}, searchText interface{}, listQuery interface{}) *orderReadRepository_SearchOrders_Call { + return &orderReadRepository_SearchOrders_Call{Call: _e.mock.On("SearchOrders", ctx, searchText, listQuery)} +} + +func (_c *orderReadRepository_SearchOrders_Call) Run(run func(ctx context.Context, searchText string, listQuery *utils.ListQuery)) *orderReadRepository_SearchOrders_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*utils.ListQuery)) + }) + return _c +} + +func (_c *orderReadRepository_SearchOrders_Call) Return(_a0 *utils.ListResult[*read_models.OrderReadModel], _a1 error) *orderReadRepository_SearchOrders_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *orderReadRepository_SearchOrders_Call) RunAndReturn(run func(context.Context, string, *utils.ListQuery) (*utils.ListResult[*read_models.OrderReadModel], error)) *orderReadRepository_SearchOrders_Call { + _c.Call.Return(run) + return _c +} + +// UpdateOrder provides a mock function with given fields: ctx, order +func (_m *orderReadRepository) UpdateOrder(ctx context.Context, order *read_models.OrderReadModel) (*read_models.OrderReadModel, error) { + ret := _m.Called(ctx, order) + + var r0 *read_models.OrderReadModel + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)); ok { + return rf(ctx, order) + } + if rf, ok := ret.Get(0).(func(context.Context, *read_models.OrderReadModel) *read_models.OrderReadModel); ok { + r0 = rf(ctx, order) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*read_models.OrderReadModel) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *read_models.OrderReadModel) error); ok { + r1 = rf(ctx, order) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// orderReadRepository_UpdateOrder_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateOrder' +type orderReadRepository_UpdateOrder_Call struct { + *mock.Call +} + +// UpdateOrder is a helper method to define mock.On call +// - ctx context.Context +// - order *read_models.OrderReadModel +func (_e *orderReadRepository_Expecter) UpdateOrder(ctx interface{}, order interface{}) *orderReadRepository_UpdateOrder_Call { + return &orderReadRepository_UpdateOrder_Call{Call: _e.mock.On("UpdateOrder", ctx, order)} +} + +func (_c *orderReadRepository_UpdateOrder_Call) Run(run func(ctx context.Context, order *read_models.OrderReadModel)) *orderReadRepository_UpdateOrder_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*read_models.OrderReadModel)) + }) + return _c +} + +func (_c *orderReadRepository_UpdateOrder_Call) Return(_a0 *read_models.OrderReadModel, _a1 error) *orderReadRepository_UpdateOrder_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *orderReadRepository_UpdateOrder_Call) RunAndReturn(run func(context.Context, *read_models.OrderReadModel) (*read_models.OrderReadModel, error)) *orderReadRepository_UpdateOrder_Call { + _c.Call.Return(run) + return _c +} + +// newOrderReadRepository creates a new instance of orderReadRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newOrderReadRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *orderReadRepository { + mock := &orderReadRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/services/orderservice/readme.md b/internal/services/orderservice/readme.md new file mode 100644 index 00000000..2f73380b --- /dev/null +++ b/internal/services/orderservice/readme.md @@ -0,0 +1,6 @@ +# Orders Service + +## Project Layout and Structure + +Based on these projects structure: +- [Standard Go Project Layout](https://github.com/golang-standards/project-layout) diff --git a/internal/services/orderservice/revive-config.toml b/internal/services/orderservice/revive-config.toml new file mode 100644 index 00000000..f9e2405d --- /dev/null +++ b/internal/services/orderservice/revive-config.toml @@ -0,0 +1,30 @@ +ignoreGeneratedHeader = false +severity = "warning" +confidence = 0.8 +errorCode = 0 +warningCode = 0 + +[rule.blank-imports] +[rule.context-as-argument] +[rule.context-keys-type] +[rule.dot-imports] +[rule.error-return] +[rule.error-strings] +[rule.error-naming] +[rule.exported] +[rule.if-return] +[rule.increment-decrement] +[rule.var-naming] +[rule.var-declaration] +[rule.package-comments] +[rule.range] +[rule.receiver-naming] +[rule.time-naming] +[rule.unexported-return] +[rule.indent-error-flow] +[rule.errorf] +[rule.empty-block] +[rule.superfluous-else] +[rule.unused-parameter] +[rule.unreachable-code] +[rule.redefines-builtin-id] diff --git a/internal/services/orderservice/taskfile.yml b/internal/services/orderservice/taskfile.yml new file mode 100644 index 00000000..ed72a03d --- /dev/null +++ b/internal/services/orderservice/taskfile.yml @@ -0,0 +1,3 @@ +#https://taskfile.dev/#/installation + +version: "3" diff --git a/internal/services/orderservice/taskfile_db.yml b/internal/services/orderservice/taskfile_db.yml new file mode 100644 index 00000000..ed72a03d --- /dev/null +++ b/internal/services/orderservice/taskfile_db.yml @@ -0,0 +1,3 @@ +#https://taskfile.dev/#/installation + +version: "3" diff --git a/internal/services/orderservice/taskfile_test.yml b/internal/services/orderservice/taskfile_test.yml new file mode 100644 index 00000000..40750f05 --- /dev/null +++ b/internal/services/orderservice/taskfile_test.yml @@ -0,0 +1,22 @@ +#https://taskfile.dev/#/installation +version: "3" +tasks: + mock: + desc: Generate interfaces mocks + cmds: + - mockery --output mocks --all + + integration: + desc: Run integration tests + cmds: + - go test -v -tags=integration ./... + + e2e: + desc: Run integration tests + cmds: + - go test -v -tags=e2e ./... + + unit: + desc: Run unit tests + cmds: + - go test -v -tags=unit ./... diff --git a/internal/services/orderservice/test/end_to_end/orders/features/creating_order/v1/create_order_test.go b/internal/services/orderservice/test/end_to_end/orders/features/creating_order/v1/create_order_test.go new file mode 100644 index 00000000..f2c83cb4 --- /dev/null +++ b/internal/services/orderservice/test/end_to_end/orders/features/creating_order/v1/create_order_test.go @@ -0,0 +1,80 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + "time" + + customTypes "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/core/customtypes" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/test_fixtures/integration" + + "github.com/brianvoe/gofakeit/v6" + "github.com/gavv/httpexpect/v2" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestCreateOrder(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "CreateOrder Endpoint EndToEnd Tests") +} + +var _ = Describe("CreateOrder Feature", func() { + var ( + ctx context.Context + request *dtos.CreateOrderRequestDto + ) + + _ = BeforeEach(func() { + ctx = context.Background() + + By("Seeding the required data") + integrationFixture.SetupTest() + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" for testing the creation of an order with valid input + Describe("Create new order return created status with valid input", func() { + BeforeEach(func() { + request = &dtos.CreateOrderRequestDto{ + AccountEmail: gofakeit.Email(), + DeliveryAddress: gofakeit.Address().Address, + DeliveryTime: customTypes.CustomTime(time.Now()), + ShopItems: []*dtosV1.ShopItemDto{ + { + Quantity: uint64(gofakeit.Number(1, 10)), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 10000), + Title: gofakeit.Name(), + }, + }, + } + }) + // "When" step for making a request to create an order + When("A valid request is made to create an order", func() { + It("Should returns a StatusCreated response", func() { + // Create an HTTPExpect instance and make the request + expect := httpexpect.Default(GinkgoT(), integrationFixture.BaseAddress) + expect.POST("orders"). + WithContext(ctx). + WithJSON(request). + Expect(). + Status(http.StatusCreated) + }) + }) + }) +}) diff --git a/internal/services/orderservice/test/end_to_end/orders/features/getting_order_by_id/v1/get_order_by_id_test.go b/internal/services/orderservice/test/end_to_end/orders/features/getting_order_by_id/v1/get_order_by_id_test.go new file mode 100644 index 00000000..9deb9b03 --- /dev/null +++ b/internal/services/orderservice/test/end_to_end/orders/features/getting_order_by_id/v1/get_order_by_id_test.go @@ -0,0 +1,61 @@ +//go:build e2e +// +build e2e + +package v1 + +import ( + "context" + "net/http" + "testing" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/test_fixtures/integration" + + "github.com/gavv/httpexpect/v2" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestGetProductById(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "GetOrderById Endpoint EndToEnd Tests") +} + +var _ = Describe("GetOrderById Feature", func() { + var ( + ctx context.Context + id string + ) + + _ = BeforeEach(func() { + ctx = context.Background() + + By("Seeding the required data") + integrationFixture.SetupTest() + + id = integrationFixture.Items[0].Id + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + // "Scenario" for testing the retrieval of an order by a valid ID + Describe("Get order by ID with a valid ID returns ok status", func() { + // "When" step for making a request to get an order by ID + When("A valid request is made with a valid ID", func() { + It("should return an 'OK' status", func() { + expect := httpexpect.Default(GinkgoT(), integrationFixture.BaseAddress) + expect.GET("orders/{id}"). + WithPath("id", id). + WithContext(ctx). + Expect(). + Status(http.StatusOK) + }) + }) + }) +}) diff --git a/internal/services/orderservice/test/end_to_end/orders/grpc/order_grpc_service_server_test.go b/internal/services/orderservice/test/end_to_end/orders/grpc/order_grpc_service_server_test.go new file mode 100644 index 00000000..2ea9f15d --- /dev/null +++ b/internal/services/orderservice/test/end_to_end/orders/grpc/order_grpc_service_server_test.go @@ -0,0 +1,87 @@ +package grpc + +// +//import ( +// "context" +// "testing" +// "time" +// +// "github.com/brianvoe/gofakeit/v6" +// "github.com/stretchr/testify/assert" +// "google.golang.org/protobuf/types/known/timestamppb" +// +// testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" +// ordersService "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/contracts/proto" +// "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/test_fixtures/e2e" +//) +// +//type OrderGrpcServiceTests struct { +// *testing.T +// *e2e.E2ETestFixture +// ordersService.OrdersServiceClient +//} +// +//func TestRunner(t *testing.T) { +// testUtils.SkipCI(t) +// fixture := e2e.NewE2ETestFixture() +// +// // https://pkg.go.dev/testing@master#hdr-Subtests_and_Sub_benchmarks +// t.Register("GRPC", func(t *testing.T) { +// // Before running the tests +// orderGrpcService := NewOrderGrpcService(fixture.InfrastructureConfigurations, fixture.OrdersMetrics, fixture.Bus) +// ordersService.RegisterOrdersServiceServer(fixture.GrpcServer.GetCurrentGrpcServer(), orderGrpcService) +// +// ctx := fixture.Ctx +// fixture.Register() +// +// orderServiceClient := ordersService.NewOrdersServiceClient(fixture.GrpcClient.GetGrpcConnection()) +// +// orderGrpcServiceTests := OrderGrpcServiceTests{ +// T: t, +// E2ETestFixture: fixture, +// OrdersServiceClient: orderServiceClient, +// } +// +// // Register Tests +// orderGrpcServiceTests.Test_GetOrder_By_Id(ctx) +// orderGrpcServiceTests.Test_Create_Order(ctx) +// orderGrpcServiceTests.Test_GetOrders(ctx) +// +// // After running the tests +// fixture.Cleanup() +// }) +//} +// +//func (p *OrderGrpcServiceTests) Test_Create_Order(ctx context.Context) { +// req := &ordersService.CreateOrderReq{ +// AccountEmail: gofakeit.Email(), +// DeliveryAddress: gofakeit.Address().Address, +// DeliveryTime: timestamppb.New(time.Now()), +// ShopItems: []*ordersService.ShopItem{ +// { +// Quantity: uint64(gofakeit.Number(1, 10)), +// Description: gofakeit.AdjectiveDescriptive(), +// Price: gofakeit.Price(100, 10000), +// Title: gofakeit.ShortTypeName(), +// }, +// }, +// } +// +// res, err := p.CreateOrder(ctx, req) +// assert.NoError(p.T, err) +// assert.NotZero(p.T, res.OrderId) +//} +// +//func (p *OrderGrpcServiceTests) Test_GetOrder_By_Id(ctx context.Context) { +// res, err := p.GetOrderByID(ctx, &ordersService.GetOrderByIDReq{Id: "1b4b0599-bc3c-4c1d-94af-fd1895713620"}) +// assert.NoError(p.T, err) +// assert.NotNil(p.T, res.Order) +// assert.Equal(p.T, res.Order.Id, "1b4b0599-bc3c-4c1d-94af-fd1895713620") +//} +// +//func (p *OrderGrpcServiceTests) Test_GetOrders(ctx context.Context) { +// res, err := p.GetOrders(ctx, &ordersService.GetOrdersReq{Size: 10, Page: 1}) +// assert.NoError(p.T, err) +// assert.NotNil(p.T, res.Orders) +// assert.NotEmpty(p.T, res.Orders) +//} diff --git a/internal/services/orderservice/test/integration/orders/features/creating_order/v1/create_order_test.go b/internal/services/orderservice/test/integration/orders/features/creating_order/v1/create_order_test.go new file mode 100644 index 00000000..112c3b27 --- /dev/null +++ b/internal/services/orderservice/test/integration/orders/features/creating_order/v1/create_order_test.go @@ -0,0 +1,213 @@ +//go:build integration +// +build integration + +package v1 + +import ( + "context" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/hypothesis" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/messaging" + testUtils "github.com/mehdihadeli/go-food-delivery-microservices/internal/pkg/test/utils" + dtosV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/dtos/v1" + createOrderCommandV1 "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/commands" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/dtos" + integrationEvents "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/creating_order/v1/events/integration_events" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/models/orders/read_models" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/test_fixtures/integration" + + "github.com/brianvoe/gofakeit/v6" + "github.com/mehdihadeli/go-mediatr" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestCreateOrder(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "Create Order Integration Tests") +} + +var _ = Describe("Create Order Feature", func() { + var ( + ctx context.Context + err error + command *createOrderCommandV1.CreateOrder + result *dtos.CreateOrderResponseDto + createdOrder *read_models.OrderReadModel + // id string + shouldPublish hypothesis.Hypothesis[*integrationEvents.OrderCreatedV1] + ) + + _ = BeforeEach(func() { + By("Seeding the required data") + integrationFixture.SetupTest() + + // id = integrationFixture.Items[0].OrderId + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + _ = BeforeSuite(func() { + ctx = context.Background() + + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + err = integrationFixture.Bus.Start(context.Background()) + Expect(err).ShouldNot(HaveOccurred()) + + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + }) + + _ = AfterSuite(func() { + integrationFixture.Log.Info("TearDownSuite started") + err := integrationFixture.Bus.Stop() + Expect(err).ShouldNot(HaveOccurred()) + time.Sleep(1 * time.Second) + }) + + // "Scenario" for testing the creation of a new order + Describe("Creating a new order in EventStoreDB", func() { + BeforeEach(func() { + command, err = createOrderCommandV1.NewCreateOrder( + []*dtosV1.ShopItemDto{ + { + Quantity: uint64(gofakeit.Number(1, 10)), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 10000), + Title: gofakeit.Name(), + }, + }, + gofakeit.Email(), + gofakeit.Address().Address, + time.Now(), + ) + Expect(err).ToNot(HaveOccurred()) + Expect(command).ToNot(BeNil()) + }) + When("the CreateOrder command is executed for non-existing order", func() { + BeforeEach(func() { + // "When" step for executing the createOrderCommand + result, err = mediatr.Send[*createOrderCommandV1.CreateOrder, *dtos.CreateOrderResponseDto]( + ctx, + command, + ) + }) + // "Then" step for expected behavior + It("Should create the order successfully", func() { + // "Then" step for assertions + Expect(err).To(BeNil()) + Expect(result).NotTo(BeNil()) + Expect(command.OrderId).To(Equal(result.OrderId)) + }) + }) + }) + + // "Scenario" for testing the creation of a new order in MongoDB Read + Describe("Creating a new order in MongoDB Read", func() { + BeforeEach(func() { + command, err = createOrderCommandV1.NewCreateOrder( + []*dtosV1.ShopItemDto{ + { + Quantity: uint64(gofakeit.Number(1, 10)), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 10000), + Title: gofakeit.Name(), + }, + }, + gofakeit.Email(), + gofakeit.Address().Address, + time.Now(), + ) + Expect(err).ToNot(HaveOccurred()) + Expect(command).ToNot(BeNil()) + }) + // "When" step for creating a new order + When("the CreateOrder command is executed for non-existing order", func() { + BeforeEach(func() { + // "When" step for executing the createOrderCommand + result, err = mediatr.Send[*createOrderCommandV1.CreateOrder, *dtos.CreateOrderResponseDto]( + context.Background(), + command, + ) + }) + + It("Should create the order successfully", func() { + // "Then" step for assertions + Expect(err).To(BeNil()) + Expect(result).NotTo(BeNil()) + }) + + // "Then" step for expected behavior + It("Should retrieve created order in MongoDB Read database", func() { + // Use a utility function to wait until the order is available in MongoDB Read + err = testUtils.WaitUntilConditionMet(func() bool { + createdOrder, err = integrationFixture.OrderMongoRepository.GetOrderByOrderId(ctx, result.OrderId) + Expect(err).ToNot(HaveOccurred()) + return createdOrder != nil + }) + + Expect(err).To(BeNil()) + }) + }) + }) + + // "Scenario" for testing the publishing of an "OrderCreated" event + Describe("Publishing an OrderCreated event to the message broker when order saved successfully", func() { + BeforeEach(func() { + shouldPublish = messaging.ShouldProduced[*integrationEvents.OrderCreatedV1]( + ctx, + integrationFixture.Bus, nil, + ) + + command, err = createOrderCommandV1.NewCreateOrder( + []*dtosV1.ShopItemDto{ + { + Quantity: uint64(gofakeit.Number(1, 10)), + Description: gofakeit.AdjectiveDescriptive(), + Price: gofakeit.Price(100, 10000), + Title: gofakeit.Name(), + }, + }, + gofakeit.Email(), + gofakeit.Address().Address, + time.Now(), + ) + + Expect(err).ToNot(HaveOccurred()) + Expect(command).ToNot(BeNil()) + }) + + // "When" step for creating and sending an order + When("CreateOrder command is executed for non-existing order", func() { + BeforeEach(func() { + // "When" step for executing the createOrderCommand + result, err = mediatr.Send[*createOrderCommandV1.CreateOrder, *dtos.CreateOrderResponseDto]( + context.Background(), + command, + ) + }) + + It("Should return no error", func() { + Expect(err).ToNot(HaveOccurred()) + }) + + It("Should return not nil result", func() { + Expect(result).ToNot(BeNil()) + }) + + It("Should publish OrderCreated event to the broker", func() { + // ensuring message published to the rabbitmq broker + shouldPublish.Validate(ctx, "there is no published message", time.Second*30) + }) + }) + }) +}) diff --git a/internal/services/orderservice/test/integration/orders/features/getting_order_by_id/v1/get_order_by_id_test.go b/internal/services/orderservice/test/integration/orders/features/getting_order_by_id/v1/get_order_by_id_test.go new file mode 100644 index 00000000..8a37db1a --- /dev/null +++ b/internal/services/orderservice/test/integration/orders/features/getting_order_by_id/v1/get_order_by_id_test.go @@ -0,0 +1,98 @@ +//go:build integration +// +build integration + +package v1 + +import ( + "context" + "testing" + "time" + + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/dtos" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/orders/features/getting_order_by_id/v1/queries" + "github.com/mehdihadeli/go-food-delivery-microservices/internal/services/orderservice/internal/shared/test_fixtures/integration" + + "github.com/mehdihadeli/go-mediatr" + uuid "github.com/satori/go.uuid" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var integrationFixture *integration.IntegrationTestSharedFixture + +func TestGetOrderById(t *testing.T) { + RegisterFailHandler(Fail) + integrationFixture = integration.NewIntegrationTestSharedFixture(t) + RunSpecs(t, "Get Order By Id Integration Tests") +} + +var _ = Describe("Get Order By Id Feature", func() { + var ( + ctx context.Context + query *queries.GetOrderById + err error + id uuid.UUID + result *dtos.GetOrderByIdResponseDto + ) + + _ = BeforeEach(func() { + By("Seeding the required data") + integrationFixture.SetupTest() + + idString := integrationFixture.Items[0].Id + id, err = uuid.FromString(idString) + Expect(err).NotTo(HaveOccurred()) + }) + + _ = AfterEach(func() { + By("Cleanup test data") + integrationFixture.TearDownTest() + }) + + _ = BeforeSuite(func() { + ctx = context.Background() + + // in test mode we set rabbitmq `AutoStart=false` in configuration in rabbitmqOptions, so we should run rabbitmq bus manually + err = integrationFixture.Bus.Start(context.Background()) + Expect(err).ShouldNot(HaveOccurred()) + + // wait for consumers ready to consume before publishing messages, preparation background workers takes a bit time (for preventing messages lost) + time.Sleep(1 * time.Second) + }) + + _ = AfterSuite(func() { + integrationFixture.Log.Info("TearDownSuite started") + err := integrationFixture.Bus.Stop() + Expect(err).ShouldNot(HaveOccurred()) + time.Sleep(1 * time.Second) + }) + + // "Scenario" for testing the retrieval of an existing order by ID + Describe("Retrieving an existing order by ID from the database", func() { + BeforeEach(func() { + query, err = queries.NewGetOrderById(id) + Expect(err).ToNot(HaveOccurred()) + Expect(query).ToNot(BeNil()) + }) + // "When" step for executing the query to get the order + When("retrieving an existing order by ID", func() { + BeforeEach(func() { + // "When" step for executing the query to get the order + result, err = mediatr.Send[*queries.GetOrderById, *dtos.GetOrderByIdResponseDto]( + ctx, + query, + ) + }) + + It("Should return the order successfully with correct properties", func() { + // "Then" step for assertions + Expect(err).NotTo(HaveOccurred()) + Expect(result).NotTo(BeNil()) + Expect(result.Order).NotTo(BeNil()) + Expect(result.Order.Id).To(Equal(id.String())) + Expect(result.Order.OrderId).NotTo(BeEmpty()) + }) + }) + }) +}) diff --git a/package-lock.json b/package-lock.json index 78521191..8f8638af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "go-ecommerce-microservices", + "name": "go-food-delivery-microservices", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "go-ecommerce-microservices", + "name": "go-food-delivery-microservices", "version": "1.0.0", "license": "MIT", "devDependencies": { diff --git a/package.json b/package.json index e60d7c39..118cbe78 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "go-ecommerce-microservices", + "name": "go-food-delivery-microservices", "version": "1.0.0", "description": "", "scripts": { @@ -7,14 +7,14 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/mehdihadeli/go-ecommerce-microservices.git" + "url": "git+https://github.com/mehdihadeli/go-food-delivery-microservices.git" }, "author": "", "license": "MIT", "bugs": { - "url": "https://github.com/mehdihadeli/go-ecommerce-microservices/issues" + "url": "https://github.com/mehdihadeli/go-food-delivery-microservices/issues" }, - "homepage": "https://github.com/mehdihadeli/go-ecommerce-microservices#readme", + "homepage": "https://github.com/mehdihadeli/go-food-delivery-microservices#readme", "devDependencies": { "@commitlint/cli": "^17.6.7", "@commitlint/config-conventional": "^17.6.7", diff --git a/scripts/format.sh b/scripts/format.sh index 49bd3dce..7299c758 100644 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -26,11 +26,12 @@ golines -m 120 -w --ignore-generated . # https://github.com/incu6us/goimports-reviser # https://github.com/incu6us/goimports-reviser/issues/118 # https://github.com/incu6us/goimports-reviser/issues/88 +# https://github.com/incu6us/goimports-reviser/issues/104 # will do `gofmt` internally if we use -format # -rm-unused, -set-alias have some errors ---> goimports-reviser -rm-unused -set-alias -format -recursive ./... -# goimports-reviser -company-prefixes "github.com/mehdihadeli" -project-name "github.com/mehdihadeli/go-ecommerce-microservices" -imports-order "std,general,company,project" -recursive ./... +# goimports-reviser -company-prefixes "github.com/mehdihadeli" -project-name "github.com/mehdihadeli/go-food-delivery-microservices" -rm-unused -set-alias -imports-order "std,general,company,project,blanked,dotted" -recursive ./... -gci write --skip-generated -s standard -s "prefix(github.com/mehdihadeli/go-ecommerce-microservices)" -s default -s blank -s dot --custom-order . +gci write --skip-generated -s standard -s "prefix(github.com/mehdihadeli/go-food-delivery-microservices)" -s default -s blank -s dot --custom-order . # https://golang.org/cmd/gofmt/ # gofmt -w . diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index e6f4c72d..4b60b113 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -7,6 +7,8 @@ set -e # `go install package@version` command works directly when we specified exact version, elsewhere it needs a `go.mod` and specifying corresponding version for each package +go install github.com/samlitowitz/goimportcycle/cmd/goimportcycle@latest + # https://github.com/incu6us/goimports-reviser go install -v github.com/incu6us/goimports-reviser/v3@latest diff --git a/scripts/openapi.sh b/scripts/openapi.sh index 169c5081..db4cc6ba 100644 --- a/scripts/openapi.sh +++ b/scripts/openapi.sh @@ -5,6 +5,5 @@ set -e readonly service="$1" -# https://github.com/swaggo/swag/issues/817 swag init --parseDependency --parseInternal --parseDepth 1 -g ./cmd/app/main.go -d "./internal/services/$service/" -o "./internal/services/$service/docs" swag init --parseDependency --parseInternal --parseDepth 1 -g ./cmd/app/main.go -d "./internal/services/$service/" -o "./api/openapi/$service/" diff --git a/scripts/service-reset.ps1 b/scripts/service-reset.ps1 new file mode 100644 index 00000000..954d36d0 --- /dev/null +++ b/scripts/service-reset.ps1 @@ -0,0 +1,2 @@ +net stop winnat +net start winnat diff --git a/scripts/update-dependencies.sh b/scripts/update-dependencies.sh new file mode 100644 index 00000000..924ceae7 --- /dev/null +++ b/scripts/update-dependencies.sh @@ -0,0 +1,13 @@ +# In a bash script, set -e is a command that enables the "exit immediately" option. When this option is set, the script will terminate immediately if any command within the script exits with a non-zero status (indicating an error). +set -e + +readonly service="$1" + +echo "start upgrading packages in $service" + +if [ "$service" = "pkg" ]; then + cd "./internal/pkg" && go get -u -t -d -v ./... && go mod tidy +# Check if input is not empty or null +elif [ -n "$service" ]; then + cd "./internal/services/$service" && go get -u -t -d -v ./... && go mod tidy +fi diff --git a/taskfile.yml b/taskfile.yml new file mode 100644 index 00000000..14f39107 --- /dev/null +++ b/taskfile.yml @@ -0,0 +1,114 @@ +#https://taskfile.dev/#/installation +#https://github.com/go-task/task/issues/1115 +version: "3" + +tasks: + install-tools: + desc: Install necessary tools + cmds: + - sh ./scripts/install-tools.sh + + run-catalogs-write-service: + desc: Run catalog write service + cmds: + - sh ./scripts/run.sh catalogwriteservice + + run-catalog-read-service: + desc: Run catalog read service + cmds: + - sh ./scripts/run.sh catalogreadservice + + run-order-service: + desc: Run order service + cmds: + - sh ./scripts/run.sh orderservice + + build: + desc: Build project components + cmds: + - sh ./scripts/build.sh pkg + - sh ./scripts/build.sh catalogwriteservice + - sh ./scripts/build.sh catalogreadservice + - sh ./scripts/build.sh orderservice + + install-dependencies: + desc: Install project dependencies + cmds: + - sh ./scripts/install-dependencies.sh pkg + - sh ./scripts/install-dependencies.sh catalogwriteservice + - sh ./scripts/install-dependencies.sh catalogreadservice + - sh ./scripts/install-dependencies.sh orderservice + + docker-compose-infra-up: + desc: Start infrastructure using docker-compose + cmds: + - docker-compose -f deployments/docker-compose/docker-compose.infrastructure.yaml up --build -d + + docker-compose-infra-down: + desc: Stop infrastructure using docker-compose + cmds: + - docker-compose -f deployments/docker-compose/docker-compose.infrastructure.yaml down + + openapi: + desc: Generate OpenAPI documentation + cmds: + - sh ./scripts/openapi.sh catalogwriteservice + - sh ./scripts/openapi.sh catalogreadservice + - sh ./scripts/openapi.sh orderservice + + proto: + desc: Generate protobuf files + cmds: + - sh ./scripts/proto.sh catalogwriteservice + - sh ./scripts/proto.sh orderservice + + unit-test: + desc: Run unit tests + cmds: + - sh ./scripts/test.sh catalogwriteservice unit + - sh ./scripts/test.sh catalogreadservice unit + - sh ./scripts/test.sh orderservice unit + + integration-test: + desc: Run integration tests + cmds: + - sh ./scripts/test.sh catalogwriteservice integration + - sh ./scripts/test.sh catalogreadservice integration + - sh ./scripts/test.sh orderservice integration + + e2e-test: + desc: Run end-to-end tests + cmds: + - sh ./scripts/test.sh catalogwriteservice e2e + - sh ./scripts/test.sh catalogreadservice e2e + - sh ./scripts/test.sh orderservice e2e + + format: + desc: Format codebase + cmds: + - sh ./scripts/format.sh catalogwriteservice + - sh ./scripts/format.sh catalogreadservice + - sh ./scripts/format.sh orderservice + - sh ./scripts/format.sh pkg + + lint: + desc: Run linters + cmds: + - sh ./scripts/lint.sh catalogwriteservice + - sh ./scripts/lint.sh catalogreadservice + - sh ./scripts/lint.sh orderservice + - sh ./scripts/lint.sh pkg + + pkg-mocks: + desc: Generate package mocks + cmds: + - cd internal/pkg/es && mockery --output mocks --all + - cd internal/pkg/core/serializer && mockery --output mocks --all + - cd internal/pkg/core/messaging && mockery --output mocks --all + + services-mocks: + desc: Generate service mocks + cmds: + - cd internal/services/catalogwriteservice && mockery --output mocks --all + - cd internal/services/catalogreadservice && mockery --output mocks --all + - cd internal/services/orderservice && mockery --output mocks --all