feat: overhelm

This commit is contained in:
Mikhail 2023-07-04 04:04:31 +00:00 committed by Kirill
parent e15242a2b8
commit 01dce42f5c
112 changed files with 9154 additions and 7750 deletions

@ -1,11 +1,11 @@
GRPC_HOST=0.0.0.0 GRPC_HOST=0.0.0.0
GPRC_PORT=9001 GPRC_PORT=9001
HTTP_PORT=8001 HTTP_PORT=8001
MONGO_HOST=mongo MONGO_HOST=mongo
MONGO_PORT=27017 MONGO_PORT=27017
MONGO_USER=test MONGO_USER=test
MONGO_PASSWORD=test MONGO_PASSWORD=test
MONGO_AUTH=admin MONGO_AUTH=admin
MONGO_DB_NAME=admin MONGO_DB_NAME=admin

@ -1,8 +1,31 @@
stages: stages:
- lint
- test
- clean - clean
- build - build
- deploy - deploy
lint:
image: golangci/golangci-lint:v1.51-alpine
stage: lint
script:
- go generate ./internal/...
- golangci-lint version
- golangci-lint run --disable-all --enable=vet --enable=golint ./...
test:
image: golang:1.20-alpine
stage: test
coverage: /\(statements\)(?:\s+)?(\d+(?:\.\d+)?%)/
script:
- CGO_ENABLED=0 go test ./internal/... -coverprofile=coverage.out
- go tool cover -html=coverage.out -o coverage.html
- go tool cover -func coverage.out
artifacts:
expire_in: "3 days"
paths:
- coverage.html
clean-old: clean-old:
stage: clean stage: clean
image: image:

164
.golangci.yaml Normal file

@ -0,0 +1,164 @@
run:
timeout: 5m
skip-files:
- \.pb\.go$
- .pb.go
- \.pb\.validate\.go$
- \.pb\.gw\.go$
- .pb.gw.go
- \.gen\.go$
skip-dirs:
- mocks
linters:
disable-all: true
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- containedctx
- dogsled
- dupword
- durationcheck
- errcheck
- errchkjson
- exportloopref
- goconst
- gocritic
- godot
- gofmt
- gci
- goprintffuncname
- gosec
- gosimple
- govet
- importas
- ineffassign
- misspell
- nakedret
- nilerr
- noctx
- nolintlint
- nosprintfhostport
- prealloc
- predeclared
- revive
- rowserrcheck
- staticcheck
- stylecheck
- thelper
- typecheck
- unconvert
- unparam
- unused
- usestdlibvars
- whitespace
linters-settings:
errcheck:
exclude-functions:
- (io.Closer).Close
govet:
check-shadowing: true
gci:
custom-order: false
section-separators:
- newLine
sections:
- standard # Standard section: captures all standard packages.
- default # Default section: contains all imports that could not be matched to another section type.
- blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled.
- dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled.
importas:
no-unaliased: true
alias:
# Foundation libraries
- pkg: git.sbercloud.tech/products/paas/shared/foundation/management-server
alias: mgmtserver
maligned:
suggest-new: true
goconst:
min-len: 2
min-occurrences: 2
lll:
line-length: 140
revive:
rules:
# The following rules are recommended https://github.com/mgechev/revive#recommended-configuration
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
# - name: exported
- name: if-return
- name: increment-decrement
- name: var-naming
- name: var-declaration
# - name: package-comments
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
- name: empty-block
- name: superfluous-else
- name: unused-parameter
- name: unreachable-code
- name: redefines-builtin-id
#
# Rules in addition to the recommended configuration above.
#
- name: bool-literal-in-expr
- name: constant-logical-expr
gosec:
excludes:
- G307 # Deferring unsafe method "Close" on type "\*os.File"
- G108 # Profiling endpoint is automatically exposed on /debug/pprof
gocritic:
enabled-tags:
- diagnostic
- experimental
- performance
disabled-checks:
- appendAssign
- dupImport # https://github.com/go-critic/go-critic/issues/845
- evalOrder
- ifElseChain
- octalLiteral
- regexpSimplify
- sloppyReassign
- truncateCmp
- typeDefFirst
- unnamedResult
- unnecessaryDefer
- whyNoLint
- wrapperFunc
- rangeValCopy
- hugeParam
issues:
exclude-rules:
- text: "at least one file in a package should have a package comment"
linters:
- stylecheck
- text: "should have a package comment, unless it's in another file for this package"
linters:
- golint
- text: "should have comment or be unexported"
linters:
- golint
- path: _test\.go
linters:
- gosec
- dupl
exclude-use-default: false
output:
# colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number"
format: colored-line-number
print-linter-name: true

6
.mockery.yaml Normal file

@ -0,0 +1,6 @@
exported: True
inpackage: False
keeptree: True
case: underscore
with-expecter: True
inpackage-suffix: True

@ -1,72 +1,72 @@
# BUILD # BUILD
FROM golang:1.19.5-alpine AS build FROM golang:1.19.5-alpine AS build
# Update depences # Update depences
RUN apk update && apk add --no-cache curl RUN apk update && apk add --no-cache curl
# Create build directory # Create build directory
RUN mkdir /app/bin -p RUN mkdir /app/bin -p
RUN mkdir /bin/golang-migrate -p RUN mkdir /bin/golang-migrate -p
# Download migrate app # Download migrate app
RUN GOLANG_MIGRATE_VERSION=v4.15.1 && \ RUN GOLANG_MIGRATE_VERSION=v4.15.1 && \
curl -L https://github.com/golang-migrate/migrate/releases/download/${GOLANG_MIGRATE_VERSION}/migrate.linux-amd64.tar.gz |\ curl -L https://github.com/golang-migrate/migrate/releases/download/${GOLANG_MIGRATE_VERSION}/migrate.linux-amd64.tar.gz |\
tar xvz migrate -C /bin/golang-migrate tar xvz migrate -C /bin/golang-migrate
# Download health check utility # Download health check utility
RUN GRPC_HEALTH_PROBE_VERSION=v0.4.6 && \ RUN GRPC_HEALTH_PROBE_VERSION=v0.4.6 && \
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 && \ 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 chmod +x /bin/grpc_health_probe
# Set home directory # Set home directory
WORKDIR /app WORKDIR /app
# Copy go.mod # Copy go.mod
ADD go.mod go.sum /app/ ADD go.mod go.sum /app/
# Download go depences # Download go depences
RUN go mod download RUN go mod download
# Copy all local files # Copy all local files
ADD . /app ADD . /app
# Build app # Build app
RUN GOOS=linux go build -o bin ./... RUN GOOS=linux go build -o bin ./...
# TEST # TEST
FROM alpine:latest AS test FROM alpine:latest AS test
# Install packages # Install packages
RUN apk --no-cache add ca-certificates RUN apk --no-cache add ca-certificates
# Create home directory # Create home directory
WORKDIR /app WORKDIR /app
# Copy build file # Copy build file
COPY --from=build /app/bin/app ./app COPY --from=build /app/bin/app ./app
# CMD # CMD
CMD ["./app"] CMD ["./app"]
# MIGRATION # MIGRATION
FROM alpine:latest AS migration FROM alpine:latest AS migration
# Install packages # Install packages
RUN apk --no-cache add ca-certificates RUN apk --no-cache add ca-certificates
# Create home directory # Create home directory
WORKDIR /app WORKDIR /app
# Copy migration dir # Copy migration dir
COPY --from=build /app/migrations/tests ./migrations COPY --from=build /app/migrations/tests ./migrations
# Install migrate tool # Install migrate tool
COPY --from=build /bin/golang-migrate /usr/local/bin COPY --from=build /bin/golang-migrate /usr/local/bin
# PRODUCTION # PRODUCTION
FROM alpine:latest AS production FROM alpine:latest AS production
# Install packages # Install packages
RUN apk --no-cache add ca-certificates RUN apk --no-cache add ca-certificates
# Create home directory # Create home directory
WORKDIR /app WORKDIR /app
# Copy build file # Copy build file
COPY --from=build /app/bin/app ./app COPY --from=build /app/bin/app ./app
# Copy grpc health probe dir # Copy grpc health probe dir
COPY --from=build /bin/grpc_health_probe /bin/grpc_health_probe COPY --from=build /bin/grpc_health_probe /bin/grpc_health_probe
# Install migrate tool # Install migrate tool
COPY --from=build /bin/golang-migrate /usr/local/bin COPY --from=build /bin/golang-migrate /usr/local/bin
# CMD # CMD
CMD ["./app"] CMD ["./app"]

@ -1,42 +1,42 @@
SERVICE_NAME = discount SERVICE_NAME = discount
help: ## show this help help: ## show this help
@echo 'usage: make [target] ...' @echo 'usage: make [target] ...'
@echo '' @echo ''
@echo 'targets:' @echo 'targets:'
@egrep '^(.+)\:\ .*##\ (.+)' ${MAKEFILE_LIST} | sed 's/:.*##/#/' | column -t -c 2 -s '#' @egrep '^(.+)\:\ .*##\ (.+)' ${MAKEFILE_LIST} | sed 's/:.*##/#/' | column -t -c 2 -s '#'
install: ## install all go dependencies install: ## install all go dependencies
go install \ go install \
github.com/bufbuild/buf/cmd/buf \ github.com/bufbuild/buf/cmd/buf@v1.23.1 \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \ github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest \
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \ github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest \
google.golang.org/grpc/cmd/protoc-gen-go-grpc \ google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/protobuf/cmd/protoc-gen-go@latest
generate: ## generate grpc proto for golang generate: ## generate grpc proto for golang
buf generate --path ./proto/${SERVICE_NAME} buf generate --path ./proto/${SERVICE_NAME}
test: ## run all layers tests test: ## run all layers tests
@make test.unit @make test.unit
@make test.integration @make test.integration
test.unit: ## run unit tests test.unit: ## run unit tests
go test ./... go test ./...
test.integration: ## run integration tests test.integration: ## run integration tests
@make test.integration.up @make test.integration.up
@make test.integration.start @make test.integration.start
@make test.integration.down @make test.integration.down
test.integration.up: ## build integration test environment test.integration.up: ## build integration test environment
docker-compose -f deployments/test/docker-compose.yaml --env-file ./.env.test up -d docker-compose -f deployments/test/docker-compose.yaml --env-file ./.env.test up -d
test.integration.start: ## run integration test test.integration.start: ## run integration test
go test -tags integration ./tests/integration/... go test -tags integration ./tests/integration/...
test.integration.down: ## shutting down integration environment test.integration.down: ## shutting down integration environment
docker-compose -f deployments/test/docker-compose.yaml --env-file ./.env.test down --volumes --rmi local docker-compose -f deployments/test/docker-compose.yaml --env-file ./.env.test down --volumes --rmi local
run: ## run app run: ## run app
go run ./cmd/app/main.go go run ./cmd/app/main.go

106
README.md

@ -1,53 +1,53 @@
# accruals-service # accruals-service
Микросервис для создания скидок и формирования цен из реализованных скидок Микросервис для создания скидок и формирования цен из реализованных скидок
| Branch | Pipeline | Code coverage | | Branch | Pipeline | Code coverage |
| ------------- |:-----------------:| --------------:| | ------------- |:-----------------:| --------------:|
| main | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/main/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/main/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | | main | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/main/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/main/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) |
| staging | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/staging/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/staging/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | | staging | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/staging/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/staging/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) |
| dev | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/dev/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/dev/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | | dev | [![pipeline status](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/dev/pipeline.svg)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) | [![coverage report](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/badges/dev/coverage.svg?job=test)](https://penahub.gitlab.yandexcloud.net/pena-services/discount-service/-/pipelines) |
## Переменные окружения приложения ## Переменные окружения приложения
``` ```
GRPC_HOST - хост прослушивания приложения (gRPC) GRPC_HOST - хост прослушивания приложения (gRPC)
GPRC_PORT - порт прослушивания приложения (gRPC) GPRC_PORT - порт прослушивания приложения (gRPC)
HTTP_PORT - порт прослушивания приложения (HTTP) HTTP_PORT - порт прослушивания приложения (HTTP)
MONGO_HOST - хост базы данных MONGO_HOST - хост базы данных
MONGO_PORT - порт базы данных MONGO_PORT - порт базы данных
MONGO_USER - имя пользователя базы данных для авторизации MONGO_USER - имя пользователя базы данных для авторизации
MONGO_PASSWORD - пароль пользователя базы данных для авторизации MONGO_PASSWORD - пароль пользователя базы данных для авторизации
MONGO_AUTH - название базы данных для авторизации MONGO_AUTH - название базы данных для авторизации
MONGO_DATABASE_NAME - название базы данных, к которой будет идти подключение MONGO_DATABASE_NAME - название базы данных, к которой будет идти подключение
``` ```
## Пример переменных окружения: ## Пример переменных окружения:
``` ```
GRPC_HOST=0.0.0.0 GRPC_HOST=0.0.0.0
GPRC_PORT=9001 GPRC_PORT=9001
HTTP_PORT=8001 HTTP_PORT=8001
MONGO_HOST=localhost MONGO_HOST=localhost
MONGO_PORT=27017 MONGO_PORT=27017
MONGO_USER=test MONGO_USER=test
MONGO_PASSWORD=test MONGO_PASSWORD=test
MONGO_AUTH=admin MONGO_AUTH=admin
``` ```
## Команды для работы с приложением: ## Команды для работы с приложением:
``` ```
make help - вывести список доступных команд с описанием make help - вывести список доступных команд с описанием
make install - устанавливает все необходимые зависимости и инструменты make install - устанавливает все необходимые зависимости и инструменты
make generate - генерирует proto файлы gRPC сервиса make generate - генерирует proto файлы gRPC сервиса
make test - запускает unit и интеграционные тесты make test - запускает unit и интеграционные тесты
make test.unit - запуск unit тестов make test.unit - запуск unit тестов
make test.integration - запуск интеграционных тестов (поднятие и завершение окружения) make test.integration - запуск интеграционных тестов (поднятие и завершение окружения)
make test.integration.up - поднятие тестового окружения make test.integration.up - поднятие тестового окружения
make test.integration.start - запуск интеграционных тестов make test.integration.start - запуск интеграционных тестов
make test.integration.down - завершение тестового окружения make test.integration.down - завершение тестового окружения
make run - запуск приложения make run - запуск приложения
``` ```

@ -1,12 +1,8 @@
version: v1beta1 version: v1
plugins: plugins:
- name: go - name: go
out: internal/proto out: internal/proto
- name: go-grpc - name: go-grpc
out: internal/proto out: internal/proto
opt: opt:
- require_unimplemented_servers=false - require_unimplemented_servers=false
- name: grpc-gateway
out: internal/proto
- name: openapiv2
out: internal/proto

3
buf.work.yaml Normal file

@ -0,0 +1,3 @@
version: v1
directories:
- proto

@ -1,7 +1,4 @@
version: v1beta1 version: v1
build:
roots:
- proto
lint: lint:
use: use:
- DEFAULT - DEFAULT

@ -8,7 +8,6 @@ import (
formatter "github.com/antonfisher/nested-logrus-formatter" formatter "github.com/antonfisher/nested-logrus-formatter"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/app" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/app"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/env" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/env"

@ -1,46 +1,46 @@
version: "3" version: "3"
services: services:
app: app:
build: build:
context: ../../. context: ../../.
dockerfile: Dockerfile dockerfile: Dockerfile
target: test target: test
env_file: env_file:
- ../../.env.test - ../../.env.test
ports: ports:
- 9001:9001 - 9001:9001
- 8001:8001 - 8001:8001
depends_on: depends_on:
- migration - migration
networks: networks:
- integration_test - integration_test
migration: migration:
build: build:
context: ../../. context: ../../.
dockerfile: Dockerfile dockerfile: Dockerfile
target: migration target: migration
command: command:
[ [
"sh", "sh",
"-c", "-c",
'migrate -source file://migrations -database "mongodb://$MONGO_USER:$MONGO_PASSWORD@$MONGO_HOST:$MONGO_PORT/$MONGO_AUTH?authSource=$MONGO_AUTH" up', 'migrate -source file://migrations -database "mongodb://$MONGO_USER:$MONGO_PASSWORD@$MONGO_HOST:$MONGO_PORT/$MONGO_AUTH?authSource=$MONGO_AUTH" up',
] ]
depends_on: depends_on:
- mongo - mongo
networks: networks:
- integration_test - integration_test
mongo: mongo:
image: 'mongo:6.0.3' image: 'mongo:6.0.3'
environment: environment:
MONGO_INITDB_ROOT_USERNAME: test MONGO_INITDB_ROOT_USERNAME: test
MONGO_INITDB_ROOT_PASSWORD: test MONGO_INITDB_ROOT_PASSWORD: test
ports: ports:
- '27017:27017' - '27017:27017'
networks: networks:
- integration_test - integration_test
networks: networks:
integration_test: integration_test:

82
go.mod

@ -5,37 +5,89 @@ go 1.20
require ( require (
github.com/antonfisher/nested-logrus-formatter v1.3.1 github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0
github.com/joho/godotenv v1.4.0 github.com/joho/godotenv v1.4.0
github.com/sethvargo/go-envconfig v0.8.3 github.com/sethvargo/go-envconfig v0.8.3
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.4
go.mongodb.org/mongo-driver v1.11.1 go.mongodb.org/mongo-driver v1.11.1
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
google.golang.org/genproto v0.0.0-20230119192704-9d59e20e5cd1 google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e
google.golang.org/grpc v1.52.0 google.golang.org/grpc v1.55.0
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 google.golang.org/protobuf v1.31.0
) )
require ( require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/bufbuild/buf v1.23.1 // indirect
github.com/bufbuild/connect-go v1.8.0 // indirect
github.com/bufbuild/connect-opentelemetry-go v0.3.0 // indirect
github.com/bufbuild/protocompile v0.5.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/docker/cli v24.0.2+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v24.0.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/go-chi/chi/v5 v5.0.8 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/klauspost/compress v1.13.6 // indirect github.com/google/go-containerregistry v0.15.2 // indirect
github.com/kr/pretty v0.2.1 // indirect github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 // indirect
github.com/klauspost/compress v1.16.6 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.7.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/cors v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tetratelabs/wazero v1.2.1 // indirect
github.com/vbatts/tar-split v0.11.3 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect go.opentelemetry.io/otel v1.16.0 // indirect
golang.org/x/net v0.4.0 // indirect go.opentelemetry.io/otel/metric v1.16.0 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect go.opentelemetry.io/otel/sdk v1.16.0 // indirect
golang.org/x/sys v0.4.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect
golang.org/x/text v0.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/term v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.10.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

149
go.sum

@ -1,25 +1,70 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
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/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
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/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ= github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
github.com/bufbuild/buf v1.23.1 h1:BeMkA3+e9XTFEZPDlAI7cydFKlq24wwYiOHp8CvsRzc=
github.com/bufbuild/buf v1.23.1/go.mod h1:ERFRzJiIjAOzUSJ3vz1zoI7XfxlBnCwZEyL+NJm4pko=
github.com/bufbuild/connect-go v1.8.0 h1:srluNkFkZBfSfg9Qb6DrO+5nMaxix//h2ctrHZhMGKc=
github.com/bufbuild/connect-go v1.8.0/go.mod h1:GmMJYR6orFqD0Y6ZgX8pwQ8j9baizDrIQMm1/a6LnHk=
github.com/bufbuild/connect-opentelemetry-go v0.3.0 h1:AuZi3asTDKmjGtd2aqpyP4p5QvBFG/YEaHopViLatnk=
github.com/bufbuild/connect-opentelemetry-go v0.3.0/go.mod h1:r1ppyTtu1EWeRodk4Q/JbyQhIWtO7eR3GoRDzjeEcNU=
github.com/bufbuild/protocompile v0.5.1 h1:mixz5lJX4Hiz4FpqFREJHIXLfaLBntfaJv1h+/jS+Qg=
github.com/bufbuild/protocompile v0.5.1/go.mod h1:G5iLmavmF4NsYtpZFvE3B/zFch2GIY8+wjsYLR/lc40=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
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/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-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/cli v24.0.2+incompatible h1:QdqR7znue1mtkXIJ+ruQMGQhpw2JzMJLRXp6zpzF6tM=
github.com/docker/cli v24.0.2+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.2+incompatible h1:eATx+oLz9WdNVkQrr0qjQ8HvRJ4bOOxfzEo8R+dA3cg=
github.com/docker/docker v24.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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.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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -27,6 +72,8 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -34,39 +81,84 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.5.2/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.5/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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE=
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A= github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0/go.mod h1:YDZoGHuwE+ov0c8smSH49WLF3F2LaWnYYuDVd+EWrc0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0/go.mod h1:YDZoGHuwE+ov0c8smSH49WLF3F2LaWnYYuDVd+EWrc0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 h1:2uT3aivO7NVpUPGcQX7RbHijHMyWix/yCnIrCWc+5co=
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
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.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/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 h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
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/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-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8=
github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sethvargo/go-envconfig v0.8.3 h1:dXyUrDCJvCm3ybP7yNpiux93qoSORvuH23bdsgFfiJ0= github.com/sethvargo/go-envconfig v0.8.3 h1:dXyUrDCJvCm3ybP7yNpiux93qoSORvuH23bdsgFfiJ0=
github.com/sethvargo/go-envconfig v0.8.3/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= github.com/sethvargo/go-envconfig v0.8.3/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@ -79,8 +171,15 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/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/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs=
github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 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/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
@ -93,22 +192,42 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8= go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8=
go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
go.uber.org/atomic v1.4.0/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.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.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-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-20180826012351-8a410e7b638d/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -120,6 +239,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -129,6 +250,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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.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-20180830151530-49385e6e1522/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -137,16 +260,26 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/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-20211007075335-d3039528d8ac/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/text v0.3.0/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -155,6 +288,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/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-20200619180055-7c47624df98f/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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -166,6 +301,12 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20230119192704-9d59e20e5cd1 h1:wSjSSQW7LuPdv3m1IrSN33nVxH/kID6OIKy+FMwGB2k= google.golang.org/genproto v0.0.0-20230119192704-9d59e20e5cd1 h1:wSjSSQW7LuPdv3m1IrSN33nVxH/kID6OIKy+FMwGB2k=
google.golang.org/genproto v0.0.0-20230119192704-9d59e20e5cd1/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230119192704-9d59e20e5cd1/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw=
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM=
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@ -173,15 +314,23 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk=
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -1,53 +1,71 @@
package app package app
import ( import (
"context" "context"
"time" "os/signal"
"syscall"
"github.com/sirupsen/logrus" "time"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/initialize" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/server" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/initialize"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/mongo" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/server"
) "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/closer"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/mongo"
func Run(config *core.Config, logger *logrus.Logger) { )
ctx, cancel := context.WithCancel(context.Background())
const (
database, err := mongo.Connect(ctx, &mongo.ConnectDeps{ shutdownTimeout = 5 * time.Second
Configuration: &config.Database.Connection, )
Timeout: 10 * time.Second,
}) func Run(config *core.Config, logger *logrus.Logger) {
if err != nil { ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
logger.Fatalln("failed connection to db: ", err) defer cancel()
panic(err)
} closer := closer.New()
repositories := initialize.NewRepositories(&initialize.RepositoriesDeps{ database, err := mongo.Connect(ctx, &mongo.ConnectDeps{
Logger: logger, Configuration: &config.Database.Connection,
Database: database, Timeout: 10 * time.Second,
}) })
if err != nil {
services := initialize.NewServices(&initialize.ServicesDeps{ logger.Fatalln("failed connection to db: ", err)
Logger: logger, panic(err)
Respositories: repositories, }
})
repositories := initialize.NewRepositories(&initialize.RepositoriesDeps{
controllers := initialize.NewControllers(&initialize.ControllersDeps{ Logger: logger,
Logger: logger, Database: database,
Services: services, })
})
services := initialize.NewServices(&initialize.ServicesDeps{
serverGRPC := server.NewGRPC(logger).Register(controllers.DiscountController) Logger: logger,
serverHTTP := server.NewHTTP(logger).Register(ctx, &config.GRPC) Repositories: repositories,
})
go serverGRPC.Run(&config.GRPC)
go serverHTTP.Run(&config.HTTP) controllers := initialize.NewControllers(&initialize.ControllersDeps{
Logger: logger,
gracefulShutdown(ctx, &gracefulShutdownDeps{ Services: services,
serverGRPC: serverGRPC, })
database: database.Client(),
cancel: cancel, serverGRPC := server.NewGRPC(logger).Register(controllers.DiscountController)
}) serverHTTP := server.NewHTTP(logger).Register(ctx, &config.GRPC)
}
go serverGRPC.Run(&config.GRPC)
go serverHTTP.Run(&config.HTTP)
closer.Add(database.Client().Disconnect)
closer.Add(serverGRPC.Stop)
<-ctx.Done()
logger.Info("shutting down app gracefully")
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
if err := closer.Close(shutdownCtx); err != nil {
logger.Errorf("failed to close with graceful shutdown: %v", err)
}
}

@ -1,29 +0,0 @@
package app
import (
"context"
"os"
"os/signal"
"syscall"
"go.mongodb.org/mongo-driver/mongo"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/server"
)
type gracefulShutdownDeps struct {
serverGRPC *server.GRPC
database *mongo.Client
cancel context.CancelFunc
}
func gracefulShutdown(ctx context.Context, deps *gracefulShutdownDeps) {
defer deps.serverGRPC.Stop()
defer deps.database.Disconnect(ctx)
defer deps.cancel()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
<-quit
}

@ -1,31 +1,31 @@
package controller package controller
import ( import (
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/repository" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/repository"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/service" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/service"
) )
var GRPCStatuses = map[error]codes.Code{ var GRPCStatuses = map[error]codes.Code{
repository.ErrEmptyArgs: codes.InvalidArgument, repository.ErrEmptyArgs: codes.InvalidArgument,
repository.ErrFindRecord: codes.Internal, repository.ErrFindRecord: codes.Internal,
repository.ErrInsertRecord: codes.Internal, repository.ErrInsertRecord: codes.Internal,
repository.ErrInvalidID: codes.InvalidArgument, repository.ErrInvalidID: codes.InvalidArgument,
repository.ErrMethodNotImplemented: codes.Unimplemented, repository.ErrMethodNotImplemented: codes.Unimplemented,
repository.ErrNoRecord: codes.NotFound, repository.ErrNoRecord: codes.NotFound,
repository.ErrTransaction: codes.Internal, repository.ErrTransaction: codes.Internal,
repository.ErrTransactionSessionStart: codes.Internal, repository.ErrTransactionSessionStart: codes.Internal,
repository.ErrUpdateRecord: codes.Internal, repository.ErrUpdateRecord: codes.Internal,
service.ErrInvalidInputValue: codes.InvalidArgument, service.ErrInvalidInputValue: codes.InvalidArgument,
service.ErrInvalidReturnValue: codes.Internal, service.ErrInvalidReturnValue: codes.Internal,
} }
func determineError(message string, err error) error { func determineError(message string, err error) error {
currentStatus, ok := GRPCStatuses[err] currentStatus, ok := GRPCStatuses[err]
if !ok { if !ok {
return status.Errorf(codes.Internal, message, err) return status.Errorf(codes.Internal, message, err)
} }
return status.Errorf(currentStatus, message, err) return status.Errorf(currentStatus, message, err)
} }

@ -1,110 +1,109 @@
package controller package controller
import ( import (
"context" "context"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer" )
)
type DiscountService interface {
type DiscountService interface { ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error)
ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error) GetDiscountByID(ctx context.Context, id string) (*models.Discount, error)
GetDiscountByID(ctx context.Context, id string) (*models.Discount, error) GetDiscountsByUserID(ctx context.Context, id string) ([]models.Discount, error)
GetDiscountsByUserID(ctx context.Context, id string) ([]models.Discount, error) GetAllDiscounts(ctx context.Context) ([]models.Discount, error)
GetAllDiscounts(ctx context.Context) ([]models.Discount, error) DeleteDiscount(ctx context.Context, id string) (*models.Discount, error)
DeleteDiscount(ctx context.Context, id string) (*models.Discount, error) CreateDiscount(ctx context.Context, discount *models.Discount) (*models.Discount, error)
CreateDiscount(ctx context.Context, discount *models.Discount) (*models.Discount, error) }
}
type DiscountControllerDeps struct {
type DiscountControllerDeps struct { Logger *logrus.Logger
Logger *logrus.Logger DiscountService DiscountService
DiscountService DiscountService }
}
/*
/* TODO:
TODO:
1) Добавить трассировку
1) Добавить трассировку 2) Добавить валидацию
2) Добавить валидацию */
*/
type DiscountController struct {
type DiscountController struct { logger *logrus.Logger
logger *logrus.Logger discountService DiscountService
discountService DiscountService
discount.UnimplementedDiscountServiceServer
discount.UnimplementedDiscountServiceServer }
}
func NewDiscountController(deps *DiscountControllerDeps) *DiscountController {
func NewDiscountController(deps *DiscountControllerDeps) *DiscountController { return &DiscountController{
return &DiscountController{ discountService: deps.DiscountService,
discountService: deps.DiscountService, logger: deps.Logger,
logger: deps.Logger, }
} }
}
func (receiver *DiscountController) ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error) {
func (receiver *DiscountController) ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error) { response, err := receiver.discountService.ApplyDiscounts(ctx, request)
response, err := receiver.discountService.ApplyDiscounts(ctx, request) if err != nil {
if err != nil { return nil, determineError("failed to apply discounts: %v", err)
return nil, determineError("failed to apply discounts: %v", err) }
}
return response, nil
return response, nil }
}
func (receiver *DiscountController) GetDiscountByID(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discount, error) {
func (receiver *DiscountController) GetDiscountByID(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discount, error) { findedDiscount, err := receiver.discountService.GetDiscountByID(ctx, request.ID)
findedDiscount, err := receiver.discountService.GetDiscountByID(ctx, request.ID) if err != nil {
if err != nil { return nil, determineError("failed to get discount by id: %v", err)
return nil, determineError("failed to get discount by id: %v", err) }
}
return transfer.DiscountModelToProto(*findedDiscount), nil
return transfer.DiscountModelToProto(*findedDiscount), nil }
}
func (receiver *DiscountController) CreateDiscount(ctx context.Context, request *discount.CreateDiscountRequest) (*discount.Discount, error) {
func (receiver *DiscountController) CreateDiscount(ctx context.Context, request *discount.CreateDiscountRequest) (*discount.Discount, error) { target := transfer.DiscountCalculationTargetProtoToModel(request.Target)
target := transfer.DiscountCalculationTargetProtoToModel(request.Target) condition := transfer.DiscountConditionProtoToModel(request.Condition)
condition := transfer.DiscountConditionProtoToModel(request.Condition)
findedDiscount, err := receiver.discountService.CreateDiscount(ctx, &models.Discount{
findedDiscount, err := receiver.discountService.CreateDiscount(ctx, &models.Discount{ Target: *target,
Target: *target, Condition: *condition,
Condition: *condition, Name: request.Name,
Name: request.Name, Description: request.Description,
Description: request.Description, Layer: request.Layer,
Layer: request.Layer, })
}) if err != nil {
if err != nil { return nil, determineError("failed to create discount: %v", err)
return nil, determineError("failed to create discount: %v", err) }
}
return transfer.DiscountModelToProto(*findedDiscount), nil
return transfer.DiscountModelToProto(*findedDiscount), nil }
}
func (receiver *DiscountController) DeleteDiscount(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discount, error) {
func (receiver *DiscountController) DeleteDiscount(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discount, error) { deletedDiscount, err := receiver.discountService.DeleteDiscount(ctx, request.ID)
deletedDiscount, err := receiver.discountService.DeleteDiscount(ctx, request.ID) if err != nil {
if err != nil { return nil, determineError("failed to delete discount: %v", err)
return nil, determineError("failed to delete discount: %v", err) }
}
return transfer.DiscountModelToProto(*deletedDiscount), nil
return transfer.DiscountModelToProto(*deletedDiscount), nil }
}
func (receiver *DiscountController) GetUserDiscounts(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discounts, error) {
func (receiver *DiscountController) GetUserDiscounts(ctx context.Context, request *discount.GetDiscountByIDRequest) (*discount.Discounts, error) { userDiscounts, err := receiver.discountService.GetDiscountsByUserID(ctx, request.ID)
userDiscounts, err := receiver.discountService.GetDiscountsByUserID(ctx, request.ID) if err != nil {
if err != nil { return nil, determineError("failed to get user discounts: %v", err)
return nil, determineError("failed to get user discounts: %v", err) }
}
return transfer.DiscountsModelToProto(userDiscounts), nil
return transfer.DiscountsModelToProto(userDiscounts), nil }
}
func (receiver *DiscountController) GetAllDiscounts(ctx context.Context, _ *emptypb.Empty) (*discount.Discounts, error) {
func (receiver *DiscountController) GetAllDiscounts(ctx context.Context, _ *emptypb.Empty) (*discount.Discounts, error) { discounts, err := receiver.discountService.GetAllDiscounts(ctx)
discounts, err := receiver.discountService.GetAllDiscounts(ctx) if err != nil {
if err != nil { return nil, determineError("failed to delete discount: %v", err)
return nil, determineError("failed to delete discount: %v", err) }
}
return transfer.DiscountsModelToProto(discounts), nil
return transfer.DiscountsModelToProto(discounts), nil }
}

@ -1,22 +1,22 @@
package core package core
import "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/mongo" import "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/mongo"
type Config struct { type Config struct {
GRPC GRPCConfiguration GRPC GRPCConfiguration
HTTP HTTPConfiguration HTTP HTTPConfiguration
Database DatabaseConfiguration Database DatabaseConfiguration
} }
type GRPCConfiguration struct { type GRPCConfiguration struct {
Host string `env:"GRPC_HOST,default=localhost"` Host string `env:"GRPC_HOST,default=localhost"`
Port string `env:"GPRC_PORT,default=9001"` Port string `env:"GPRC_PORT,default=9001"`
} }
type HTTPConfiguration struct { type HTTPConfiguration struct {
Port string `env:"HTTP_PORT,default=8001"` Port string `env:"HTTP_PORT,default=8001"`
} }
type DatabaseConfiguration struct { type DatabaseConfiguration struct {
Connection mongo.Configuration Connection mongo.Configuration
} }

@ -1,31 +1,31 @@
package core package core
import ( import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
) )
type DiscountConditions struct { type DiscountConditions struct {
Common CommonDiscountCondition `json:"common"` Common CommonDiscountCondition `json:"common"`
Optionals []OptionalDiscounCondition `json:"optionals"` Optionals []OptionalDiscounCondition `json:"optionals"`
} }
type CommonDiscountCondition struct { type CommonDiscountCondition struct {
Period models.PeriodCondition `json:"period"` Period models.PeriodCondition `json:"period"`
User string `json:"user"` User string `json:"user"`
UserType string `json:"userType"` UserType string `json:"userType"`
Coupon *string `json:"coupon"` Coupon *string `json:"coupon"`
PurchasesAmount float64 `json:"purchasesAmount"` PurchasesAmount uint64 `json:"purchasesAmount"`
CartPurchasesAmount float64 `json:"cartPurchasesAmount"` CartPurchasesAmount uint64 `json:"cartPurchasesAmount"`
} }
type OptionalDiscounCondition struct { type OptionalDiscounCondition struct {
Product *string `json:"product"` Product *string `json:"product"`
Group *string `json:"group"` Group *string `json:"group"`
PriceFrom *float64 `json:"priceFrom"` PriceFrom *uint64 `json:"priceFrom"`
/* Срок использования (количество дней использования) */ /* Срок использования (количество дней использования) */
Term *uint64 `json:"term"` Term *uint64 `json:"term"`
/* Количество использований (количество попыток) */ /* Количество использований (количество попыток) */
Usage *uint64 `json:"usage"` Usage *uint64 `json:"usage"`
} }

@ -1,7 +1,7 @@
package core package core
type Product struct { type Product struct {
ID string `json:"id"` ID string `json:"id"`
Price float64 `json:"price"` Price float64 `json:"price"`
Group string `json:"group"` Group string `json:"group"`
} }

@ -1,31 +1,31 @@
package core package core
import "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" import "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
type UpdateDiscountSettings struct { type UpdateDiscountSettings struct {
ID string ID string
Condition *models.DiscountCondition Condition *models.DiscountCondition
Name *string Name *string
Description *string Description *string
Layer *uint32 Layer *uint32
Deprecated *bool Deprecated *bool
Products *[]models.ProductTarget Products *[]models.ProductTarget
Factor *float64 Factor *float64
TargetScope *models.TargetScope TargetScope *models.TargetScope
TargetGroup *string TargetGroup *string
Overhelm *bool Overhelm *bool
} }
type FilterDiscountSettings struct { type FilterDiscountSettings struct {
ID *string ID *string
Condition *models.DiscountCondition Condition *models.DiscountCondition
Name *string Name *string
Description *string Description *string
Layer *uint32 Layer *uint32
Deprecated *bool Deprecated *bool
Products *[]models.ProductTarget Products *[]models.ProductTarget
Factor *float64 Factor *float64
TargetScope *models.TargetScope TargetScope *models.TargetScope
TargetGroup *string TargetGroup *string
Overhelm *bool Overhelm *bool
} }

@ -1,15 +1,15 @@
package fields package fields
import "fmt" import "fmt"
var Audit = struct { var Audit = struct {
UpdatedAt string UpdatedAt string
DeletedAt string DeletedAt string
CreatedAt string CreatedAt string
Deleted string Deleted string
}{ }{
UpdatedAt: fmt.Sprintf("%s.updatedAt", Discount.Audit), UpdatedAt: fmt.Sprintf("%s.updatedAt", Discount.Audit),
DeletedAt: fmt.Sprintf("%s.deletedAt", Discount.Audit), DeletedAt: fmt.Sprintf("%s.deletedAt", Discount.Audit),
CreatedAt: fmt.Sprintf("%s.createdAt", Discount.Audit), CreatedAt: fmt.Sprintf("%s.createdAt", Discount.Audit),
Deleted: fmt.Sprintf("%s.deleted", Discount.Audit), Deleted: fmt.Sprintf("%s.deleted", Discount.Audit),
} }

@ -1,71 +1,71 @@
package fields package fields
import "fmt" import "fmt"
var Discount = struct { var Discount = struct {
ID string ID string
Name string Name string
Description string Description string
Target string Target string
Condition string Condition string
Layer string Layer string
Deprecated string Deprecated string
Audit string Audit string
}{ }{
ID: "_id", ID: "_id",
Name: "name", Name: "name",
Description: "description", Description: "description",
Target: "target", Target: "target",
Condition: "condition", Condition: "condition",
Layer: "layer", Layer: "layer",
Deprecated: "deprecated", Deprecated: "deprecated",
Audit: "audit", Audit: "audit",
} }
var DiscountTarget = struct { var DiscountTarget = struct {
Products string Products string
Factor string Factor string
TargetScope string TargetScope string
TargetGroup string TargetGroup string
Overhelm string Overhelm string
}{ }{
Products: fmt.Sprintf("%s.products", Discount.Target), Products: fmt.Sprintf("%s.products", Discount.Target),
Factor: fmt.Sprintf("%s.factor", Discount.Target), Factor: fmt.Sprintf("%s.factor", Discount.Target),
TargetScope: fmt.Sprintf("%s.scope", Discount.Target), TargetScope: fmt.Sprintf("%s.scope", Discount.Target),
TargetGroup: fmt.Sprintf("%s.group", Discount.Target), TargetGroup: fmt.Sprintf("%s.group", Discount.Target),
Overhelm: fmt.Sprintf("%s.overhelm", Discount.Target), Overhelm: fmt.Sprintf("%s.overhelm", Discount.Target),
} }
var DiscountCondition = struct { var DiscountCondition = struct {
Period string Period string
Product string Product string
Term string Term string
Usage string Usage string
PriceFrom string PriceFrom string
Group string Group string
User string User string
UserType string UserType string
Coupon string Coupon string
PurchasesAmount string PurchasesAmount string
CartPurchasesAmount string CartPurchasesAmount string
}{ }{
Period: fmt.Sprintf("%s.period", Discount.Condition), Period: fmt.Sprintf("%s.period", Discount.Condition),
Product: fmt.Sprintf("%s.product", Discount.Condition), Product: fmt.Sprintf("%s.product", Discount.Condition),
Term: fmt.Sprintf("%s.term", Discount.Condition), Term: fmt.Sprintf("%s.term", Discount.Condition),
Usage: fmt.Sprintf("%s.usage", Discount.Condition), Usage: fmt.Sprintf("%s.usage", Discount.Condition),
PriceFrom: fmt.Sprintf("%s.priceFrom", Discount.Condition), PriceFrom: fmt.Sprintf("%s.priceFrom", Discount.Condition),
Group: fmt.Sprintf("%s.group", Discount.Condition), Group: fmt.Sprintf("%s.group", Discount.Condition),
User: fmt.Sprintf("%s.user", Discount.Condition), User: fmt.Sprintf("%s.user", Discount.Condition),
UserType: fmt.Sprintf("%s.userType", Discount.Condition), UserType: fmt.Sprintf("%s.userType", Discount.Condition),
Coupon: fmt.Sprintf("%s.coupon", Discount.Condition), Coupon: fmt.Sprintf("%s.coupon", Discount.Condition),
PurchasesAmount: fmt.Sprintf("%s.purchasesAmount", Discount.Condition), PurchasesAmount: fmt.Sprintf("%s.purchasesAmount", Discount.Condition),
CartPurchasesAmount: fmt.Sprintf("%s.cartPurchasesAmount", Discount.Condition), CartPurchasesAmount: fmt.Sprintf("%s.cartPurchasesAmount", Discount.Condition),
} }
var PeriodCondition = struct { var PeriodCondition = struct {
From string From string
To string To string
}{ }{
From: fmt.Sprintf("%s.from", DiscountCondition.Period), From: fmt.Sprintf("%s.from", DiscountCondition.Period),
To: fmt.Sprintf("%s.to", DiscountCondition.Period), To: fmt.Sprintf("%s.to", DiscountCondition.Period),
} }

@ -1,24 +1,24 @@
package initialize package initialize
import ( import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/controller" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/controller"
) )
type ControllersDeps struct { type ControllersDeps struct {
Logger *logrus.Logger Logger *logrus.Logger
Services *Services Services *Services
} }
type Controllers struct { type Controllers struct {
DiscountController *controller.DiscountController DiscountController *controller.DiscountController
} }
func NewControllers(deps *ControllersDeps) *Controllers { func NewControllers(deps *ControllersDeps) *Controllers {
return &Controllers{ return &Controllers{
DiscountController: controller.NewDiscountController(&controller.DiscountControllerDeps{ DiscountController: controller.NewDiscountController(&controller.DiscountControllerDeps{
Logger: deps.Logger, Logger: deps.Logger,
DiscountService: deps.Services.DiscountService, DiscountService: deps.Services.DiscountService,
}), }),
} }
} }

@ -1,23 +1,22 @@
package initialize package initialize
import ( import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/repository"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/repository" )
)
type RepositoriesDeps struct {
type RepositoriesDeps struct { Database *mongo.Database
Database *mongo.Database Logger *logrus.Logger
Logger *logrus.Logger }
}
type Repositories struct {
type Repositories struct { DiscountRepository *repository.DiscountRepository
DiscountRepository *repository.DiscountRepository }
}
func NewRepositories(deps *RepositoriesDeps) *Repositories {
func NewRepositories(deps *RepositoriesDeps) *Repositories { return &Repositories{
return &Repositories{ DiscountRepository: repository.NewDiscountRepository(deps.Database.Collection("discounts"), deps.Logger),
DiscountRepository: repository.NewDiscountRepository(deps.Database.Collection("discounts"), deps.Logger), }
} }
}

@ -1,24 +1,24 @@
package initialize package initialize
import ( import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/service" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/service"
) )
type ServicesDeps struct { type ServicesDeps struct {
Logger *logrus.Logger Logger *logrus.Logger
Respositories *Repositories Repositories *Repositories
} }
type Services struct { type Services struct {
DiscountService *service.DiscountService DiscountService *service.DiscountService
} }
func NewServices(deps *ServicesDeps) *Services { func NewServices(deps *ServicesDeps) *Services {
return &Services{ return &Services{
DiscountService: service.NewDiscountService(&service.DiscountServiceDeps{ DiscountService: service.NewDiscountService(&service.DiscountServiceDeps{
Logger: deps.Logger, Logger: deps.Logger,
DiscountRepository: deps.Respositories.DiscountRepository, DiscountRepository: deps.Repositories.DiscountRepository,
}), }),
} }
} }

@ -36,19 +36,19 @@ type DiscountCalculationTarget struct {
type DiscountCondition struct { type DiscountCondition struct {
Period *PeriodCondition `json:"period,omitempty" bson:"period,omitempty"` Period *PeriodCondition `json:"period,omitempty" bson:"period,omitempty"`
Product *string `json:"product,omitempty" bson:"product,omitempty"` Product *string `json:"product,omitempty" bson:"product,omitempty"`
PriceFrom *float64 `json:"priceFrom,omitempty" bson:"priceFrom,omitempty"` PriceFrom *uint64 `json:"priceFrom,omitempty" bson:"priceFrom,omitempty"`
Group *string `json:"group,omitempty" bson:"group,omitempty"` Group *string `json:"group,omitempty" bson:"group,omitempty"`
User *string `json:"user,omitempty" bson:"user,omitempty"` User *string `json:"user,omitempty" bson:"user,omitempty"`
UserType *string `json:"userType,omitempty" bson:"userType,omitempty"` UserType *string `json:"userType,omitempty" bson:"userType,omitempty"`
Coupon *string `json:"coupon,omitempty" bson:"coupon,omitempty"` Coupon *string `json:"coupon,omitempty" bson:"coupon,omitempty"`
PurchasesAmount *float64 `json:"purchasesAmount,omitempty" bson:"purchasesAmount,omitempty"` PurchasesAmount *uint64 `json:"purchasesAmount,omitempty" bson:"purchasesAmount,omitempty"`
CartPurchasesAmount *float64 `json:"cartPurchasesAmount,omitempty" bson:"cartPurchasesAmount,omitempty"` CartPurchasesAmount *uint64 `json:"cartPurchasesAmount,omitempty" bson:"cartPurchasesAmount,omitempty"`
/* Срок использования (количество дней использования) */ /* Срок использования (количество дней использования) */
Term *uint64 `json:"term,omitempty" bson:"term,omitempty"` Term *uint64 `json:"term" bson:"term"`
/* Количество использований (количество попыток) */ /* Количество использований (количество попыток) */
Usage *uint64 `json:"usage,omitempty" bson:"usage,omitempty"` Usage *uint64 `json:"usage" bson:"usage,omitempty"`
} }
type ProductTarget struct { type ProductTarget struct {
@ -65,9 +65,14 @@ type PeriodCondition struct {
type TargetScope string type TargetScope string
const ( const (
TargetSum TargetScope = "sum" // TargetSum тип скидки, применяющийся на финальную сформированную сумму всех товаров.
TargetSum TargetScope = "sum"
// TargetGroup тип скидки, применяющийся на сумму товаров определённой группы.
TargetGroup TargetScope = "group" TargetGroup TargetScope = "group"
TargetEach TargetScope = "each"
// TargetEach тип скидки, применяющийся на определённый товар.
TargetEach TargetScope = "each"
) )
var TargetScopeModelMap = map[discount.TargetScope]TargetScope{ var TargetScopeModelMap = map[discount.TargetScope]TargetScope{

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.26.0 // protoc-gen-go v1.31.0
// protoc (unknown) // protoc (unknown)
// source: discount/audit.model.proto // source: discount/audit.model.proto

@ -34,6 +34,7 @@
"details": { "details": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object",
"$ref": "#/definitions/protobufAny" "$ref": "#/definitions/protobufAny"
} }
} }

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.26.0 // protoc-gen-go v1.31.0
// protoc (unknown) // protoc (unknown)
// source: discount/discount.model.proto // source: discount/discount.model.proto
@ -397,12 +397,12 @@ type DiscountCondition struct {
User *string `protobuf:"bytes,2,opt,name=User,proto3,oneof" json:"User,omitempty"` User *string `protobuf:"bytes,2,opt,name=User,proto3,oneof" json:"User,omitempty"`
UserType *string `protobuf:"bytes,3,opt,name=UserType,proto3,oneof" json:"UserType,omitempty"` UserType *string `protobuf:"bytes,3,opt,name=UserType,proto3,oneof" json:"UserType,omitempty"`
Coupon *string `protobuf:"bytes,4,opt,name=Coupon,proto3,oneof" json:"Coupon,omitempty"` Coupon *string `protobuf:"bytes,4,opt,name=Coupon,proto3,oneof" json:"Coupon,omitempty"`
PurchasesAmount *float64 `protobuf:"fixed64,5,opt,name=PurchasesAmount,proto3,oneof" json:"PurchasesAmount,omitempty"` PurchasesAmount *uint64 `protobuf:"varint,5,opt,name=PurchasesAmount,proto3,oneof" json:"PurchasesAmount,omitempty"`
CartPurchasesAmount *float64 `protobuf:"fixed64,6,opt,name=CartPurchasesAmount,proto3,oneof" json:"CartPurchasesAmount,omitempty"` CartPurchasesAmount *uint64 `protobuf:"varint,6,opt,name=CartPurchasesAmount,proto3,oneof" json:"CartPurchasesAmount,omitempty"`
Product *string `protobuf:"bytes,7,opt,name=Product,proto3,oneof" json:"Product,omitempty"` Product *string `protobuf:"bytes,7,opt,name=Product,proto3,oneof" json:"Product,omitempty"`
Term *uint64 `protobuf:"varint,8,opt,name=Term,proto3,oneof" json:"Term,omitempty"` Term *uint64 `protobuf:"varint,8,opt,name=Term,proto3,oneof" json:"Term,omitempty"`
Usage *uint64 `protobuf:"varint,9,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"` Usage *uint64 `protobuf:"varint,9,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"`
PriceFrom *float64 `protobuf:"fixed64,10,opt,name=PriceFrom,proto3,oneof" json:"PriceFrom,omitempty"` PriceFrom *uint64 `protobuf:"varint,10,opt,name=PriceFrom,proto3,oneof" json:"PriceFrom,omitempty"`
Group *string `protobuf:"bytes,11,opt,name=Group,proto3,oneof" json:"Group,omitempty"` Group *string `protobuf:"bytes,11,opt,name=Group,proto3,oneof" json:"Group,omitempty"`
} }
@ -466,14 +466,14 @@ func (x *DiscountCondition) GetCoupon() string {
return "" return ""
} }
func (x *DiscountCondition) GetPurchasesAmount() float64 { func (x *DiscountCondition) GetPurchasesAmount() uint64 {
if x != nil && x.PurchasesAmount != nil { if x != nil && x.PurchasesAmount != nil {
return *x.PurchasesAmount return *x.PurchasesAmount
} }
return 0 return 0
} }
func (x *DiscountCondition) GetCartPurchasesAmount() float64 { func (x *DiscountCondition) GetCartPurchasesAmount() uint64 {
if x != nil && x.CartPurchasesAmount != nil { if x != nil && x.CartPurchasesAmount != nil {
return *x.CartPurchasesAmount return *x.CartPurchasesAmount
} }
@ -501,7 +501,7 @@ func (x *DiscountCondition) GetUsage() uint64 {
return 0 return 0
} }
func (x *DiscountCondition) GetPriceFrom() float64 { func (x *DiscountCondition) GetPriceFrom() uint64 {
if x != nil && x.PriceFrom != nil { if x != nil && x.PriceFrom != nil {
return *x.PriceFrom return *x.PriceFrom
} }
@ -638,10 +638,10 @@ type UserInformation struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"` Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"`
PurchasesAmount float64 `protobuf:"fixed64,3,opt,name=PurchasesAmount,proto3" json:"PurchasesAmount,omitempty"` PurchasesAmount uint64 `protobuf:"varint,3,opt,name=PurchasesAmount,proto3" json:"PurchasesAmount,omitempty"`
CartPurchasesAmount float64 `protobuf:"fixed64,4,opt,name=CartPurchasesAmount,proto3" json:"CartPurchasesAmount,omitempty"` CartPurchasesAmount uint64 `protobuf:"varint,4,opt,name=CartPurchasesAmount,proto3" json:"CartPurchasesAmount,omitempty"`
} }
func (x *UserInformation) Reset() { func (x *UserInformation) Reset() {
@ -690,14 +690,14 @@ func (x *UserInformation) GetType() string {
return "" return ""
} }
func (x *UserInformation) GetPurchasesAmount() float64 { func (x *UserInformation) GetPurchasesAmount() uint64 {
if x != nil { if x != nil {
return x.PurchasesAmount return x.PurchasesAmount
} }
return 0 return 0
} }
func (x *UserInformation) GetCartPurchasesAmount() float64 { func (x *UserInformation) GetCartPurchasesAmount() uint64 {
if x != nil { if x != nil {
return x.CartPurchasesAmount return x.CartPurchasesAmount
} }
@ -710,7 +710,7 @@ type ProductInformation struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Price float64 `protobuf:"fixed64,2,opt,name=Price,proto3" json:"Price,omitempty"` Price uint64 `protobuf:"varint,2,opt,name=Price,proto3" json:"Price,omitempty"`
Term *uint64 `protobuf:"varint,3,opt,name=Term,proto3,oneof" json:"Term,omitempty"` Term *uint64 `protobuf:"varint,3,opt,name=Term,proto3,oneof" json:"Term,omitempty"`
Usage *uint64 `protobuf:"varint,4,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"` Usage *uint64 `protobuf:"varint,4,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"`
Group *string `protobuf:"bytes,5,opt,name=Group,proto3,oneof" json:"Group,omitempty"` Group *string `protobuf:"bytes,5,opt,name=Group,proto3,oneof" json:"Group,omitempty"`
@ -755,7 +755,7 @@ func (x *ProductInformation) GetID() string {
return "" return ""
} }
func (x *ProductInformation) GetPrice() float64 { func (x *ProductInformation) GetPrice() uint64 {
if x != nil { if x != nil {
return x.Price return x.Price
} }
@ -868,10 +868,10 @@ var file_discount_discount_model_proto_rawDesc = []byte{
0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x06,
0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0f, 0x50, 0x75, 0x72, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0f, 0x50, 0x75, 0x72,
0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01,
0x28, 0x01, 0x48, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x28, 0x04, 0x48, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x13, 0x43, 0x61, 0x72, 0x74, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x13, 0x43, 0x61, 0x72, 0x74,
0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18,
0x06, 0x20, 0x01, 0x28, 0x01, 0x48, 0x05, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, 0x06, 0x20, 0x01, 0x28, 0x04, 0x48, 0x05, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72,
0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12,
0x1d, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x1d, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
0x48, 0x06, 0x52, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, 0x17, 0x48, 0x06, 0x52, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x88, 0x01, 0x01, 0x12, 0x17,
@ -879,7 +879,7 @@ var file_discount_discount_model_proto_rawDesc = []byte{
0x54, 0x65, 0x72, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x54, 0x65, 0x72, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65,
0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x48, 0x08, 0x52, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x88, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x48, 0x08, 0x52, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x88,
0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x01, 0x48, 0x09, 0x52, 0x09, 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, 0x72, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x48, 0x09, 0x52, 0x09, 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, 0x72,
0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x0b, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x0b,
0x20, 0x01, 0x28, 0x09, 0x48, 0x0a, 0x52, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0a, 0x52, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01,
0x42, 0x09, 0x0a, 0x07, 0x5f, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x5f,
@ -909,14 +909,14 @@ var file_discount_discount_model_proto_rawDesc = []byte{
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x28, 0x0a,
0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65,
0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50,
0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04,
0x20, 0x01, 0x28, 0x01, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61,
0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa6, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa6, 0x01, 0x0a, 0x12, 0x50, 0x72,
0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44,
0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x18, 0x03, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x18, 0x03,
0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x88, 0x01, 0x01, 0x12,
0x19, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x19, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01,

@ -34,6 +34,7 @@
"details": { "details": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object",
"$ref": "#/definitions/protobufAny" "$ref": "#/definitions/protobufAny"
} }
} }

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.26.0 // protoc-gen-go v1.31.0
// protoc (unknown) // protoc (unknown)
// source: discount/service.proto // source: discount/service.proto
@ -146,7 +146,7 @@ type ApplyDiscountResponse struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Price float64 `protobuf:"fixed64,1,opt,name=Price,proto3" json:"Price,omitempty"` Price uint64 `protobuf:"varint,1,opt,name=Price,proto3" json:"Price,omitempty"`
AppliedDiscounts []*Discount `protobuf:"bytes,2,rep,name=AppliedDiscounts,proto3" json:"AppliedDiscounts,omitempty"` AppliedDiscounts []*Discount `protobuf:"bytes,2,rep,name=AppliedDiscounts,proto3" json:"AppliedDiscounts,omitempty"`
} }
@ -182,7 +182,7 @@ func (*ApplyDiscountResponse) Descriptor() ([]byte, []int) {
return file_discount_service_proto_rawDescGZIP(), []int{2} return file_discount_service_proto_rawDescGZIP(), []int{2}
} }
func (x *ApplyDiscountResponse) GetPrice() float64 { func (x *ApplyDiscountResponse) GetPrice() uint64 {
if x != nil { if x != nil {
return x.Price return x.Price
} }
@ -307,7 +307,7 @@ var file_discount_service_proto_rawDesc = []byte{
0x00, 0x52, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x00, 0x52, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07,
0x5f, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x15, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x5f, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x15, 0x41, 0x70, 0x70, 0x6c, 0x79,
0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65,
0x64, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x64, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x32, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63,

@ -681,7 +681,7 @@ func RegisterDiscountServiceHandlerServer(ctx context.Context, mux *runtime.Serv
// RegisterDiscountServiceHandlerFromEndpoint is same as RegisterDiscountServiceHandler but // RegisterDiscountServiceHandlerFromEndpoint is same as RegisterDiscountServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done. // automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterDiscountServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { func RegisterDiscountServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...) conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil { if err != nil {
return err return err
} }

@ -341,6 +341,7 @@
"Products": { "Products": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object",
"$ref": "#/definitions/discountProductInformation" "$ref": "#/definitions/discountProductInformation"
} }
}, },
@ -363,6 +364,7 @@
"AppliedDiscounts": { "AppliedDiscounts": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object",
"$ref": "#/definitions/discountDiscount" "$ref": "#/definitions/discountDiscount"
} }
} }
@ -445,6 +447,7 @@
"Products": { "Products": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object",
"$ref": "#/definitions/discountProductTarget" "$ref": "#/definitions/discountProductTarget"
} }
}, },
@ -512,6 +515,7 @@
"Discounts": { "Discounts": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object",
"$ref": "#/definitions/discountDiscount" "$ref": "#/definitions/discountDiscount"
} }
} }
@ -618,6 +622,7 @@
"details": { "details": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object",
"$ref": "#/definitions/protobufAny" "$ref": "#/definitions/protobufAny"
} }
} }

@ -1,4 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: discount/service.proto
package discount package discount
@ -15,6 +19,18 @@ import (
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion7
const (
DiscountService_GetAllDiscounts_FullMethodName = "/discount.DiscountService/GetAllDiscounts"
DiscountService_GetUserDiscounts_FullMethodName = "/discount.DiscountService/GetUserDiscounts"
DiscountService_DetermineDiscounts_FullMethodName = "/discount.DiscountService/DetermineDiscounts"
DiscountService_ApplyDiscounts_FullMethodName = "/discount.DiscountService/ApplyDiscounts"
DiscountService_GetDiscountByID_FullMethodName = "/discount.DiscountService/GetDiscountByID"
DiscountService_CreateDiscount_FullMethodName = "/discount.DiscountService/CreateDiscount"
DiscountService_ReplaceDiscount_FullMethodName = "/discount.DiscountService/ReplaceDiscount"
DiscountService_UpdateDiscount_FullMethodName = "/discount.DiscountService/UpdateDiscount"
DiscountService_DeleteDiscount_FullMethodName = "/discount.DiscountService/DeleteDiscount"
)
// DiscountServiceClient is the client API for DiscountService service. // DiscountServiceClient is the client API for DiscountService 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. // 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.
@ -40,7 +56,7 @@ func NewDiscountServiceClient(cc grpc.ClientConnInterface) DiscountServiceClient
func (c *discountServiceClient) GetAllDiscounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Discounts, error) { func (c *discountServiceClient) GetAllDiscounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Discounts, error) {
out := new(Discounts) out := new(Discounts)
err := c.cc.Invoke(ctx, "/discount.DiscountService/GetAllDiscounts", in, out, opts...) err := c.cc.Invoke(ctx, DiscountService_GetAllDiscounts_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -49,7 +65,7 @@ func (c *discountServiceClient) GetAllDiscounts(ctx context.Context, in *emptypb
func (c *discountServiceClient) GetUserDiscounts(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discounts, error) { func (c *discountServiceClient) GetUserDiscounts(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discounts, error) {
out := new(Discounts) out := new(Discounts)
err := c.cc.Invoke(ctx, "/discount.DiscountService/GetUserDiscounts", in, out, opts...) err := c.cc.Invoke(ctx, DiscountService_GetUserDiscounts_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -58,7 +74,7 @@ func (c *discountServiceClient) GetUserDiscounts(ctx context.Context, in *GetDis
func (c *discountServiceClient) DetermineDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*Discounts, error) { func (c *discountServiceClient) DetermineDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*Discounts, error) {
out := new(Discounts) out := new(Discounts)
err := c.cc.Invoke(ctx, "/discount.DiscountService/DetermineDiscounts", in, out, opts...) err := c.cc.Invoke(ctx, DiscountService_DetermineDiscounts_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -67,7 +83,7 @@ func (c *discountServiceClient) DetermineDiscounts(ctx context.Context, in *Appl
func (c *discountServiceClient) ApplyDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*ApplyDiscountResponse, error) { func (c *discountServiceClient) ApplyDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*ApplyDiscountResponse, error) {
out := new(ApplyDiscountResponse) out := new(ApplyDiscountResponse)
err := c.cc.Invoke(ctx, "/discount.DiscountService/ApplyDiscounts", in, out, opts...) err := c.cc.Invoke(ctx, DiscountService_ApplyDiscounts_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -76,7 +92,7 @@ func (c *discountServiceClient) ApplyDiscounts(ctx context.Context, in *ApplyDis
func (c *discountServiceClient) GetDiscountByID(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) { func (c *discountServiceClient) GetDiscountByID(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount) out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/GetDiscountByID", in, out, opts...) err := c.cc.Invoke(ctx, DiscountService_GetDiscountByID_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -85,7 +101,7 @@ func (c *discountServiceClient) GetDiscountByID(ctx context.Context, in *GetDisc
func (c *discountServiceClient) CreateDiscount(ctx context.Context, in *CreateDiscountRequest, opts ...grpc.CallOption) (*Discount, error) { func (c *discountServiceClient) CreateDiscount(ctx context.Context, in *CreateDiscountRequest, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount) out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/CreateDiscount", in, out, opts...) err := c.cc.Invoke(ctx, DiscountService_CreateDiscount_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -94,7 +110,7 @@ func (c *discountServiceClient) CreateDiscount(ctx context.Context, in *CreateDi
func (c *discountServiceClient) ReplaceDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) { func (c *discountServiceClient) ReplaceDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount) out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/ReplaceDiscount", in, out, opts...) err := c.cc.Invoke(ctx, DiscountService_ReplaceDiscount_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -103,7 +119,7 @@ func (c *discountServiceClient) ReplaceDiscount(ctx context.Context, in *Discoun
func (c *discountServiceClient) UpdateDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) { func (c *discountServiceClient) UpdateDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount) out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/UpdateDiscount", in, out, opts...) err := c.cc.Invoke(ctx, DiscountService_UpdateDiscount_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -112,7 +128,7 @@ func (c *discountServiceClient) UpdateDiscount(ctx context.Context, in *Discount
func (c *discountServiceClient) DeleteDiscount(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) { func (c *discountServiceClient) DeleteDiscount(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) {
out := new(Discount) out := new(Discount)
err := c.cc.Invoke(ctx, "/discount.DiscountService/DeleteDiscount", in, out, opts...) err := c.cc.Invoke(ctx, DiscountService_DeleteDiscount_FullMethodName, in, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -187,7 +203,7 @@ func _DiscountService_GetAllDiscounts_Handler(srv interface{}, ctx context.Conte
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/discount.DiscountService/GetAllDiscounts", FullMethod: DiscountService_GetAllDiscounts_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).GetAllDiscounts(ctx, req.(*emptypb.Empty)) return srv.(DiscountServiceServer).GetAllDiscounts(ctx, req.(*emptypb.Empty))
@ -205,7 +221,7 @@ func _DiscountService_GetUserDiscounts_Handler(srv interface{}, ctx context.Cont
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/discount.DiscountService/GetUserDiscounts", FullMethod: DiscountService_GetUserDiscounts_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).GetUserDiscounts(ctx, req.(*GetDiscountByIDRequest)) return srv.(DiscountServiceServer).GetUserDiscounts(ctx, req.(*GetDiscountByIDRequest))
@ -223,7 +239,7 @@ func _DiscountService_DetermineDiscounts_Handler(srv interface{}, ctx context.Co
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/discount.DiscountService/DetermineDiscounts", FullMethod: DiscountService_DetermineDiscounts_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).DetermineDiscounts(ctx, req.(*ApplyDiscountRequest)) return srv.(DiscountServiceServer).DetermineDiscounts(ctx, req.(*ApplyDiscountRequest))
@ -241,7 +257,7 @@ func _DiscountService_ApplyDiscounts_Handler(srv interface{}, ctx context.Contex
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/discount.DiscountService/ApplyDiscounts", FullMethod: DiscountService_ApplyDiscounts_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).ApplyDiscounts(ctx, req.(*ApplyDiscountRequest)) return srv.(DiscountServiceServer).ApplyDiscounts(ctx, req.(*ApplyDiscountRequest))
@ -259,7 +275,7 @@ func _DiscountService_GetDiscountByID_Handler(srv interface{}, ctx context.Conte
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/discount.DiscountService/GetDiscountByID", FullMethod: DiscountService_GetDiscountByID_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).GetDiscountByID(ctx, req.(*GetDiscountByIDRequest)) return srv.(DiscountServiceServer).GetDiscountByID(ctx, req.(*GetDiscountByIDRequest))
@ -277,7 +293,7 @@ func _DiscountService_CreateDiscount_Handler(srv interface{}, ctx context.Contex
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/discount.DiscountService/CreateDiscount", FullMethod: DiscountService_CreateDiscount_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).CreateDiscount(ctx, req.(*CreateDiscountRequest)) return srv.(DiscountServiceServer).CreateDiscount(ctx, req.(*CreateDiscountRequest))
@ -295,7 +311,7 @@ func _DiscountService_ReplaceDiscount_Handler(srv interface{}, ctx context.Conte
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/discount.DiscountService/ReplaceDiscount", FullMethod: DiscountService_ReplaceDiscount_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).ReplaceDiscount(ctx, req.(*DiscountOptional)) return srv.(DiscountServiceServer).ReplaceDiscount(ctx, req.(*DiscountOptional))
@ -313,7 +329,7 @@ func _DiscountService_UpdateDiscount_Handler(srv interface{}, ctx context.Contex
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/discount.DiscountService/UpdateDiscount", FullMethod: DiscountService_UpdateDiscount_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).UpdateDiscount(ctx, req.(*DiscountOptional)) return srv.(DiscountServiceServer).UpdateDiscount(ctx, req.(*DiscountOptional))
@ -331,7 +347,7 @@ func _DiscountService_DeleteDiscount_Handler(srv interface{}, ctx context.Contex
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/discount.DiscountService/DeleteDiscount", FullMethod: DiscountService_DeleteDiscount_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiscountServiceServer).DeleteDiscount(ctx, req.(*GetDiscountByIDRequest)) return srv.(DiscountServiceServer).DeleteDiscount(ctx, req.(*GetDiscountByIDRequest))

@ -11,7 +11,6 @@ import (
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readconcern" "go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/writeconcern" "go.mongodb.org/mongo-driver/mongo/writeconcern"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
@ -184,7 +183,9 @@ func (receiver *DiscountRepository) UpdateMany(ctx context.Context, updates []co
if _, transactionError := session.WithTransaction(ctx, func(sessionContext mongo.SessionContext) (interface{}, error) { if _, transactionError := session.WithTransaction(ctx, func(sessionContext mongo.SessionContext) (interface{}, error) {
for _, update := range updates { for _, update := range updates {
if err := receiver.UpdateOne(sessionContext, &update); err != nil { updateCopy := update
if err := receiver.UpdateOne(sessionContext, &updateCopy); err != nil {
receiver.logger.Errorf("failed to update <%s> in transaction on <UpdateMany> of <DiscountRepository>: %v", update.ID, err) receiver.logger.Errorf("failed to update <%s> in transaction on <UpdateMany> of <DiscountRepository>: %v", update.ID, err)
return nil, err return nil, err
} }

@ -1,73 +1,72 @@
package server package server
import ( import (
"fmt" "context"
"net" "fmt"
"net"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
"github.com/sirupsen/logrus" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
"google.golang.org/grpc" "github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "google.golang.org/grpc"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
) "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
)
type GRPC struct {
logger *logrus.Entry type GRPC struct {
grpc *grpc.Server logger *logrus.Entry
} grpc *grpc.Server
}
func NewGRPC(logger *logrus.Logger) *GRPC {
grpcLogger := logrus.NewEntry(logger) func NewGRPC(logger *logrus.Logger) *GRPC {
grpcLogger := logrus.NewEntry(logger)
grpcStreamInterceptor := grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_logrus.StreamServerInterceptor(grpcLogger), grpcStreamInterceptor := grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_recovery.StreamServerInterceptor(), grpc_logrus.StreamServerInterceptor(grpcLogger),
)) grpc_recovery.StreamServerInterceptor(),
))
grpcUnaryInterceptor := grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_logrus.UnaryServerInterceptor(grpcLogger), grpcUnaryInterceptor := grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_recovery.UnaryServerInterceptor(), grpc_logrus.UnaryServerInterceptor(grpcLogger),
)) grpc_recovery.UnaryServerInterceptor(),
))
return &GRPC{
grpc: grpc.NewServer(grpcStreamInterceptor, grpcUnaryInterceptor), return &GRPC{
logger: grpcLogger, grpc: grpc.NewServer(grpcStreamInterceptor, grpcUnaryInterceptor),
} logger: grpcLogger,
} }
}
func (receiver *GRPC) Run(config *core.GRPCConfiguration) {
connectionString := fmt.Sprintf("%s:%s", config.Host, config.Port) func (receiver *GRPC) Run(config *core.GRPCConfiguration) {
connectionString := fmt.Sprintf("%s:%s", config.Host, config.Port)
receiver.logger.Infof("Starting GRPC Server on %s", connectionString)
receiver.logger.Infof("Starting GRPC Server on %s", connectionString)
if err := receiver.listen(connectionString); err != nil && err != grpc.ErrServerStopped {
receiver.logger.Errorf("GRPC Listen error: %v", err) if err := receiver.listen(connectionString); err != nil && err != grpc.ErrServerStopped {
panic(err) receiver.logger.Errorf("GRPC Listen error: %v", err)
} panic(err)
} }
}
func (receiver *GRPC) Register(server discount.DiscountServiceServer) *GRPC {
discount.RegisterDiscountServiceServer(receiver.grpc, server) func (receiver *GRPC) Register(server discount.DiscountServiceServer) *GRPC {
discount.RegisterDiscountServiceServer(receiver.grpc, server)
return receiver
} return receiver
}
func (receiver *GRPC) listen(address string) error {
listener, err := net.Listen("tcp", address) func (receiver *GRPC) listen(address string) error {
if err != nil { listener, err := net.Listen("tcp", address)
return err if err != nil {
} return err
}
if err := receiver.grpc.Serve(listener); err != nil {
return err return receiver.grpc.Serve(listener)
} }
return nil func (receiver *GRPC) Stop(_ context.Context) error {
} receiver.grpc.GracefulStop()
receiver.logger.Infoln("Shutting down GRPC server...")
func (receiver *GRPC) Stop() {
receiver.grpc.GracefulStop() return nil
receiver.logger.Infoln("Shutting down GRPC server...") }
}

@ -1,50 +1,64 @@
package server package server
import ( import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"time"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/sirupsen/logrus" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc" "github.com/sirupsen/logrus"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
) )
type HTTP struct { type HTTP struct {
logger *logrus.Logger logger *logrus.Logger
mux *runtime.ServeMux server *http.Server
} mux *runtime.ServeMux
}
func NewHTTP(logger *logrus.Logger) *HTTP {
return &HTTP{ func NewHTTP(logger *logrus.Logger) *HTTP {
logger: logger, return &HTTP{
mux: runtime.NewServeMux(), logger: logger,
} mux: runtime.NewServeMux(),
} server: &http.Server{
MaxHeaderBytes: 1 << 20,
func (receiver *HTTP) Register(ctx context.Context, config *core.GRPCConfiguration) *HTTP { ReadTimeout: 20 * time.Second,
options := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} WriteTimeout: 20 * time.Second,
endpoint := fmt.Sprintf("%s:%s", config.Host, config.Port) IdleTimeout: 20 * time.Second,
},
if err := discount.RegisterDiscountServiceHandlerFromEndpoint(ctx, receiver.mux, endpoint, options); err != nil { }
receiver.logger.Errorf("HTTP register error: %v", err) }
panic(err)
} func (receiver *HTTP) Register(ctx context.Context, config *core.GRPCConfiguration) *HTTP {
options := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
return receiver endpoint := fmt.Sprintf("%s:%s", config.Host, config.Port)
}
if err := discount.RegisterDiscountServiceHandlerFromEndpoint(ctx, receiver.mux, endpoint, options); err != nil {
func (receiver *HTTP) Run(config *core.HTTPConfiguration) { receiver.logger.Errorf("HTTP register error: %v", err)
endpoint := fmt.Sprintf(":%s", config.Port) panic(err)
}
receiver.logger.Infof("HTTP server started on: <%s>", endpoint)
return receiver
if err := http.ListenAndServe(endpoint, receiver.mux); err != nil && err != http.ErrServerClosed { }
receiver.logger.Errorf("HTTP listen error: %v", err)
panic(err) func (receiver *HTTP) Listen(address string) error {
} receiver.server.Addr = address
} receiver.server.Handler = receiver.mux
return receiver.server.ListenAndServe()
}
func (receiver *HTTP) Run(config *core.HTTPConfiguration) {
endpoint := fmt.Sprintf(":%s", config.Port)
receiver.logger.Infof("HTTP server started on: <%s>", endpoint)
if err := receiver.Listen(endpoint); err != nil && err != http.ErrServerClosed {
receiver.logger.Errorf("HTTP listen error: %v", err)
panic(err)
}
}

@ -1,136 +1,135 @@
package service package service
import ( import (
"context" "context"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils" discountUtils "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/calculate" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer" )
)
type DiscountRepository interface {
type DiscountRepository interface { Insert(ctx context.Context, discount *models.Discount) (string, error)
Insert(ctx context.Context, discount *models.Discount) (string, error) DeleteByID(ctx context.Context, discountID string) (*models.Discount, error)
DeleteByID(ctx context.Context, discountID string) (*models.Discount, error) FindByID(ctx context.Context, discountID string) (*models.Discount, error)
FindByID(ctx context.Context, discountID string) (*models.Discount, error) FindAll(ctx context.Context) ([]models.Discount, error)
FindAll(ctx context.Context) ([]models.Discount, error) FindByUserID(ctx context.Context, userID string) ([]models.Discount, error)
FindByUserID(ctx context.Context, userID string) ([]models.Discount, error) Determine(ctx context.Context, conditions *core.DiscountConditions) ([]models.Discount, error)
Determine(ctx context.Context, conditions *core.DiscountConditions) ([]models.Discount, error) UpdateOne(ctx context.Context, update *core.UpdateDiscountSettings) error
UpdateOne(ctx context.Context, update *core.UpdateDiscountSettings) error }
}
type DiscountServiceDeps struct {
type DiscountServiceDeps struct { Logger *logrus.Logger
Logger *logrus.Logger DiscountRepository DiscountRepository
DiscountRepository DiscountRepository }
}
type DiscountService struct {
type DiscountService struct { logger *logrus.Logger
logger *logrus.Logger discountRepository DiscountRepository
discountRepository DiscountRepository }
}
func NewDiscountService(deps *DiscountServiceDeps) *DiscountService {
func NewDiscountService(deps *DiscountServiceDeps) *DiscountService { return &DiscountService{
return &DiscountService{ logger: deps.Logger,
logger: deps.Logger, discountRepository: deps.DiscountRepository,
discountRepository: deps.DiscountRepository, }
} }
}
func (receiver *DiscountService) ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error) {
func (receiver *DiscountService) ApplyDiscounts(ctx context.Context, request *discount.ApplyDiscountRequest) (*discount.ApplyDiscountResponse, error) { conditions, err := utils.ConstituteConditions(request)
conditions, err := utils.ConstituteConditions(request) if err != nil {
if err != nil { receiver.logger.Errorf("failed to constitute conditions on <ApplyDiscounts> of <DiscountService>: %v", err)
receiver.logger.Errorf("failed to constitute conditions on <ApplyDiscounts> of <DiscountService>: %v", err) return nil, ErrInvalidInputValue
return nil, ErrInvalidInputValue }
}
discounts, err := receiver.discountRepository.Determine(ctx, conditions)
discounts, err := receiver.discountRepository.Determine(ctx, conditions) if err != nil {
if err != nil { receiver.logger.Errorf("failed to determine conditions on <ApplyDiscounts> of <DiscountService>: %v", err)
receiver.logger.Errorf("failed to determine conditions on <ApplyDiscounts> of <DiscountService>: %v", err) return nil, err
return nil, err }
}
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts(discounts)
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts(discounts)
if couponDiscount != nil {
if couponDiscount != nil { deprecated := true
deprecated := true
if err := receiver.discountRepository.UpdateOne(ctx, &core.UpdateDiscountSettings{
if err := receiver.discountRepository.UpdateOne(ctx, &core.UpdateDiscountSettings{ ID: couponDiscount.ID,
ID: couponDiscount.ID, Deprecated: &deprecated,
Deprecated: &deprecated, }); err != nil {
}); err != nil { receiver.logger.Errorf("failed to deprecate coupon discount on <ApplyDiscounts> of <DiscountService>: %v", err)
receiver.logger.Errorf("failed to deprecate coupon discount on <ApplyDiscounts> of <DiscountService>: %v", err) return nil, err
return nil, err }
} }
}
products := transfer.ProductsProtoToCore(request.Products)
products := transfer.ProductsProtoToCore(request.Products)
return &discount.ApplyDiscountResponse{
return &discount.ApplyDiscountResponse{ Price: discountUtils.Calculate(products, filteredDiscounts),
Price: calculate.Discount(products, filteredDiscounts), AppliedDiscounts: transfer.DiscountsModelToProto(filteredDiscounts).Discounts,
AppliedDiscounts: transfer.DiscountsModelToProto(filteredDiscounts).Discounts, }, nil
}, nil }
}
func (receiver *DiscountService) GetDiscountByID(ctx context.Context, id string) (*models.Discount, error) {
func (receiver *DiscountService) GetDiscountByID(ctx context.Context, id string) (*models.Discount, error) { discount, err := receiver.discountRepository.FindByID(ctx, id)
discount, err := receiver.discountRepository.FindByID(ctx, id) if err != nil {
if err != nil { receiver.logger.Errorf("failed to get discount on <GetDiscountByID> of <DiscountService>: %v", err)
receiver.logger.Errorf("failed to get discount on <GetDiscountByID> of <DiscountService>: %v", err) return nil, err
return nil, err }
}
return discount, nil
return discount, nil }
}
func (receiver *DiscountService) GetDiscountsByUserID(ctx context.Context, id string) ([]models.Discount, error) {
func (receiver *DiscountService) GetDiscountsByUserID(ctx context.Context, id string) ([]models.Discount, error) { discounts, err := receiver.discountRepository.FindByUserID(ctx, id)
discounts, err := receiver.discountRepository.FindByUserID(ctx, id) if err != nil {
if err != nil { receiver.logger.Errorf("failed to get discount on <GetDiscountByUserID> of <DiscountService>: %v", err)
receiver.logger.Errorf("failed to get discount on <GetDiscountByUserID> of <DiscountService>: %v", err) return nil, err
return nil, err }
}
return discounts, nil
return discounts, nil }
}
func (receiver *DiscountService) GetAllDiscounts(ctx context.Context) ([]models.Discount, error) {
func (receiver *DiscountService) GetAllDiscounts(ctx context.Context) ([]models.Discount, error) { discounts, err := receiver.discountRepository.FindAll(ctx)
discounts, err := receiver.discountRepository.FindAll(ctx) if err != nil {
if err != nil { receiver.logger.Errorf("failed to get all discounts on <GetAllDiscounts> of <DiscountService>: %v", err)
receiver.logger.Errorf("failed to get all discounts on <GetAllDiscounts> of <DiscountService>: %v", err) return nil, err
return nil, err }
}
return discounts, nil
return discounts, nil }
}
func (receiver *DiscountService) DeleteDiscount(ctx context.Context, id string) (*models.Discount, error) {
func (receiver *DiscountService) DeleteDiscount(ctx context.Context, id string) (*models.Discount, error) { deletedDiscount, err := receiver.discountRepository.DeleteByID(ctx, id)
deletedDiscount, err := receiver.discountRepository.DeleteByID(ctx, id) if err != nil {
if err != nil { receiver.logger.Errorf("failed delete discount on <DeleteDiscount> of <DiscountService>: %v", err)
receiver.logger.Errorf("failed delete discount on <DeleteDiscount> of <DiscountService>: %v", err) return nil, err
return nil, err }
}
return deletedDiscount, nil
return deletedDiscount, nil }
}
func (receiver *DiscountService) CreateDiscount(ctx context.Context, discount *models.Discount) (*models.Discount, error) {
func (receiver *DiscountService) CreateDiscount(ctx context.Context, discount *models.Discount) (*models.Discount, error) { createdDiscountID, err := receiver.discountRepository.Insert(ctx, discount)
createdDiscountID, err := receiver.discountRepository.Insert(ctx, discount) if err != nil {
if err != nil { return nil, err
return nil, err }
} if createdDiscountID == "" {
if createdDiscountID == "" { receiver.logger.Errorf("empty discount ID on <CreateDiscount> of <DiscountService>")
receiver.logger.Errorf("empty discount ID on <CreateDiscount> of <DiscountService>") return nil, ErrInvalidReturnValue
return nil, ErrInvalidReturnValue }
}
return &models.Discount{
return &models.Discount{ Target: discount.Target,
Target: discount.Target, Condition: discount.Condition,
Condition: discount.Condition, ID: createdDiscountID,
ID: createdDiscountID, Name: discount.Name,
Name: discount.Name, Description: discount.Description,
Description: discount.Description, Layer: discount.Layer,
Layer: discount.Layer, }, nil
}, nil }
}

@ -1,8 +1,8 @@
package service package service
import "errors" import "errors"
var ( var (
ErrInvalidInputValue = errors.New("invalid input value in args of method") ErrInvalidInputValue = errors.New("invalid input value in args of method")
ErrInvalidReturnValue = errors.New("invalid return value of function inside method") ErrInvalidReturnValue = errors.New("invalid return value of function inside method")
) )

@ -1,52 +0,0 @@
package calculate
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
/*
TODO:
1) Рефактор всех приватных утилит
2) Покрытие тестами всех утилит
3) Уточнить систему overhelm при высчитывании
*/
func Discount(products []core.Product, discounts []models.Discount) float64 {
if len(products) < 1 {
return 0
}
price := calculateProducts(products)
if len(discounts) < 1 {
return price
}
discountTargets := constituteDiscountTargets(divideDiscounts(discounts))
productsMap := convert.ArrayToMap(products, func(product core.Product) string {
return product.ID
})
if eachTarget, ok := discountTargets[models.TargetEach]; ok {
productsMap = calculateEachProduct(productsMap, eachTarget)
price = calculateProductsMap(productsMap)
}
if groupTarget, ok := discountTargets[models.TargetGroup]; ok {
productGroups := convert.MapToMapArray(productsMap, func(product core.Product) string {
return product.Group
})
pricesGroup := calculateGroupProducts(productGroups, groupTarget)
price = calculatePricesGroup(pricesGroup)
}
if sumTarget, ok := discountTargets[models.TargetSum]; ok {
price = price * sumTarget.Factor
}
return price
}

@ -1,94 +0,0 @@
package calculate
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
func calculateProducts(products []core.Product) float64 {
productsPrice := float64(0)
for _, product := range products {
productsPrice = productsPrice + product.Price
}
return productsPrice
}
func calculateProductsMap(products map[string]core.Product) float64 {
productsPrice := float64(0)
for _, product := range products {
productsPrice = productsPrice + product.Price
}
return productsPrice
}
func calculatePricesGroup(pricesGroup map[string]float64) float64 {
finalPrice := float64(0)
for _, price := range pricesGroup {
finalPrice = finalPrice + price
}
return finalPrice
}
func calculateEachProduct(products map[string]core.Product, target models.DiscountCalculationTarget) map[string]core.Product {
if target.Products == nil && target.Factor == 0 {
return products
}
if target.Products == nil {
for key, product := range products {
products[key] = core.Product{
ID: key,
Price: product.Price * target.Factor,
}
}
return products
}
for _, productTarget := range target.Products {
product, ok := products[productTarget.ID]
if !ok {
continue
}
if productTarget.Factor != 0 {
products[product.ID] = core.Product{
ID: product.ID,
Price: product.Price * productTarget.Factor,
}
continue
}
products[product.ID] = core.Product{
ID: product.ID,
Price: product.Price * target.Factor,
}
}
return products
}
func calculateGroupProducts(productsGroup map[string][]core.Product, target models.DiscountCalculationTarget) map[string]float64 {
pricesGroup := make(map[string]float64)
if target.TargetGroup == "" && target.Factor == 0 {
for group, products := range productsGroup {
pricesGroup[group] = calculateProducts(products)
}
return pricesGroup
}
for group, products := range productsGroup {
pricesGroup[group] = calculateProducts(products) * target.Factor
}
return pricesGroup
}

@ -1,92 +0,0 @@
package calculate
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
func constituteDiscountTargets(dividedDiscounts map[models.TargetScope][]models.Discount) map[models.TargetScope]models.DiscountCalculationTarget {
discountTargets := make(map[models.TargetScope]models.DiscountCalculationTarget, 3)
for group, discounts := range dividedDiscounts {
switch group {
case models.TargetEach:
discountTargets[group] = models.DiscountCalculationTarget{
Products: sumEachFactor(discounts),
}
case models.TargetGroup, models.TargetSum:
discountTargets[group] = models.DiscountCalculationTarget{
Factor: sumCommonFactor(discounts),
}
}
}
return discountTargets
}
func divideDiscounts(discounts []models.Discount) map[models.TargetScope][]models.Discount {
dividedDiscounts := make(map[models.TargetScope][]models.Discount, 3)
for _, discount := range discounts {
if len(discount.Target.Products) > 0 {
dividedDiscounts[models.TargetEach] = append(dividedDiscounts[models.TargetEach], discount)
continue
}
if discount.Target.TargetScope == "" && len(discount.Target.Products) < 1 {
dividedDiscounts[models.TargetSum] = append(dividedDiscounts[models.TargetSum], discount)
continue
}
if discount.Target.TargetScope == "" {
continue
}
dividedDiscounts[discount.Target.TargetScope] = append(dividedDiscounts[discount.Target.TargetScope], discount)
}
return dividedDiscounts
}
func sumCommonFactor(discounts []models.Discount) float64 {
sum := float64(1)
for _, discount := range discounts {
if discount.Target.Overhelm {
sum = discount.Target.Factor
break
}
sum = sum * discount.Target.Factor
}
return sum
}
func sumEachFactor(discounts []models.Discount) []models.ProductTarget {
productTargetsMap := make(map[string]models.ProductTarget)
for _, discount := range discounts {
for _, productTarget := range discount.Target.Products {
if productTargetsMap[productTarget.ID].Overhelm {
continue
}
if productTargetsMap[productTarget.ID].Factor > 0 {
productTargetsMap[productTarget.ID] = models.ProductTarget{
ID: productTarget.ID,
Factor: productTargetsMap[productTarget.ID].Factor * productTarget.Factor,
Overhelm: productTargetsMap[productTarget.ID].Overhelm,
}
continue
}
productTargetsMap[productTarget.ID] = models.ProductTarget{
ID: productTarget.ID,
Factor: 1 * productTarget.Factor,
Overhelm: productTargetsMap[productTarget.ID].Overhelm,
}
}
}
return convert.MapToArray(productTargetsMap)
}

@ -1,58 +1,58 @@
package utils package utils
import ( import (
"errors" "errors"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
) )
func ConstituteConditions(request *discount.ApplyDiscountRequest) (*core.DiscountConditions, error) { func ConstituteConditions(request *discount.ApplyDiscountRequest) (*core.DiscountConditions, error) {
if request == nil { if request == nil {
return nil, nil return nil, nil
} }
if len(request.GetProducts()) < 1 { if len(request.GetProducts()) < 1 {
return nil, nil return nil, nil
} }
optionalConditions := make([]core.OptionalDiscounCondition, len(request.GetProducts())*2) optionalConditions := make([]core.OptionalDiscounCondition, len(request.GetProducts())*2)
optionalConditionsIndex := 0 optionalConditionsIndex := 0
for _, product := range request.Products { for _, product := range request.Products {
if product == nil { if product == nil {
return nil, errors.New("some of products are nil") return nil, errors.New("some of products are nil")
} }
optionalConditions[optionalConditionsIndex] = core.OptionalDiscounCondition{ optionalConditions[optionalConditionsIndex] = core.OptionalDiscounCondition{
Product: &product.ID, Product: &product.ID,
PriceFrom: &product.Price, PriceFrom: &product.Price,
Group: product.Group, Group: product.Group,
Term: product.Term, Term: product.Term,
Usage: product.Usage, Usage: product.Usage,
} }
optionalConditions[optionalConditionsIndex+1] = core.OptionalDiscounCondition{ optionalConditions[optionalConditionsIndex+1] = core.OptionalDiscounCondition{
Product: &product.ID, Product: &product.ID,
Group: product.Group, Group: product.Group,
Term: product.Term, Term: product.Term,
Usage: product.Usage, Usage: product.Usage,
} }
optionalConditionsIndex = optionalConditionsIndex + 2 optionalConditionsIndex += 2
} }
return &core.DiscountConditions{ return &core.DiscountConditions{
Common: core.CommonDiscountCondition{ Common: core.CommonDiscountCondition{
User: request.UserInformation.ID, User: request.UserInformation.ID,
UserType: request.UserInformation.Type, UserType: request.UserInformation.Type,
PurchasesAmount: request.UserInformation.PurchasesAmount, PurchasesAmount: request.UserInformation.PurchasesAmount,
CartPurchasesAmount: request.UserInformation.CartPurchasesAmount, CartPurchasesAmount: request.UserInformation.CartPurchasesAmount,
Coupon: request.Coupon, Coupon: request.Coupon,
Period: models.PeriodCondition{ Period: models.PeriodCondition{
From: request.Date.AsTime(), From: request.Date.AsTime(),
To: request.Date.AsTime(), To: request.Date.AsTime(),
}, },
}, },
Optionals: optionalConditions, Optionals: optionalConditions,
}, nil }, nil
} }

@ -1,151 +1,150 @@
package utils_test package utils_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils" )
)
func TestConstituteConditions(t *testing.T) {
func TestConstituteConditions(t *testing.T) { userID := "1"
userID := "1" userType := "nkvo"
userType := "nkvo" purchasesAmount := uint64(100000)
purchasesAmount := float64(100000) cartPurchasesAmount := uint64(5000)
cartPurchasesAmount := float64(5000) productsGroup := "group"
productsGroup := "group" productID1 := "1"
productID1 := "1" productID2 := "2"
productID2 := "2" productID3 := "3"
productID3 := "3" productID4 := "4"
productID4 := "4" productPrice1 := uint64(1000)
productPrice1 := float64(1000) productPrice2 := uint64(6000)
productPrice2 := float64(6000) productPrice3 := uint64(10000)
productPrice3 := float64(10000) productPrice4 := uint64(4000)
productPrice4 := float64(4000) productTerm1 := uint64(14)
productTerm1 := uint64(14) productTerm2 := uint64(16)
productTerm2 := uint64(16) productUsage1 := uint64(24)
productUsage1 := uint64(24) productUsage2 := uint64(14)
productUsage2 := uint64(14) coupon := "coupon"
coupon := "coupon" period := models.PeriodCondition{
period := models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), }
}
t.Run("Формирование условий поиска с пустыми значениями", func(t *testing.T) {
t.Run("Формирование условий поиска с пустыми значениями", func(t *testing.T) { conditions, _ := utils.ConstituteConditions(&discount.ApplyDiscountRequest{})
conditions, _ := utils.ConstituteConditions(&discount.ApplyDiscountRequest{})
assert.Nil(t, conditions)
assert.Nil(t, conditions) })
})
t.Run("Формирование условий поиска с nil", func(t *testing.T) {
t.Run("Формирование условий поиска с nil", func(t *testing.T) { conditions, _ := utils.ConstituteConditions(nil)
conditions, _ := utils.ConstituteConditions(nil)
assert.Nil(t, conditions)
assert.Nil(t, conditions) })
})
t.Run("Формирование условий поиска с заполненной информацией", func(t *testing.T) {
t.Run("Формирование условий поиска с заполненной информацией", func(t *testing.T) { conditions, err := utils.ConstituteConditions(&discount.ApplyDiscountRequest{
conditions, err := utils.ConstituteConditions(&discount.ApplyDiscountRequest{ UserInformation: &discount.UserInformation{
UserInformation: &discount.UserInformation{ ID: userID,
ID: userID, Type: userType,
Type: userType, PurchasesAmount: purchasesAmount,
PurchasesAmount: purchasesAmount, CartPurchasesAmount: cartPurchasesAmount,
CartPurchasesAmount: cartPurchasesAmount, },
}, Products: []*discount.ProductInformation{
Products: []*discount.ProductInformation{ {ID: productID1, Price: productPrice1, Term: &productTerm1, Group: &productsGroup},
{ID: productID1, Price: productPrice1, Term: &productTerm1, Group: &productsGroup}, {ID: productID2, Price: productPrice2, Term: &productTerm2, Group: &productsGroup},
{ID: productID2, Price: productPrice2, Term: &productTerm2, Group: &productsGroup}, {ID: productID3, Price: productPrice3, Usage: &productUsage1},
{ID: productID3, Price: productPrice3, Usage: &productUsage1}, {ID: productID4, Price: productPrice4, Term: &productTerm2, Usage: &productUsage2},
{ID: productID4, Price: productPrice4, Term: &productTerm2, Usage: &productUsage2}, },
}, Coupon: &coupon,
Coupon: &coupon, Date: &timestamppb.Timestamp{
Date: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, })
})
assert.Nil(t, err)
assert.Nil(t, err) assert.Equal(t, &core.DiscountConditions{
assert.Equal(t, &core.DiscountConditions{ Common: core.CommonDiscountCondition{
Common: core.CommonDiscountCondition{ User: userID,
User: userID, UserType: userType,
UserType: userType, PurchasesAmount: purchasesAmount,
PurchasesAmount: purchasesAmount, CartPurchasesAmount: cartPurchasesAmount,
CartPurchasesAmount: cartPurchasesAmount, Coupon: &coupon,
Coupon: &coupon, Period: period,
Period: period, },
}, Optionals: []core.OptionalDiscounCondition{
Optionals: []core.OptionalDiscounCondition{ {
{ Product: &productID1,
Product: &productID1, PriceFrom: &productPrice1,
PriceFrom: &productPrice1, Group: &productsGroup,
Group: &productsGroup, Term: &productTerm1,
Term: &productTerm1, },
}, {
{ Product: &productID1,
Product: &productID1, Group: &productsGroup,
Group: &productsGroup, Term: &productTerm1,
Term: &productTerm1, },
}, {
{ Product: &productID2,
Product: &productID2, PriceFrom: &productPrice2,
PriceFrom: &productPrice2, Group: &productsGroup,
Group: &productsGroup, Term: &productTerm2,
Term: &productTerm2, },
}, {
{ Product: &productID2,
Product: &productID2, Group: &productsGroup,
Group: &productsGroup, Term: &productTerm2,
Term: &productTerm2, },
}, {
{ Product: &productID3,
Product: &productID3, PriceFrom: &productPrice3,
PriceFrom: &productPrice3, Usage: &productUsage1,
Usage: &productUsage1, },
}, {
{ Product: &productID3,
Product: &productID3, Usage: &productUsage1,
Usage: &productUsage1, },
}, {
{ Product: &productID4,
Product: &productID4, PriceFrom: &productPrice4,
PriceFrom: &productPrice4, Term: &productTerm2,
Term: &productTerm2, Usage: &productUsage2,
Usage: &productUsage2, },
}, {
{ Product: &productID4,
Product: &productID4, Term: &productTerm2,
Term: &productTerm2, Usage: &productUsage2,
Usage: &productUsage2, },
}, },
}, }, conditions)
}, conditions) })
})
t.Run("Формирование условий поиска с наличием nil'ого продукта", func(t *testing.T) {
t.Run("Формирование условий поиска с наличием nil'ого продукта", func(t *testing.T) { conditions, err := utils.ConstituteConditions(&discount.ApplyDiscountRequest{
conditions, err := utils.ConstituteConditions(&discount.ApplyDiscountRequest{ UserInformation: &discount.UserInformation{
UserInformation: &discount.UserInformation{ ID: userID,
ID: userID, Type: userType,
Type: userType, PurchasesAmount: purchasesAmount,
PurchasesAmount: purchasesAmount, CartPurchasesAmount: cartPurchasesAmount,
CartPurchasesAmount: cartPurchasesAmount, },
}, Products: []*discount.ProductInformation{
Products: []*discount.ProductInformation{ {ID: productID1, Price: productPrice1, Term: &productTerm1, Group: &productsGroup},
{ID: productID1, Price: productPrice1, Term: &productTerm1, Group: &productsGroup}, nil,
nil, },
}, Date: &timestamppb.Timestamp{
Date: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, })
})
assert.Nil(t, conditions)
assert.Nil(t, conditions) assert.Error(t, err)
assert.Error(t, err) })
}) }
}

@ -0,0 +1,144 @@
package discount
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
/*
TODO:
1) Рефактор всех приватных утилит
2) Покрытие тестами всех утилит
*/
func Calculate(products []core.Product, discounts []models.Discount) uint64 {
if len(products) < 1 {
return 0
}
price := calculateProducts(products)
if len(discounts) < 1 {
return convert.FloatToUint64(price)
}
discountTargetsMap := SortOverhelmingDiscounts(discounts)
productsMap := convert.ArrayToMap(products, func(product core.Product) string {
return product.ID
})
if targetsEach, ok := discountTargetsMap[models.TargetEach]; ok {
for _, target := range targetsEach {
productsMap = calculateEachProductWithTarget(productsMap, target)
}
price = calculateProductsMap(productsMap)
}
if targetsGroup, ok := discountTargetsMap[models.TargetGroup]; ok {
productGroups := convert.MapToMapArray(productsMap, func(product core.Product) string {
return product.Group
})
pricesGroup := calculateProductsGroup(productGroups)
for _, target := range targetsGroup {
pricesGroup[target.TargetGroup] *= target.Factor
}
price = calculatePricesGroupSum(pricesGroup)
}
if targetsSum, ok := discountTargetsMap[models.TargetSum]; ok {
for _, target := range targetsSum {
price *= target.Factor
}
}
return convert.FloatToUint64(price)
}
func calculateProducts(products []core.Product) float64 {
productsPrice := float64(0)
for _, product := range products {
productsPrice += product.Price
}
return productsPrice
}
func calculateProductsMap(products map[string]core.Product) float64 {
productsPrice := float64(0)
for _, product := range products {
productsPrice += product.Price
}
return productsPrice
}
func calculatePricesGroupSum(pricesGroup map[string]float64) float64 {
finalPrice := float64(0)
for _, price := range pricesGroup {
finalPrice += price
}
return finalPrice
}
func calculateEachProductWithTarget(products map[string]core.Product, target models.DiscountCalculationTarget) map[string]core.Product {
if target.Products == nil && target.Factor == 0 {
return products
}
if target.Products == nil {
for key, product := range products {
products[key] = core.Product{
ID: key,
Price: product.Price * target.Factor,
Group: product.Group,
}
}
return products
}
for _, productTarget := range target.Products {
product, ok := products[productTarget.ID]
if !ok {
continue
}
if productTarget.Factor != 0 {
products[product.ID] = core.Product{
ID: product.ID,
Price: product.Price * productTarget.Factor,
Group: product.Group,
}
continue
}
products[product.ID] = core.Product{
ID: product.ID,
Price: product.Price * target.Factor,
Group: product.Group,
}
}
return products
}
func calculateProductsGroup(productsGroup map[string][]core.Product) map[string]float64 {
pricesGroup := make(map[string]float64)
for group, products := range productsGroup {
pricesGroup[group] = calculateProducts(products)
}
return pricesGroup
}

@ -1,344 +1,346 @@
package calculate_test package discount_test
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/calculate" )
)
func TestCalculate(t *testing.T) {
func TestCalculate(t *testing.T) { t.Run("Покупка продуктов в количестве не больше 1 при отсутствии скидок", func(t *testing.T) {
t.Run("Покупка продуктов в количестве не больше 1 при отсутствии скидок", func(t *testing.T) { assert.Equal(t, uint64(3), discount.Calculate(
assert.Equal(t, 3.0, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p1", Price: 1},
{ID: "p1", Price: 1.0}, {ID: "p2", Price: 2},
{ID: "p2", Price: 2.0}, },
}, []models.Discount{},
[]models.Discount{}, ))
)) })
})
t.Run("Сумма корзины достигла 5к", func(t *testing.T) {
t.Run("Сумма корзины достигла 5к", func(t *testing.T) { assert.Equal(t, uint64(4925), discount.Calculate(
assert.Equal(t, 4925.0, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p1", Price: 2000},
{ID: "p1", Price: 2000.0}, {ID: "p2", Price: 3000},
{ID: "p2", Price: 3000.0}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Factor: 0.985,
Factor: 0.985, },
}, },
}, },
}, ))
)) assert.Equal(t, uint64(4925), discount.Calculate(
assert.Equal(t, 4925.0, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p1", Price: 2000},
{ID: "p1", Price: 2000.0}, {ID: "p2", Price: 3000},
{ID: "p2", Price: 3000.0}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Factor: 0.985,
Factor: 0.985, },
}, },
}, },
}, ))
)) })
})
t.Run("Наложение несколько скидок разного формата", func(t *testing.T) {
t.Run("Наложение несколько скидок разного формата", func(t *testing.T) { assert.Equal(t, uint64(5025), discount.Calculate(
assert.InEpsilon(t, 5025.32, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p1", Price: 2000},
{ID: "p1", Price: 2000.0}, {ID: "p2", Price: 3000},
{ID: "p2", Price: 3000.0}, {ID: "p3", Price: 105},
{ID: "p3", Price: 105.0}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Products: []models.ProductTarget{{ID: "p3", Factor: 0.97}},
Products: []models.ProductTarget{{ID: "p3", Factor: 0.97}}, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Factor: 0.985,
Factor: 0.985, },
}, },
}, },
}, ))
), 0.001) })
})
t.Run("Наложение несколько скидок разного формата с несколькими таргетами", func(t *testing.T) {
t.Run("Наложение несколько скидок разного формата с несколькими таргетами", func(t *testing.T) { assert.Equal(t, uint64(5223), discount.Calculate(
assert.InEpsilon(t, 5223.89, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p1", Price: 2000},
{ID: "p1", Price: 2000.0}, {ID: "p2", Price: 3000},
{ID: "p2", Price: 3000.0}, {ID: "p3", Price: 105},
{ID: "p3", Price: 105.0}, {ID: "p4", Price: 210},
{ID: "p4", Price: 210.0}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Products: []models.ProductTarget{
Products: []models.ProductTarget{ {ID: "p3", Factor: 0.97},
{ID: "p3", Factor: 0.97}, {ID: "p4", Factor: 0.96},
{ID: "p4", Factor: 0.96}, },
}, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Factor: 0.985,
Factor: 0.985, },
}, },
}, },
}, ))
), 0.001) })
})
t.Run("Наложение несколько скидок с наличием скидки на лояльность", func(t *testing.T) {
t.Run("Наложение несколько скидок с наличием скидки на лояльность", func(t *testing.T) { assert.Equal(t, uint64(5171), discount.Calculate(
assert.InEpsilon(t, 5171.66, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p1", Price: 2000},
{ID: "p1", Price: 2000.0}, {ID: "p2", Price: 3000},
{ID: "p2", Price: 3000.0}, {ID: "p3", Price: 105},
{ID: "p3", Price: 105.0}, {ID: "p4", Price: 210},
{ID: "p4", Price: 210.0}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Products: []models.ProductTarget{
Products: []models.ProductTarget{ {ID: "p3", Factor: 0.97},
{ID: "p3", Factor: 0.97}, {ID: "p4", Factor: 0.96},
{ID: "p4", Factor: 0.96}, },
}, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Factor: 0.985,
Factor: 0.985, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Factor: 0.99,
Factor: 0.99, },
}, },
}, },
}, ), 0.001)
), 0.001) })
})
t.Run("Наложение несколько скидок на привелегии", func(t *testing.T) {
t.Run("Наложение несколько скидок на привелегии", func(t *testing.T) { assert.Equal(t, uint64(422), discount.Calculate(
assert.InEpsilon(t, 422.28, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p5", Price: 30},
{ID: "p5", Price: 30}, {ID: "p6", Price: 70},
{ID: "p6", Price: 70}, {ID: "p7", Price: 400},
{ID: "p7", Price: 400}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Products: []models.ProductTarget{
Products: []models.ProductTarget{ {ID: "p5", Factor: 0.97},
{ID: "p5", Factor: 0.97}, {ID: "p6", Factor: 0.935},
{ID: "p6", Factor: 0.935}, },
}, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Products: []models.ProductTarget{
Products: []models.ProductTarget{ {ID: "p7", Factor: 0.83},
{ID: "p7", Factor: 0.83}, },
}, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Factor: 0.99,
Factor: 0.99, },
}, },
}, },
}, ))
), 0.001) })
})
t.Run("Наложение несколько скидок на привелегии без лояльности", func(t *testing.T) {
t.Run("Наложение несколько скидок на привелегии без лояльности", func(t *testing.T) { assert.Equal(t, uint64(426), discount.Calculate(
assert.InEpsilon(t, 426.55, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p5", Price: 30},
{ID: "p5", Price: 30}, {ID: "p6", Price: 70},
{ID: "p6", Price: 70}, {ID: "p7", Price: 400},
{ID: "p7", Price: 400}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Products: []models.ProductTarget{
Products: []models.ProductTarget{ {ID: "p5", Factor: 0.97},
{ID: "p5", Factor: 0.97}, {ID: "p6", Factor: 0.935},
{ID: "p6", Factor: 0.935}, {ID: "p7", Factor: 0.83},
{ID: "p7", Factor: 0.83}, },
}, },
}, },
}, },
}, ))
), 0.001) })
})
t.Run("Вычисление скидки за привелегии и сервис", func(t *testing.T) {
t.Run("Вычисление скидки за привелегии и сервис", func(t *testing.T) { assert.Equal(t, uint64(743), discount.Calculate(
assert.InEpsilon(t, 743.45, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p5", Price: 30, Group: "dwarfener"}, {ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"}, {ID: "p7", Price: 800, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Products: []models.ProductTarget{
Products: []models.ProductTarget{ {ID: "p5", Factor: 0.97},
{ID: "p5", Factor: 0.97}, {ID: "p6", Factor: 0.935},
{ID: "p6", Factor: 0.935}, {ID: "p7", Factor: 0.83},
{ID: "p7", Factor: 0.83}, },
}, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Factor: 0.99,
Factor: 0.99, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetGroup,
TargetScope: models.TargetGroup, TargetGroup: "dwarfener",
TargetGroup: "dwarfener", Factor: 0.99,
Factor: 0.99, },
}, },
}, },
}, ))
), 0.001) })
})
t.Run("Наложение скидок на несколько сервисов", func(t *testing.T) {
t.Run("Наложение скидок на несколько сервисов", func(t *testing.T) { assert.Equal(t, uint64(2398), discount.Calculate(
assert.InEpsilon(t, 2398.5, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p1", Price: 1500, Group: "templategen"}, {ID: "p2", Price: 300, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"}, {ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p5", Price: 30, Group: "dwarfener"}, {ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"}, {ID: "p7", Price: 800, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Products: []models.ProductTarget{
Products: []models.ProductTarget{ {ID: "p1", Factor: 0.93},
{ID: "p1", Factor: 0.93}, {ID: "p2", Factor: 0.945},
{ID: "p2", Factor: 0.945}, {ID: "p5", Factor: 0.97},
{ID: "p5", Factor: 0.97}, {ID: "p6", Factor: 0.935},
{ID: "p6", Factor: 0.935}, {ID: "p7", Factor: 0.83},
{ID: "p7", Factor: 0.83}, },
}, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Factor: 0.99,
Factor: 0.99, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetGroup,
TargetScope: models.TargetGroup, TargetGroup: "templategen",
TargetGroup: "templategen", Factor: 0.996,
Factor: 0.996, },
}, },
}, {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetGroup,
TargetScope: models.TargetGroup, TargetGroup: "dwarfener",
TargetGroup: "dwarfener", Factor: 0.99,
Factor: 0.99, },
}, },
}, },
}, ))
), 0.01) })
})
t.Run("Наложение скидок с промокодом", func(t *testing.T) {
t.Run("Наложение скидок с промокодом", func(t *testing.T) { assert.Equal(t, uint64(2368), discount.Calculate(
assert.InEpsilon(t, 2368.68, calculate.Discount( []core.Product{
[]core.Product{ {ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p1", Price: 1500, Group: "templategen"}, {ID: "p2", Price: 300, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"}, {ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p5", Price: 30, Group: "dwarfener"}, {ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"}, {ID: "p7", Price: 800, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"}, },
}, []models.Discount{
[]models.Discount{ {
{ Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ Products: []models.ProductTarget{
Products: []models.ProductTarget{ {ID: "p1", Factor: 0.93},
{ID: "p1", Factor: 0.93}, {ID: "p2", Factor: 0.945},
{ID: "p2", Factor: 0.945}, {ID: "p5", Factor: 0.97},
{ID: "p5", Factor: 0.97}, {ID: "p6", Factor: 0.97},
{ID: "p6", Factor: 0.97}, {ID: "p7", Factor: 0.83},
{ID: "p7", Factor: 0.83}, },
}, },
}, },
}, {
{ Layer: 2,
Target: models.DiscountCalculationTarget{ Target: models.DiscountCalculationTarget{
Products: []models.ProductTarget{ Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true}, {ID: "p6", Factor: 0.5, Overhelm: true},
}, },
}, },
}, },
{ {
Target: models.DiscountCalculationTarget{ Target: models.DiscountCalculationTarget{
Factor: 0.94, Factor: 0.94,
}, },
}, },
{ {
Target: models.DiscountCalculationTarget{ ID: "2",
Factor: 0.99, Layer: 2,
Overhelm: true, Target: models.DiscountCalculationTarget{
}, Factor: 0.99,
}, Overhelm: true,
{ },
Target: models.DiscountCalculationTarget{ },
TargetScope: models.TargetGroup, {
TargetGroup: "templategen", Target: models.DiscountCalculationTarget{
Factor: 0.996, TargetScope: models.TargetGroup,
}, TargetGroup: "templategen",
}, Factor: 0.996,
{ },
Target: models.DiscountCalculationTarget{ },
TargetScope: models.TargetGroup, {
TargetGroup: "dwarfener", Target: models.DiscountCalculationTarget{
Factor: 0.99, TargetScope: models.TargetGroup,
}, TargetGroup: "dwarfener",
}, Factor: 0.99,
}, },
), 0.01) },
}) },
))
t.Run("Наложение скидок по типу пользователя", func(t *testing.T) { })
assert.InEpsilon(t, 540, calculate.Discount(
[]core.Product{ t.Run("Наложение скидок по типу пользователя", func(t *testing.T) {
{ID: "p1", Price: 1500, Group: "templategen"}, assert.Equal(t, uint64(540), discount.Calculate(
{ID: "p2", Price: 300, Group: "templategen"}, []core.Product{
{ID: "p5", Price: 30, Group: "dwarfener"}, {ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p6", Price: 70, Group: "dwarfener"}, {ID: "p2", Price: 300, Group: "templategen"},
{ID: "p7", Price: 800, Group: "dwarfener"}, {ID: "p5", Price: 30, Group: "dwarfener"},
}, {ID: "p6", Price: 70, Group: "dwarfener"},
[]models.Discount{ {ID: "p7", Price: 800, Group: "dwarfener"},
{ },
Target: models.DiscountCalculationTarget{ []models.Discount{
TargetScope: models.TargetSum, {
Factor: 0.2, Target: models.DiscountCalculationTarget{
}, TargetScope: models.TargetSum,
}, Factor: 0.2,
}, },
), 0.01) },
}) },
} ))
})
}

@ -0,0 +1,147 @@
package discount
import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
)
func SortOverhelmingDiscounts(discounts []models.Discount) map[models.TargetScope][]models.DiscountCalculationTarget {
dividedDiscounts := divideDiscountsByScope(discounts)
return map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: sortOverhelmingDiscountsEach(dividedDiscounts[models.TargetEach]),
models.TargetGroup: sortOverhelmingDiscounts(dividedDiscounts[models.TargetGroup]),
models.TargetSum: sortOverhelmingDiscounts(dividedDiscounts[models.TargetSum]),
}
}
func constituteOverhelmingProductTargetLayersMap(discounts []models.Discount) map[string]uint32 {
overhelmingProductTargetLayers := make(map[string]uint32)
for _, discount := range discounts {
for _, productTarget := range discount.Target.Products {
if existingLayer, ok := overhelmingProductTargetLayers[productTarget.ID]; ok {
if productTarget.Overhelm && existingLayer < discount.Layer {
overhelmingProductTargetLayers[productTarget.ID] = discount.Layer
}
continue
}
if productTarget.Overhelm {
overhelmingProductTargetLayers[productTarget.ID] = discount.Layer
}
}
}
return overhelmingProductTargetLayers
}
func constituteOverhelmingDiscountLayer(discounts []models.Discount) uint32 {
overhelmingDiscountLayer := uint32(0)
for _, discount := range discounts {
if discount.Target.Overhelm && overhelmingDiscountLayer < discount.Layer {
overhelmingDiscountLayer = discount.Layer
}
}
return overhelmingDiscountLayer
}
func sortOverhelmingDiscountsEach(discounts []models.Discount) []models.DiscountCalculationTarget {
if len(discounts) < 1 {
return []models.DiscountCalculationTarget{}
}
sortedDiscountCalculationTargetsEach := make([]models.DiscountCalculationTarget, 0, len(discounts))
overhelmingProductTargetLayers := constituteOverhelmingProductTargetLayersMap(discounts)
overhelmingDiscountLayer := constituteOverhelmingDiscountLayer(discounts)
for _, discount := range discounts {
if overhelmingDiscountLayer > discount.Layer {
continue
}
updatedProductTargets := make([]models.ProductTarget, 0, len(discount.Target.Products))
for _, productTarget := range discount.Target.Products {
if overhelmingProductTarget, ok := overhelmingProductTargetLayers[productTarget.ID]; ok && overhelmingProductTarget > discount.Layer {
continue
}
updatedProductTargets = append(updatedProductTargets, models.ProductTarget{
ID: productTarget.ID,
Factor: productTarget.Factor,
})
}
sortedDiscountCalculationTargetsEach = append(sortedDiscountCalculationTargetsEach, models.DiscountCalculationTarget{
Products: updatedProductTargets,
Factor: discount.Target.Factor,
TargetScope: discount.Target.TargetScope,
TargetGroup: discount.Target.TargetGroup,
})
}
return sortedDiscountCalculationTargetsEach
}
func sortOverhelmingDiscounts(discounts []models.Discount) []models.DiscountCalculationTarget {
if len(discounts) < 1 {
return []models.DiscountCalculationTarget{}
}
sortedDiscountCalculationTargets := make([]models.DiscountCalculationTarget, 0, len(discounts))
overhelmingDiscountLayer := constituteOverhelmingDiscountLayer(discounts)
for _, discount := range discounts {
if overhelmingDiscountLayer > discount.Layer {
continue
}
sortedDiscountCalculationTargets = append(sortedDiscountCalculationTargets, models.DiscountCalculationTarget{
Factor: discount.Target.Factor,
TargetScope: discount.Target.TargetScope,
TargetGroup: discount.Target.TargetGroup,
})
}
return sortedDiscountCalculationTargets
}
func divideDiscountsByScope(discounts []models.Discount) map[models.TargetScope][]models.Discount {
dividedTargets := make(map[models.TargetScope][]models.Discount, 3)
for _, discount := range discounts {
if discount.Target.TargetScope == "" && len(discount.Target.Products) > 0 {
dividedTargets[models.TargetEach] = append(dividedTargets[models.TargetEach], models.Discount{
Layer: discount.Layer,
Target: models.DiscountCalculationTarget{
Products: discount.Target.Products,
Factor: discount.Target.Factor,
TargetScope: models.TargetEach,
Overhelm: discount.Target.Overhelm,
},
})
continue
}
if discount.Target.TargetScope == "" && len(discount.Target.Products) < 1 {
dividedTargets[models.TargetSum] = append(dividedTargets[models.TargetSum], models.Discount{
Layer: discount.Layer,
Target: models.DiscountCalculationTarget{
Factor: discount.Target.Factor,
TargetScope: models.TargetSum,
Overhelm: discount.Target.Overhelm,
},
})
continue
}
dividedTargets[discount.Target.TargetScope] = append(dividedTargets[discount.Target.TargetScope], discount)
}
return dividedTargets
}

@ -0,0 +1,743 @@
package discount_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/discount"
)
func TestSortOverhelmingDiscounts(t *testing.T) {
t.Run("Успешная сортировка скидок по каждому продукту при наличии перекрывающией скидки на каждый продукт и на определённый продукт", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5},
},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 1,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.94},
},
},
},
{
ID: "2",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91},
},
},
},
{
ID: "3",
Layer: 3,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
ID: "4",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по каждому продукту при наличии самой перекрывающей скидки", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "5",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
{
ID: "6",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91, Overhelm: true},
},
},
},
{
ID: "7",
Layer: 3,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
ID: "8",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по каждому продукту при наличии скидок с одинаковым значением слоя", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "9",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91, Overhelm: true},
},
},
},
{
ID: "2",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
ID: "3",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по каждому продукту при наличии скидок с одинаковым значением слоя и полностью перекрывающими другие", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
Factor: 0.92,
TargetScope: models.TargetEach,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
{
Factor: 0.92,
TargetScope: models.TargetEach,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91},
},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "4",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
{
ID: "7",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91, Overhelm: true},
},
},
},
{
ID: "4",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по каждому продукту при наличии скидок, где одна полностью перекрывает продукты другой", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
{
Factor: 0.92,
TargetScope: models.TargetEach,
Products: []models.ProductTarget{},
},
},
models.TargetGroup: {},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "5",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94, Overhelm: true},
},
},
},
{
ID: "6",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
t.Run("Успешная сортировка скидок по группе продуктов при наличии перекрывающих скидок", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {},
models.TargetGroup: {
{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "7",
Layer: 6,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
{
ID: "8",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.96,
},
},
{
ID: "9",
Layer: 3,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.91,
},
},
}),
)
})
t.Run("Успешная сортировка скидок по группе продуктов при наличии перекрывающих скидок и разных групп", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {},
models.TargetGroup: {
{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
models.TargetSum: {},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
{
ID: "2",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.42,
},
},
{
ID: "3",
Layer: 5,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "111",
Factor: 0.9,
},
},
{
ID: "4",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "111",
Factor: 0.8,
},
},
}),
)
})
t.Run("Успешная сортировка скидок по сумме при наличии скидки, перекрывающая все остальные", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {},
models.TargetGroup: {},
models.TargetSum: {
{
TargetScope: models.TargetSum,
Factor: 0.92,
},
},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetSum,
Factor: 0.92,
},
},
{
ID: "2",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.42,
},
},
{
ID: "3",
Layer: 5,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Overhelm: true,
Factor: 0.9,
},
},
{
ID: "4",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.8,
},
},
}),
)
})
t.Run("Успешная сортировка скидок по сумме при наличии перекрывающей скидки", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {},
models.TargetGroup: {},
models.TargetSum: {
{
TargetScope: models.TargetSum,
Factor: 0.92,
},
{
TargetScope: models.TargetSum,
Factor: 0.42,
},
{
TargetScope: models.TargetSum,
Factor: 0.9,
},
},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.92,
},
},
{
ID: "2",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Overhelm: true,
Factor: 0.42,
},
},
{
ID: "3",
Layer: 5,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.9,
},
},
{
ID: "4",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.8,
},
},
}),
)
})
t.Run("Успешная сортировка смешанных скидок при наличии перекрывающих", func(t *testing.T) {
assert.Equal(t,
map[models.TargetScope][]models.DiscountCalculationTarget{
models.TargetEach: {
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91},
},
},
{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
models.TargetGroup: {
{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
models.TargetSum: {
{
TargetScope: models.TargetSum,
Factor: 0.92,
},
{
TargetScope: models.TargetSum,
Factor: 0.42,
},
{
TargetScope: models.TargetSum,
Factor: 0.9,
},
},
},
discount.SortOverhelmingDiscounts([]models.Discount{
{
ID: "1",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.92,
},
},
{
ID: "2",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Overhelm: true,
Factor: 0.42,
},
},
{
ID: "3",
Layer: 5,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.9,
},
},
{
ID: "4",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetSum,
Factor: 0.8,
},
},
{
ID: "5",
Layer: 6,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.92,
},
},
{
ID: "6",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "123",
Factor: 0.42,
},
},
{
ID: "7",
Layer: 5,
Target: models.DiscountCalculationTarget{
Overhelm: true,
TargetScope: models.TargetGroup,
TargetGroup: "111",
Factor: 0.9,
},
},
{
ID: "8",
Layer: 2,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetGroup,
TargetGroup: "111",
Factor: 0.8,
},
},
{
ID: "9",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p2", Factor: 0.94},
},
},
},
{
ID: "10",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Products: []models.ProductTarget{
{ID: "p7", Factor: 0.91, Overhelm: true},
},
},
},
{
ID: "11",
Layer: 6,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.92,
Overhelm: true,
Products: []models.ProductTarget{
{ID: "p1", Factor: 0.93},
{ID: "p2", Factor: 0.945},
{ID: "p5", Factor: 0.97},
{ID: "p6", Factor: 0.97},
{ID: "p7", Factor: 0.83},
},
},
},
{
ID: "12",
Layer: 4,
Target: models.DiscountCalculationTarget{
TargetScope: models.TargetEach,
Factor: 0.8,
Products: []models.ProductTarget{
{ID: "p6", Factor: 0.5, Overhelm: true},
},
},
},
}),
)
})
}

@ -1,45 +1,44 @@
package expression package expression
import ( import (
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" )
)
type PeriodFieldPaths struct {
type PeriodFieldPaths struct { From string
From string To string
To string }
}
func GetAscRangeConditionBSON[T constraints.Ordered](key string, value T) []bson.D {
func GetAscRangeConditionBSON[T constraints.Ordered](key string, value T) []bson.D { return []bson.D{
return []bson.D{ {{Key: key, Value: bson.M{"$lte": value}}},
{{Key: key, Value: bson.M{"$lte": value}}}, {{Key: key, Value: nil}},
{{Key: key, Value: nil}}, }
} }
}
func GetValueConditionBSON(key string, value any) []bson.D {
func GetValueConditionBSON(key string, value any) []bson.D { return []bson.D{
return []bson.D{ {{Key: key, Value: value}},
{{Key: key, Value: value}}, {{Key: key, Value: nil}},
{{Key: key, Value: nil}}, }
} }
}
func GetPerionConditionBSON(period models.PeriodCondition, paths PeriodFieldPaths) []bson.M {
func GetPerionConditionBSON(period models.PeriodCondition, paths PeriodFieldPaths) []bson.M { return []bson.M{
return []bson.M{ {
{ "$and": []bson.D{
"$and": []bson.D{ {{Key: paths.From, Value: bson.M{"$lte": primitive.NewDateTimeFromTime(period.From)}}},
{{Key: paths.From, Value: bson.M{"$lte": primitive.NewDateTimeFromTime(period.From)}}}, {{Key: paths.To, Value: bson.M{"$gte": primitive.NewDateTimeFromTime(period.To)}}},
{{Key: paths.To, Value: bson.M{"$gte": primitive.NewDateTimeFromTime(period.To)}}}, },
}, },
}, {
{ "$and": []bson.D{
"$and": []bson.D{ {{Key: paths.From, Value: nil}},
{{Key: paths.From, Value: nil}}, {{Key: paths.To, Value: nil}},
{{Key: paths.To, Value: nil}}, },
}, },
}, }
} }
}

@ -1,84 +1,82 @@
package expression_test package expression_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression" )
)
func TestGetAscRangeConditionBSON(t *testing.T) {
func TestGetAscRangeConditionBSON(t *testing.T) { t.Run("Получение bson условия диапазона от меньшего с заполненными данными", func(t *testing.T) {
t.Run("Получение bson условия диапазона от меньшего с заполненными данными", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, []bson.D{
[]bson.D{ {{Key: "test", Value: bson.M{"$lte": "2020"}}},
{{Key: "test", Value: bson.M{"$lte": "2020"}}}, {{Key: "test", Value: nil}},
{{Key: "test", Value: nil}}, },
}, expression.GetAscRangeConditionBSON("test", "2020"),
expression.GetAscRangeConditionBSON("test", "2020"), )
) assert.Equal(t,
assert.Equal(t, []bson.D{
[]bson.D{ {{Key: "test2", Value: bson.M{"$lte": 200.00}}},
{{Key: "test2", Value: bson.M{"$lte": 200.00}}}, {{Key: "test2", Value: nil}},
{{Key: "test2", Value: nil}}, },
}, expression.GetAscRangeConditionBSON("test2", 200.00),
expression.GetAscRangeConditionBSON("test2", 200.00), )
) })
}) }
}
func TestGetValueConditionBSON(t *testing.T) {
func TestGetValueConditionBSON(t *testing.T) { t.Run("Получение bson условия значения с заполненными данными", func(t *testing.T) {
t.Run("Получение bson условия значения с заполненными данными", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, []bson.D{
[]bson.D{ {{Key: "test", Value: "2020"}},
{{Key: "test", Value: "2020"}}, {{Key: "test", Value: nil}},
{{Key: "test", Value: nil}}, },
}, expression.GetValueConditionBSON("test", "2020"),
expression.GetValueConditionBSON("test", "2020"), )
) assert.Equal(t,
assert.Equal(t, []bson.D{
[]bson.D{ {{Key: "test2", Value: 200.00}},
{{Key: "test2", Value: 200.00}}, {{Key: "test2", Value: nil}},
{{Key: "test2", Value: nil}}, },
}, expression.GetValueConditionBSON("test2", 200.00),
expression.GetValueConditionBSON("test2", 200.00), )
) })
}) }
}
func TestGetPerionConditionBSON(t *testing.T) {
func TestGetPerionConditionBSON(t *testing.T) { t.Run("Получение bson условия периода с заполненными данными", func(t *testing.T) {
assert.Equal(t,
t.Run("Получение bson условия периода с заполненными данными", func(t *testing.T) { []bson.M{
assert.Equal(t, {
[]bson.M{ "$and": []bson.D{
{ {{Key: fields.PeriodCondition.From, Value: bson.M{"$lte": primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC())}}},
"$and": []bson.D{ {{Key: fields.PeriodCondition.To, Value: bson.M{"$gte": primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC())}}},
{{Key: fields.PeriodCondition.From, Value: bson.M{"$lte": primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC())}}}, },
{{Key: fields.PeriodCondition.To, Value: bson.M{"$gte": primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC())}}}, },
}, {
}, "$and": []bson.D{
{ {{Key: fields.PeriodCondition.From, Value: nil}},
"$and": []bson.D{ {{Key: fields.PeriodCondition.To, Value: nil}},
{{Key: fields.PeriodCondition.From, Value: nil}}, },
{{Key: fields.PeriodCondition.To, Value: nil}}, },
}, },
}, expression.GetPerionConditionBSON(
}, models.PeriodCondition{
expression.GetPerionConditionBSON( From: time.Unix(1674546287, 0).UTC(),
models.PeriodCondition{ To: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), },
To: time.Unix(1674546287, 0).UTC(), expression.PeriodFieldPaths{
}, From: fields.PeriodCondition.From,
expression.PeriodFieldPaths{ To: fields.PeriodCondition.To,
From: fields.PeriodCondition.From, },
To: fields.PeriodCondition.To, ),
}, )
), })
) }
})
}

@ -1,47 +1,46 @@
package discount package discount
import ( import (
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults" )
)
func ConstituteCommonConditions(conditions *core.CommonDiscountCondition) []bson.M {
func ConstituteCommonConditions(conditions *core.CommonDiscountCondition) []bson.M { if conditions == nil {
if conditions == nil { return []bson.M{}
return []bson.M{} }
}
return []bson.M{
return []bson.M{ {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, conditions.User)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, conditions.User)}, {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, conditions.UserType)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, conditions.UserType)}, {
{ "$or": expression.GetPerionConditionBSON(
"$or": expression.GetPerionConditionBSON( conditions.Period,
conditions.Period, expression.PeriodFieldPaths{
expression.PeriodFieldPaths{ From: fields.PeriodCondition.From,
From: fields.PeriodCondition.From, To: fields.PeriodCondition.To,
To: fields.PeriodCondition.To, },
}, ),
), },
}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, conditions.PurchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, conditions.PurchasesAmount)}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, conditions.CartPurchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, conditions.CartPurchasesAmount)}, {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, defaults.GetDefaultValue(conditions.Coupon, ""))},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, defaults.GetDefaultValue(conditions.Coupon, ""))}, }
} }
}
func ConstituteOptionalConditions(conditions *core.OptionalDiscounCondition) []bson.M {
func ConstituteOptionalConditions(conditions *core.OptionalDiscounCondition) []bson.M { if conditions == nil {
if conditions == nil { return []bson.M{}
return []bson.M{} }
}
return []bson.M{
return []bson.M{ {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, defaults.GetDefaultValue(conditions.Product, ""))},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, defaults.GetDefaultValue(conditions.Product, ""))}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, defaults.GetDefaultValue(conditions.Term, uint64(0)))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, defaults.GetDefaultValue(conditions.Term, uint64(0)))}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, defaults.GetDefaultValue(conditions.Usage, uint64(0)))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, defaults.GetDefaultValue(conditions.Usage, uint64(0)))}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, defaults.GetDefaultValue(conditions.PriceFrom, uint64(0)))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, defaults.GetDefaultValue(conditions.PriceFrom, float64(0)))}, {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, defaults.GetDefaultValue(conditions.Group, ""))},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, defaults.GetDefaultValue(conditions.Group, ""))}, }
} }
}

@ -1,140 +1,139 @@
package discount_test package discount_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount" )
)
func TestConstituteCommonConditions(t *testing.T) {
func TestConstituteCommonConditions(t *testing.T) { userID := "user12"
userID := "user1" userType := "nkvo9"
userType := "nkvo" coupon := "coupon11"
coupon := "coupon" purchasesAmount := uint64(10000)
purchasesAmount := float64(10000) cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000)
t.Run("Формирование фильтра общих условий поиска с nil", func(t *testing.T) {
t.Run("Формирование фильтра общих условий поиска с nil", func(t *testing.T) { assert.Equal(t, []bson.M{}, discount.ConstituteCommonConditions(nil))
assert.Equal(t, []bson.M{}, discount.ConstituteCommonConditions(nil)) })
})
t.Run("Формирование фильтра общих условий поиска с купоном", func(t *testing.T) {
t.Run("Формирование фильтра общих условий поиска с купоном", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, []bson.M{
[]bson.M{ {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, userID)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, userID)}, {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, userType)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, userType)}, {
{ "$or": expression.GetPerionConditionBSON(
"$or": expression.GetPerionConditionBSON( models.PeriodCondition{
models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, expression.PeriodFieldPaths{
expression.PeriodFieldPaths{ From: fields.PeriodCondition.From,
From: fields.PeriodCondition.From, To: fields.PeriodCondition.To,
To: fields.PeriodCondition.To, },
}, ),
), },
}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, purchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, purchasesAmount)}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, cartPurchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, cartPurchasesAmount)}, {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, coupon)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, coupon)}, },
}, discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
discount.ConstituteCommonConditions(&core.CommonDiscountCondition{ Period: models.PeriodCondition{
Period: models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, User: userID,
User: userID, UserType: userType,
UserType: userType, Coupon: &coupon,
Coupon: &coupon, PurchasesAmount: purchasesAmount,
PurchasesAmount: purchasesAmount, CartPurchasesAmount: cartPurchasesAmount,
CartPurchasesAmount: cartPurchasesAmount, }),
}), )
) })
})
t.Run("Формирование фильтра общих условий поиска без купона", func(t *testing.T) {
t.Run("Формирование фильтра общих условий поиска без купона", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, []bson.M{
[]bson.M{ {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, userID)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.User, userID)}, {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, userType)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.UserType, userType)}, {
{ "$or": expression.GetPerionConditionBSON(
"$or": expression.GetPerionConditionBSON( models.PeriodCondition{
models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, expression.PeriodFieldPaths{
expression.PeriodFieldPaths{ From: fields.PeriodCondition.From,
From: fields.PeriodCondition.From, To: fields.PeriodCondition.To,
To: fields.PeriodCondition.To, },
}, ),
), },
}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, purchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PurchasesAmount, purchasesAmount)}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, cartPurchasesAmount)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.CartPurchasesAmount, cartPurchasesAmount)}, {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, "")},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Coupon, "")}, },
}, discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
discount.ConstituteCommonConditions(&core.CommonDiscountCondition{ Period: models.PeriodCondition{
Period: models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, User: userID,
User: userID, UserType: userType,
UserType: userType, PurchasesAmount: purchasesAmount,
PurchasesAmount: purchasesAmount, CartPurchasesAmount: cartPurchasesAmount,
CartPurchasesAmount: cartPurchasesAmount, }),
}), )
) })
}) }
}
func TestConstituteOptionalConditions(t *testing.T) {
func TestConstituteOptionalConditions(t *testing.T) { productID := "productgg1"
productID := "product1" term := uint64(14)
term := uint64(14) usage := uint64(10)
usage := uint64(10) priceFrom := uint64(13000)
priceFrom := float64(13000) group := "group888"
group := "group"
t.Run("Формирование фильтра опциональных условий поиска и nil", func(t *testing.T) {
t.Run("Формирование фильтра опциональных условий поиска и nil", func(t *testing.T) { assert.Equal(t, []bson.M{}, discount.ConstituteOptionalConditions(nil))
assert.Equal(t, []bson.M{}, discount.ConstituteOptionalConditions(nil)) })
})
t.Run("Формирование фильтра опциональных условий поиска с заполненными параметрами", func(t *testing.T) {
t.Run("Формирование фильтра опциональных условий поиска с заполненными параметрами", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, []bson.M{
[]bson.M{ {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, productID)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, productID)}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, term)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, term)}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, usage)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, usage)}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, priceFrom)},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, priceFrom)}, {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, group)},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, group)}, },
}, discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{
discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{ Product: &productID,
Product: &productID, Term: &term,
Term: &term, Usage: &usage,
Usage: &usage, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, Group: &group,
Group: &group, }),
}), )
) })
})
t.Run("Формирование фильтра опциональных условий поиска с пустыми параметрами", func(t *testing.T) {
t.Run("Формирование фильтра опциональных условий поиска с пустыми параметрами", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, []bson.M{
[]bson.M{ {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, "")},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Product, "")}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, uint64(0))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Term, uint64(0))}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, uint64(0))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.Usage, uint64(0))}, {"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, uint64(0))},
{"$or": expression.GetAscRangeConditionBSON(fields.DiscountCondition.PriceFrom, float64(0))}, {"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, "")},
{"$or": expression.GetValueConditionBSON(fields.DiscountCondition.Group, "")}, },
}, discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{}),
discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{}), )
) })
}) }
}

@ -1,34 +1,34 @@
package discount package discount
import ( import (
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" )
)
func ConditionFilter(conditions *core.DiscountConditions) *bson.M {
func ConditionFilter(conditions *core.DiscountConditions) *bson.M { if conditions == nil {
if conditions == nil { return nil
return nil }
}
expressions := make([]bson.M, 0)
expressions := make([]bson.M, 0) commonExpressions := ConstituteCommonConditions(&conditions.Common)
commonExpressions := ConstituteCommonConditions(&conditions.Common)
if len(conditions.Optionals) == 0 {
if len(conditions.Optionals) == 0 { optionalExpressions := ConstituteOptionalConditions(&core.OptionalDiscounCondition{})
optionalExpressions := ConstituteOptionalConditions(&core.OptionalDiscounCondition{}) expressions = append(expressions, bson.M{
expressions = append(expressions, bson.M{ "$and": append([]bson.M{}, append(commonExpressions, optionalExpressions...)...),
"$and": append([]bson.M{}, append(commonExpressions, optionalExpressions...)...), })
}) }
}
for _, expression := range conditions.Optionals {
for _, expression := range conditions.Optionals { expressionCopy := expression
optionalExpressions := ConstituteOptionalConditions(&expression) optionalExpressions := ConstituteOptionalConditions(&expressionCopy)
expressions = append(expressions, bson.M{ expressions = append(expressions, bson.M{
"$and": append([]bson.M{}, append(commonExpressions, optionalExpressions...)...), "$and": append([]bson.M{}, append(commonExpressions, optionalExpressions...)...),
}) })
} }
return &bson.M{ return &bson.M{
"$or": expressions, "$or": expressions,
} }
} }

@ -1,123 +1,122 @@
package discount_test package discount_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount" )
)
func TestConditionFilter(t *testing.T) {
func TestConditionFilter(t *testing.T) { productID := "product1"
productID := "product1" productID2 := "product2"
productID2 := "product2" term := uint64(14)
term := uint64(14) usage := uint64(10)
usage := uint64(10) priceFrom := uint64(13000)
priceFrom := float64(13000) group := "groupopo"
group := "group" userID := "user1"
userID := "user1" userType := "nkvo01001"
userType := "nkvo" coupon := "coupon"
coupon := "coupon" purchasesAmount := uint64(10000)
purchasesAmount := float64(10000) cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000)
t.Run("Формирование фильтра условий поиска с пустыми условиями", func(t *testing.T) {
t.Run("Формирование фильтра условий поиска с пустыми условиями", func(t *testing.T) { assert.NotNil(t, discount.ConditionFilter(&core.DiscountConditions{}))
assert.NotNil(t, discount.ConditionFilter(&core.DiscountConditions{})) })
})
t.Run("Формирование фильтра условий поиска с nil", func(t *testing.T) {
t.Run("Формирование фильтра условий поиска с nil", func(t *testing.T) { assert.Nil(t, discount.ConditionFilter(nil))
assert.Nil(t, discount.ConditionFilter(nil)) })
})
t.Run("Формирование фильтра условий поиска со всеми условиями", func(t *testing.T) {
t.Run("Формирование фильтра условий поиска со всеми условиями", func(t *testing.T) { commonConditions := discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
commonConditions := discount.ConstituteCommonConditions(&core.CommonDiscountCondition{ Period: models.PeriodCondition{
Period: models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, User: userID,
User: userID, UserType: userType,
UserType: userType, Coupon: &coupon,
Coupon: &coupon, PurchasesAmount: purchasesAmount,
PurchasesAmount: purchasesAmount, CartPurchasesAmount: cartPurchasesAmount,
CartPurchasesAmount: cartPurchasesAmount, })
})
productCondition1 := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{
productCondition1 := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{ Product: &productID,
Product: &productID, Term: &term,
Term: &term, Usage: &usage,
Usage: &usage, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, Group: &group,
Group: &group, })
})
productCondition2 := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{
productCondition2 := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{ Product: &productID2,
Product: &productID2, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, })
})
assert.Equal(t,
assert.Equal(t, &bson.M{
&bson.M{ "$or": []bson.M{
"$or": []bson.M{ {"$and": append([]bson.M{}, append(commonConditions, productCondition1...)...)},
{"$and": append([]bson.M{}, append(commonConditions, productCondition1...)...)}, {"$and": append([]bson.M{}, append(commonConditions, productCondition2...)...)},
{"$and": append([]bson.M{}, append(commonConditions, productCondition2...)...)}, },
}, },
}, discount.ConditionFilter(&core.DiscountConditions{
discount.ConditionFilter(&core.DiscountConditions{ Common: core.CommonDiscountCondition{
Common: core.CommonDiscountCondition{ Period: models.PeriodCondition{
Period: models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, User: userID,
User: userID, UserType: userType,
UserType: userType, Coupon: &coupon,
Coupon: &coupon, PurchasesAmount: purchasesAmount,
PurchasesAmount: purchasesAmount, CartPurchasesAmount: cartPurchasesAmount,
CartPurchasesAmount: cartPurchasesAmount, },
}, Optionals: []core.OptionalDiscounCondition{
Optionals: []core.OptionalDiscounCondition{ {
{ Product: &productID,
Product: &productID, Term: &term,
Term: &term, Usage: &usage,
Usage: &usage, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, Group: &group,
Group: &group, },
}, {
{ Product: &productID2,
Product: &productID2, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, },
}, },
}, }),
}), )
) })
})
t.Run("Формирование фильтра условий поиска с периодом", func(t *testing.T) {
t.Run("Формирование фильтра условий поиска с периодом", func(t *testing.T) { productCondition := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{})
productCondition := discount.ConstituteOptionalConditions(&core.OptionalDiscounCondition{}) commonConditions := discount.ConstituteCommonConditions(&core.CommonDiscountCondition{
commonConditions := discount.ConstituteCommonConditions(&core.CommonDiscountCondition{ Period: models.PeriodCondition{
Period: models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, })
})
assert.Equal(t,
assert.Equal(t, &bson.M{
&bson.M{ "$or": []bson.M{
"$or": []bson.M{ {"$and": append([]bson.M{}, append(commonConditions, productCondition...)...)},
{"$and": append([]bson.M{}, append(commonConditions, productCondition...)...)}, },
}, },
}, discount.ConditionFilter(&core.DiscountConditions{
discount.ConditionFilter(&core.DiscountConditions{ Common: core.CommonDiscountCondition{
Common: core.CommonDiscountCondition{ Period: models.PeriodCondition{
Period: models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, },
}, }),
}), )
) })
}) }
}

@ -1,145 +1,144 @@
package discount package discount
import ( import (
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils" )
)
func GetDiscountExpression(settings *core.FilterDiscountSettings) *bson.M {
func GetDiscountExpression(settings *core.FilterDiscountSettings) *bson.M { if settings == nil || utils.GetFilledFieldsCount(settings) < 2 {
if settings == nil || utils.GetFilledFieldsCount(settings) < 2 { return nil
return nil }
}
expression := bson.M{}
expression := bson.M{} commonExpression := getCommonDiscountExpression(settings)
commonExpression := getCommonDiscountExpression(settings) conditionExpression := getDiscountConditionExpression(settings)
conditionExpression := getDiscountConditionExpression(settings) targetExpression := getDiscountTargetExpression(settings)
targetExpression := getDiscountTargetExpression(settings)
for key, value := range commonExpression {
for key, value := range commonExpression { expression[key] = value
expression[key] = value }
}
for key, value := range conditionExpression {
for key, value := range conditionExpression { expression[key] = value
expression[key] = value }
}
for key, value := range targetExpression {
for key, value := range targetExpression { expression[key] = value
expression[key] = value }
}
return &expression
return &expression }
}
func getCommonDiscountExpression(deps *core.FilterDiscountSettings) bson.M {
func getCommonDiscountExpression(deps *core.FilterDiscountSettings) bson.M { expression := bson.M{}
expression := bson.M{}
if deps.ID != nil {
if deps.ID != nil { expression[fields.Discount.ID] = *deps.ID
expression[fields.Discount.ID] = *deps.ID }
}
if deps.Name != nil {
if deps.Name != nil { expression[fields.Discount.Name] = *deps.Name
expression[fields.Discount.Name] = *deps.Name }
}
if deps.Description != nil {
if deps.Description != nil { expression[fields.Discount.Description] = *deps.Description
expression[fields.Discount.Description] = *deps.Description }
}
if deps.Layer != nil {
if deps.Layer != nil { expression[fields.Discount.Layer] = *deps.Layer
expression[fields.Discount.Layer] = *deps.Layer }
}
if deps.Deprecated != nil {
if deps.Deprecated != nil { expression[fields.Discount.Deprecated] = *deps.Deprecated
expression[fields.Discount.Deprecated] = *deps.Deprecated }
}
return expression
return expression }
}
func getDiscountConditionExpression(deps *core.FilterDiscountSettings) bson.M {
func getDiscountConditionExpression(deps *core.FilterDiscountSettings) bson.M { expression := bson.M{}
expression := bson.M{}
if deps.Condition == nil {
if deps.Condition == nil { return expression
return expression }
}
if deps.Condition.Period != nil {
if deps.Condition.Period != nil { from := primitive.NewDateTimeFromTime(deps.Condition.Period.From)
from := primitive.NewDateTimeFromTime(deps.Condition.Period.From) to := primitive.NewDateTimeFromTime(deps.Condition.Period.To)
to := primitive.NewDateTimeFromTime(deps.Condition.Period.To)
expression[fields.PeriodCondition.From] = from
expression[fields.PeriodCondition.From] = from expression[fields.PeriodCondition.To] = to
expression[fields.PeriodCondition.To] = to }
}
if deps.Condition.Product != nil {
if deps.Condition.Product != nil { expression[fields.DiscountCondition.Product] = *deps.Condition.Product
expression[fields.DiscountCondition.Product] = *deps.Condition.Product }
}
if deps.Condition.PriceFrom != nil {
if deps.Condition.PriceFrom != nil { expression[fields.DiscountCondition.PriceFrom] = *deps.Condition.PriceFrom
expression[fields.DiscountCondition.PriceFrom] = *deps.Condition.PriceFrom }
}
if deps.Condition.Group != nil {
if deps.Condition.Group != nil { expression[fields.DiscountCondition.Group] = *deps.Condition.Group
expression[fields.DiscountCondition.Group] = *deps.Condition.Group }
}
if deps.Condition.User != nil {
if deps.Condition.User != nil { expression[fields.DiscountCondition.User] = *deps.Condition.User
expression[fields.DiscountCondition.User] = *deps.Condition.User }
}
if deps.Condition.UserType != nil {
if deps.Condition.UserType != nil { expression[fields.DiscountCondition.UserType] = *deps.Condition.UserType
expression[fields.DiscountCondition.UserType] = *deps.Condition.UserType }
}
if deps.Condition.Coupon != nil {
if deps.Condition.Coupon != nil { expression[fields.DiscountCondition.Coupon] = *deps.Condition.Coupon
expression[fields.DiscountCondition.Coupon] = *deps.Condition.Coupon }
}
if deps.Condition.PurchasesAmount != nil {
if deps.Condition.PurchasesAmount != nil { expression[fields.DiscountCondition.PurchasesAmount] = *deps.Condition.PurchasesAmount
expression[fields.DiscountCondition.PurchasesAmount] = *deps.Condition.PurchasesAmount }
}
if deps.Condition.CartPurchasesAmount != nil {
if deps.Condition.CartPurchasesAmount != nil { expression[fields.DiscountCondition.CartPurchasesAmount] = *deps.Condition.CartPurchasesAmount
expression[fields.DiscountCondition.CartPurchasesAmount] = *deps.Condition.CartPurchasesAmount }
}
if deps.Condition.Term != nil {
if deps.Condition.Term != nil { expression[fields.DiscountCondition.Term] = *deps.Condition.Term
expression[fields.DiscountCondition.Term] = *deps.Condition.Term }
}
if deps.Condition.Usage != nil {
if deps.Condition.Usage != nil { expression[fields.DiscountCondition.Usage] = *deps.Condition.Usage
expression[fields.DiscountCondition.Usage] = *deps.Condition.Usage }
}
return expression
return expression }
}
func getDiscountTargetExpression(deps *core.FilterDiscountSettings) bson.M {
func getDiscountTargetExpression(deps *core.FilterDiscountSettings) bson.M { expression := bson.M{}
expression := bson.M{}
if deps.Products != nil {
if deps.Products != nil { expression[fields.DiscountTarget.Products] = *deps.Products
expression[fields.DiscountTarget.Products] = *deps.Products }
}
if deps.Factor != nil {
if deps.Factor != nil { expression[fields.DiscountTarget.Factor] = *deps.Factor
expression[fields.DiscountTarget.Factor] = *deps.Factor }
}
if deps.TargetScope != nil {
if deps.TargetScope != nil { expression[fields.DiscountTarget.TargetScope] = *deps.TargetScope
expression[fields.DiscountTarget.TargetScope] = *deps.TargetScope }
}
if deps.TargetGroup != nil {
if deps.TargetGroup != nil { expression[fields.DiscountTarget.TargetGroup] = *deps.TargetGroup
expression[fields.DiscountTarget.TargetGroup] = *deps.TargetGroup }
}
if deps.Overhelm != nil {
if deps.Overhelm != nil { expression[fields.DiscountTarget.Overhelm] = *deps.Overhelm
expression[fields.DiscountTarget.Overhelm] = *deps.Overhelm }
}
return expression
return expression }
}

@ -1,122 +1,121 @@
package discount_test package discount_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount" )
)
func TestGetDiscountExpression(t *testing.T) {
func TestGetDiscountExpression(t *testing.T) { id := "id1"
id := "id1" name := "discount1oo"
name := "discount" description := ""
description := "" layer := uint32(3)
layer := uint32(3) deprecated := false
deprecated := false user := "testUser3"
user := "testUser" userType := "nkvo090909"
userType := "nkvo" coupon := "coupon14"
coupon := "coupon1" product := "p12"
product := "p1" priceFrom := uint64(200)
priceFrom := float64(200) cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000) purchasesAmount := uint64(10000)
purchasesAmount := float64(10000) usage := uint64(20)
usage := uint64(20) term := uint64(40)
term := uint64(40) factor := 0.95
factor := 0.95 targetScope := models.TargetSum
targetScope := models.TargetSum targetGroup := "groupffffff"
targetGroup := "group" overhelm := true
overhelm := true condition := models.DiscountCondition{
condition := models.DiscountCondition{ User: &user,
User: &user, Product: &product,
Product: &product, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, Group: &targetGroup,
Group: &targetGroup, Usage: &usage,
Usage: &usage, UserType: &userType,
UserType: &userType, Coupon: &coupon,
Coupon: &coupon, CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, PurchasesAmount: &purchasesAmount,
PurchasesAmount: &purchasesAmount, Term: &term,
Term: &term, Period: &models.PeriodCondition{
Period: &models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, }
} products := []models.ProductTarget{
products := []models.ProductTarget{ {
{ ID: "p1",
ID: "p1", Factor: 0.42,
Factor: 0.42, Overhelm: false,
Overhelm: false, },
}, }
}
t.Run("Формирование запроса фильтрации с пустыми параметрами", func(t *testing.T) {
t.Run("Формирование запроса фильтрации с пустыми параметрами", func(t *testing.T) { assert.Nil(t, discount.GetDiscountExpression(&core.FilterDiscountSettings{}))
assert.Nil(t, discount.GetDiscountExpression(&core.FilterDiscountSettings{})) })
})
t.Run("Формирование запроса фильтрации с nil", func(t *testing.T) {
t.Run("Формирование запроса фильтрации с nil", func(t *testing.T) { assert.Nil(t, discount.GetDiscountExpression(nil))
assert.Nil(t, discount.GetDiscountExpression(nil)) })
})
t.Run("Формирование запроса фильтрации с заполненными параметрами", func(t *testing.T) {
t.Run("Формирование запроса фильтрации с заполненными параметрами", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &bson.M{
&bson.M{ fields.Discount.ID: id,
fields.Discount.ID: id, fields.Discount.Name: name,
fields.Discount.Name: name, fields.Discount.Description: description,
fields.Discount.Description: description, fields.Discount.Layer: layer,
fields.Discount.Layer: layer, fields.Discount.Deprecated: deprecated,
fields.Discount.Deprecated: deprecated, fields.PeriodCondition.From: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.PeriodCondition.From: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()), fields.PeriodCondition.To: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.PeriodCondition.To: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()), fields.DiscountCondition.Product: product,
fields.DiscountCondition.Product: product, fields.DiscountCondition.PriceFrom: priceFrom,
fields.DiscountCondition.PriceFrom: priceFrom, fields.DiscountCondition.PurchasesAmount: purchasesAmount,
fields.DiscountCondition.PurchasesAmount: purchasesAmount, fields.DiscountCondition.CartPurchasesAmount: cartPurchasesAmount,
fields.DiscountCondition.CartPurchasesAmount: cartPurchasesAmount, fields.DiscountCondition.Group: targetGroup,
fields.DiscountCondition.Group: targetGroup, fields.DiscountCondition.Usage: usage,
fields.DiscountCondition.Usage: usage, fields.DiscountCondition.User: user,
fields.DiscountCondition.User: user, fields.DiscountCondition.UserType: userType,
fields.DiscountCondition.UserType: userType, fields.DiscountCondition.Coupon: coupon,
fields.DiscountCondition.Coupon: coupon, fields.DiscountCondition.Term: term,
fields.DiscountCondition.Term: term, fields.DiscountTarget.TargetGroup: targetGroup,
fields.DiscountTarget.TargetGroup: targetGroup, fields.DiscountTarget.TargetScope: targetScope,
fields.DiscountTarget.TargetScope: targetScope, fields.DiscountTarget.Factor: factor,
fields.DiscountTarget.Factor: factor, fields.DiscountTarget.Overhelm: overhelm,
fields.DiscountTarget.Overhelm: overhelm, fields.DiscountTarget.Products: products,
fields.DiscountTarget.Products: products, },
}, discount.GetDiscountExpression(&core.FilterDiscountSettings{
discount.GetDiscountExpression(&core.FilterDiscountSettings{ ID: &id,
ID: &id, Name: &name,
Name: &name, Description: &description,
Description: &description, Layer: &layer,
Layer: &layer, Deprecated: &deprecated,
Deprecated: &deprecated, Factor: &factor,
Factor: &factor, TargetScope: &targetScope,
TargetScope: &targetScope, TargetGroup: &targetGroup,
TargetGroup: &targetGroup, Overhelm: &overhelm,
Overhelm: &overhelm, Condition: &condition,
Condition: &condition, Products: &products,
Products: &products, }),
}), )
) })
})
t.Run("Формирование запроса фильтрации с отсутствующими параметрами", func(t *testing.T) {
t.Run("Формирование запроса фильтрации с отсутствующими параметрами", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &bson.M{
&bson.M{ fields.Discount.Name: name,
fields.Discount.Name: name, fields.Discount.Description: description,
fields.Discount.Description: description, },
}, discount.GetDiscountExpression(&core.FilterDiscountSettings{
discount.GetDiscountExpression(&core.FilterDiscountSettings{ Name: &name,
Name: &name, Description: &description,
Description: &description, }),
}), )
) })
}) }
}

@ -1,72 +1,71 @@
package discount package discount
import ( import (
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert" )
)
type UpdateDiscountExpression struct {
type UpdateDiscountExpression struct { ID string
ID string Expression *bson.M
Expression *bson.M }
}
func GetUpdateDiscountsExpressions(deps []core.UpdateDiscountSettings) []UpdateDiscountExpression {
func GetUpdateDiscountsExpressions(deps []core.UpdateDiscountSettings) []UpdateDiscountExpression { discounts := make([]UpdateDiscountExpression, 0, len(deps))
discounts := make([]UpdateDiscountExpression, 0, len(deps)) requestsMap := convert.ArrayToMap(deps, func(element core.UpdateDiscountSettings) string {
requestsMap := convert.ArrayToMap(deps, func(element core.UpdateDiscountSettings) string { return element.ID
return element.ID })
})
for _, request := range requestsMap {
for _, request := range requestsMap { updateExpression := GetUpdateDiscountExpression(&core.UpdateDiscountSettings{
updateExpression := GetUpdateDiscountExpression(&core.UpdateDiscountSettings{ ID: request.ID,
ID: request.ID, Name: request.Name,
Name: request.Name, Description: request.Description,
Description: request.Description, Layer: request.Layer,
Layer: request.Layer, Deprecated: request.Deprecated,
Deprecated: request.Deprecated, Factor: request.Factor,
Factor: request.Factor, TargetScope: request.TargetScope,
TargetScope: request.TargetScope, TargetGroup: request.TargetGroup,
TargetGroup: request.TargetGroup, Overhelm: request.Overhelm,
Overhelm: request.Overhelm, Condition: request.Condition,
Condition: request.Condition, Products: request.Products,
Products: request.Products, })
})
if updateExpression == nil {
if updateExpression == nil { continue
continue }
}
discounts = append(discounts, *updateExpression)
discounts = append(discounts, *updateExpression) }
}
return discounts
return discounts }
}
func GetUpdateDiscountExpression(deps *core.UpdateDiscountSettings) *UpdateDiscountExpression {
func GetUpdateDiscountExpression(deps *core.UpdateDiscountSettings) *UpdateDiscountExpression { if deps == nil {
if deps == nil { return nil
return nil }
}
updateExpression := GetDiscountExpression(&core.FilterDiscountSettings{
updateExpression := GetDiscountExpression(&core.FilterDiscountSettings{ Condition: deps.Condition,
Condition: deps.Condition, Name: deps.Name,
Name: deps.Name, Description: deps.Description,
Description: deps.Description, Layer: deps.Layer,
Layer: deps.Layer, Deprecated: deps.Deprecated,
Deprecated: deps.Deprecated, Products: deps.Products,
Products: deps.Products, Factor: deps.Factor,
Factor: deps.Factor, TargetScope: deps.TargetScope,
TargetScope: deps.TargetScope, TargetGroup: deps.TargetGroup,
TargetGroup: deps.TargetGroup, Overhelm: deps.Overhelm,
Overhelm: deps.Overhelm, })
})
if updateExpression == nil {
if updateExpression == nil { return nil
return nil }
}
return &UpdateDiscountExpression{
return &UpdateDiscountExpression{ ID: deps.ID,
ID: deps.ID, Expression: &bson.M{"$set": *updateExpression},
Expression: &bson.M{"$set": *updateExpression}, }
} }
}

@ -1,185 +1,184 @@
package discount_test package discount_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/fields" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/expression/discount" )
)
func TestGetUpdateDiscountExpression(t *testing.T) {
func TestGetUpdateDiscountExpression(t *testing.T) { name := "discount"
name := "discount" description := ""
description := "" layer := uint32(3)
layer := uint32(3) deprecated := false
deprecated := false user := "testUser"
user := "testUser" userType := "nkvo"
userType := "nkvo" coupon := "coupon1"
coupon := "coupon1" product := "p1"
product := "p1" priceFrom := uint64(200)
priceFrom := float64(200) cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000) purchasesAmount := uint64(10000)
purchasesAmount := float64(10000) usage := uint64(20)
usage := uint64(20) term := uint64(40)
term := uint64(40) factor := 0.95
factor := 0.95 targetScope := models.TargetSum
targetScope := models.TargetSum targetGroup := "group"
targetGroup := "group" overhelm := true
overhelm := true condition := models.DiscountCondition{
condition := models.DiscountCondition{ User: &user,
User: &user, Product: &product,
Product: &product, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, Group: &targetGroup,
Group: &targetGroup, Usage: &usage,
Usage: &usage, UserType: &userType,
UserType: &userType, Coupon: &coupon,
Coupon: &coupon, CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, PurchasesAmount: &purchasesAmount,
PurchasesAmount: &purchasesAmount, Term: &term,
Term: &term, Period: &models.PeriodCondition{
Period: &models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, }
} products := []models.ProductTarget{
products := []models.ProductTarget{ {
{ ID: "p1",
ID: "p1", Factor: 0.42,
Factor: 0.42, Overhelm: false,
Overhelm: false, },
}, }
}
t.Run("Формирование запроса обновления с пустыми параметрами", func(t *testing.T) {
t.Run("Формирование запроса обновления с пустыми параметрами", func(t *testing.T) { assert.Nil(t, discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{}))
assert.Nil(t, discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{})) })
})
t.Run("Формирование запроса обновления с nil", func(t *testing.T) {
t.Run("Формирование запроса обновления с nil", func(t *testing.T) { assert.Nil(t, discount.GetUpdateDiscountExpression(nil))
assert.Nil(t, discount.GetUpdateDiscountExpression(nil)) })
})
t.Run("Формирование запроса обновления с заполненными параметрами", func(t *testing.T) {
t.Run("Формирование запроса обновления с заполненными параметрами", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &discount.UpdateDiscountExpression{
&discount.UpdateDiscountExpression{ ID: "test",
ID: "test", Expression: &bson.M{
Expression: &bson.M{ "$set": bson.M{
"$set": bson.M{ fields.Discount.Name: name,
fields.Discount.Name: name, fields.Discount.Description: description,
fields.Discount.Description: description, fields.Discount.Layer: layer,
fields.Discount.Layer: layer, fields.Discount.Deprecated: deprecated,
fields.Discount.Deprecated: deprecated, fields.PeriodCondition.From: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.PeriodCondition.From: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()), fields.PeriodCondition.To: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()),
fields.PeriodCondition.To: primitive.NewDateTimeFromTime(time.Unix(1674546287, 0).UTC()), fields.DiscountCondition.Product: product,
fields.DiscountCondition.Product: product, fields.DiscountCondition.PriceFrom: priceFrom,
fields.DiscountCondition.PriceFrom: priceFrom, fields.DiscountCondition.PurchasesAmount: purchasesAmount,
fields.DiscountCondition.PurchasesAmount: purchasesAmount, fields.DiscountCondition.CartPurchasesAmount: cartPurchasesAmount,
fields.DiscountCondition.CartPurchasesAmount: cartPurchasesAmount, fields.DiscountCondition.Group: targetGroup,
fields.DiscountCondition.Group: targetGroup, fields.DiscountCondition.Usage: usage,
fields.DiscountCondition.Usage: usage, fields.DiscountCondition.User: user,
fields.DiscountCondition.User: user, fields.DiscountCondition.UserType: userType,
fields.DiscountCondition.UserType: userType, fields.DiscountCondition.Coupon: coupon,
fields.DiscountCondition.Coupon: coupon, fields.DiscountCondition.Term: term,
fields.DiscountCondition.Term: term, fields.DiscountTarget.TargetGroup: targetGroup,
fields.DiscountTarget.TargetGroup: targetGroup, fields.DiscountTarget.TargetScope: targetScope,
fields.DiscountTarget.TargetScope: targetScope, fields.DiscountTarget.Factor: factor,
fields.DiscountTarget.Factor: factor, fields.DiscountTarget.Overhelm: overhelm,
fields.DiscountTarget.Overhelm: overhelm, fields.DiscountTarget.Products: products,
fields.DiscountTarget.Products: products, },
}, },
}, },
}, discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{
discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{ ID: "test",
ID: "test", Name: &name,
Name: &name, Description: &description,
Description: &description, Layer: &layer,
Layer: &layer, Deprecated: &deprecated,
Deprecated: &deprecated, Factor: &factor,
Factor: &factor, TargetScope: &targetScope,
TargetScope: &targetScope, TargetGroup: &targetGroup,
TargetGroup: &targetGroup, Overhelm: &overhelm,
Overhelm: &overhelm, Condition: &condition,
Condition: &condition, Products: &products,
Products: &products, }),
}), )
) })
})
t.Run("Формирование запроса обновления с отсутствующими параметрами", func(t *testing.T) {
t.Run("Формирование запроса обновления с отсутствующими параметрами", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &discount.UpdateDiscountExpression{
&discount.UpdateDiscountExpression{ Expression: &bson.M{
Expression: &bson.M{ "$set": bson.M{
"$set": bson.M{ fields.Discount.Name: name,
fields.Discount.Name: name, fields.Discount.Description: description,
fields.Discount.Description: description, },
}, },
}, },
}, discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{
discount.GetUpdateDiscountExpression(&core.UpdateDiscountSettings{ Name: &name,
Name: &name, Description: &description,
Description: &description, }),
}), )
) })
}) }
}
func TestGetUpdateDiscountsExpressions(t *testing.T) {
func TestGetUpdateDiscountsExpressions(t *testing.T) { name1 := "discount1"
name1 := "discount1" name2 := "discount2"
name2 := "discount2" description := ""
description := ""
t.Run("Формирование запросов обновления с пустым массивом", func(t *testing.T) {
t.Run("Формирование запросов обновления с пустым массивом", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, []discount.UpdateDiscountExpression{},
[]discount.UpdateDiscountExpression{}, discount.GetUpdateDiscountsExpressions([]core.UpdateDiscountSettings{}),
discount.GetUpdateDiscountsExpressions([]core.UpdateDiscountSettings{}), )
) })
})
t.Run("Формирование запроса обновления с отсутствующими и пустыми параметрами, а также с одинаковыми ID", func(t *testing.T) {
t.Run("Формирование запроса обновления с отсутствующими и пустыми параметрами, а также с одинаковыми ID", func(t *testing.T) { assert.ElementsMatch(t,
assert.ElementsMatch(t, []discount.UpdateDiscountExpression{
[]discount.UpdateDiscountExpression{ {
{ ID: "discount1",
ID: "discount1", Expression: &bson.M{
Expression: &bson.M{ "$set": bson.M{
"$set": bson.M{ fields.Discount.Name: name1,
fields.Discount.Name: name1, fields.Discount.Description: description,
fields.Discount.Description: description, },
}, },
}, },
}, {
{ ID: "discount2",
ID: "discount2", Expression: &bson.M{
Expression: &bson.M{ "$set": bson.M{
"$set": bson.M{ fields.Discount.Name: name2,
fields.Discount.Name: name2, fields.Discount.Description: description,
fields.Discount.Description: description, },
}, },
}, },
}, },
}, discount.GetUpdateDiscountsExpressions([]core.UpdateDiscountSettings{
discount.GetUpdateDiscountsExpressions([]core.UpdateDiscountSettings{ {
{ ID: "discount1",
ID: "discount1", Name: &name2,
Name: &name2, Description: &description,
Description: &description, },
}, {
{ ID: "discount1",
ID: "discount1", Name: &name1,
Name: &name1, Description: &description,
Description: &description, },
}, {
{ ID: "discount2",
ID: "discount2", Name: &name2,
Name: &name2, Description: &description,
Description: &description, },
}, {},
{}, }),
}), )
) })
}) }
}

@ -1,30 +1,32 @@
package utils package utils
import ( import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
) )
func FilterCouponDiscounts(discounts []models.Discount) ([]models.Discount, *models.Discount) { func FilterCouponDiscounts(discounts []models.Discount) ([]models.Discount, *models.Discount) {
if len(discounts) == 0 { if len(discounts) == 0 {
return []models.Discount{}, nil return []models.Discount{}, nil
} }
var couponDiscount *models.Discount var couponDiscount *models.Discount
notCouponDiscount := make([]models.Discount, 0, len(discounts))
notCouponDiscount := make([]models.Discount, 0, len(discounts))
for _, discount := range discounts {
isCouponDiscount := discount.Condition.Coupon != nil && *discount.Condition.Coupon != "" for _, discount := range discounts {
currentDiscount := discount isCouponDiscount := discount.Condition.Coupon != nil && *discount.Condition.Coupon != ""
currentDiscount := discount
if isCouponDiscount && couponDiscount != nil {
continue if isCouponDiscount && couponDiscount != nil {
} continue
if isCouponDiscount && couponDiscount == nil { }
couponDiscount = &currentDiscount
} if isCouponDiscount && couponDiscount == nil {
couponDiscount = &currentDiscount
notCouponDiscount = append(notCouponDiscount, discount) }
}
notCouponDiscount = append(notCouponDiscount, discount)
return notCouponDiscount, couponDiscount }
}
return notCouponDiscount, couponDiscount
}

@ -1,48 +1,47 @@
package utils_test package utils_test
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils" )
)
func TestFilterDiscounts(t *testing.T) {
func TestFilterDiscounts(t *testing.T) { coupon1 := "testCoupon1"
coupon1 := "testCoupon1" coupon2 := "testCoupon2"
coupon2 := "testCoupon2" user := "user1"
user := "user1"
t.Run("Корректная фильтрация купоночных скидок", func(t *testing.T) {
t.Run("Корректная фильтрация купоночных скидок", func(t *testing.T) { filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{ {Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}},
{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}}, {Name: "11", Condition: models.DiscountCondition{Coupon: &coupon2}},
{Name: "11", Condition: models.DiscountCondition{Coupon: &coupon2}}, {Name: "2", Condition: models.DiscountCondition{User: &user}},
{Name: "2", Condition: models.DiscountCondition{User: &user}}, {Name: "3"},
{Name: "3"}, {Name: "4"},
{Name: "4"}, })
})
assert.ElementsMatch(t, []models.Discount{
assert.ElementsMatch(t, []models.Discount{ {Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}},
{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}}, {Name: "2", Condition: models.DiscountCondition{User: &user}},
{Name: "2", Condition: models.DiscountCondition{User: &user}}, {Name: "3"},
{Name: "3"}, {Name: "4"},
{Name: "4"}, }, filteredDiscounts)
}, filteredDiscounts) assert.Equal(t, &models.Discount{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}}, couponDiscount)
assert.Equal(t, &models.Discount{Name: "1", Condition: models.DiscountCondition{Coupon: &coupon1}}, couponDiscount) })
})
t.Run("Корректная фильтрация купоночных скидок с пустым значением", func(t *testing.T) {
t.Run("Корректная фильтрация купоночных скидок с пустым значением", func(t *testing.T) { filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{})
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{})
assert.Equal(t, []models.Discount{}, filteredDiscounts)
assert.Equal(t, []models.Discount{}, filteredDiscounts) assert.Nil(t, couponDiscount)
assert.Nil(t, couponDiscount) })
})
t.Run("Корректная фильтрация купоночных скидок без купоночной скидки", func(t *testing.T) {
t.Run("Корректная фильтрация купоночных скидок без купоночной скидки", func(t *testing.T) { filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{{}})
filteredDiscounts, couponDiscount := utils.FilterCouponDiscounts([]models.Discount{{}})
assert.Equal(t, []models.Discount{{}}, filteredDiscounts)
assert.Equal(t, []models.Discount{{}}, filteredDiscounts) assert.Nil(t, couponDiscount)
assert.Nil(t, couponDiscount) })
}) }
}

@ -1,51 +1,50 @@
package transfer package transfer
import ( import (
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" )
)
func AuditModelToProto(model *models.Audit) *discount.Audit {
func AuditModelToProto(model *models.Audit) *discount.Audit { proto := discount.Audit{}
proto := discount.Audit{}
if model == nil {
if model == nil { return &proto
return &proto }
}
proto.CreatedAt = timestamppb.New(model.CreatedAt)
proto.CreatedAt = timestamppb.New(model.CreatedAt) proto.UpdatedAt = timestamppb.New(model.UpdatedAt)
proto.UpdatedAt = timestamppb.New(model.UpdatedAt)
if model.DeletedAt != nil {
if model.DeletedAt != nil { proto.DeletedAt = timestamppb.New(*model.DeletedAt)
proto.DeletedAt = timestamppb.New(*model.DeletedAt) proto.Deleted = true
proto.Deleted = true }
}
return &proto
return &proto }
}
func AuditProtoToModel(proto *discount.Audit) *models.Audit {
func AuditProtoToModel(proto *discount.Audit) *models.Audit { model := models.Audit{}
model := models.Audit{}
if proto == nil {
if proto == nil { return &model
return &model }
}
if proto.GetCreatedAt() != nil {
if proto.GetCreatedAt() != nil { model.CreatedAt = proto.GetCreatedAt().AsTime()
model.CreatedAt = proto.GetCreatedAt().AsTime() }
}
if proto.GetUpdatedAt() != nil {
if proto.GetUpdatedAt() != nil { model.UpdatedAt = proto.GetUpdatedAt().AsTime()
model.UpdatedAt = proto.GetUpdatedAt().AsTime() }
}
if proto.GetDeletedAt() != nil {
if proto.GetDeletedAt() != nil { deletedAt := proto.GetDeletedAt().AsTime()
deletedAt := proto.GetDeletedAt().AsTime() model.DeletedAt = &deletedAt
model.DeletedAt = &deletedAt }
}
model.Deleted = proto.GetDeleted()
model.Deleted = proto.GetDeleted()
return &model
return &model }
}

@ -1,135 +1,134 @@
package transfer_test package transfer_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer" )
)
func TestAuditModelToProto(t *testing.T) {
func TestAuditModelToProto(t *testing.T) { deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtModel := time.Unix(1674546287, 0).UTC() deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод аудита с пустой модели в прототип", func(t *testing.T) {
t.Run("Перевод аудита с пустой модели в прототип", func(t *testing.T) { assert.Equal(t, &discount.Audit{
assert.Equal(t, &discount.Audit{ CreatedAt: timestamppb.New(time.Time{}),
CreatedAt: timestamppb.New(time.Time{}), UpdatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}), }, transfer.AuditModelToProto(&models.Audit{}))
}, transfer.AuditModelToProto(&models.Audit{})) })
})
t.Run("Перевод аудита с nil модели в прототип", func(t *testing.T) {
t.Run("Перевод аудита с nil модели в прототип", func(t *testing.T) { assert.Equal(t, &discount.Audit{CreatedAt: nil, UpdatedAt: nil}, transfer.AuditModelToProto(nil))
assert.Equal(t, &discount.Audit{CreatedAt: nil, UpdatedAt: nil}, transfer.AuditModelToProto(nil)) })
})
t.Run("Перевод аудита с модели, которая хранит дату удаления, но флаг deleted при этом false в прототип", func(t *testing.T) {
t.Run("Перевод аудита с модели, которая хранит дату удаления, но флаг deleted при этом false в прототип", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &discount.Audit{
&discount.Audit{ UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, DeletedAt: &deletedAtProto,
DeletedAt: &deletedAtProto, Deleted: true,
Deleted: true, CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, },
}, transfer.AuditModelToProto(&models.Audit{
transfer.AuditModelToProto(&models.Audit{ UpdatedAt: time.Unix(1674546287, 0).UTC(),
UpdatedAt: time.Unix(1674546287, 0).UTC(), DeletedAt: &deletedAtModel,
DeletedAt: &deletedAtModel, Deleted: false,
Deleted: false, CreatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(), }),
}), )
) })
})
t.Run("Перевод аудита с модели, которая хранит флаг deleted равный true, но при этом не имеет даты удаления в прототип", func(t *testing.T) {
t.Run("Перевод аудита с модели, которая хранит флаг deleted равный true, но при этом не имеет даты удаления в прототип", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &discount.Audit{
&discount.Audit{ UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, },
}, transfer.AuditModelToProto(&models.Audit{
transfer.AuditModelToProto(&models.Audit{ UpdatedAt: time.Unix(1674546287, 0).UTC(),
UpdatedAt: time.Unix(1674546287, 0).UTC(), Deleted: true,
Deleted: true, CreatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(), }),
}), )
) })
})
t.Run("Перевод аудита с модели без информации об удалении в прототип", func(t *testing.T) {
t.Run("Перевод аудита с модели без информации об удалении в прототип", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &discount.Audit{
&discount.Audit{ UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, },
}, transfer.AuditModelToProto(&models.Audit{
transfer.AuditModelToProto(&models.Audit{ UpdatedAt: time.Unix(1674546287, 0).UTC(),
UpdatedAt: time.Unix(1674546287, 0).UTC(), CreatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(), }),
}), )
) })
})
t.Run("Перевод аудита с модели в прототип", func(t *testing.T) {
t.Run("Перевод аудита с модели в прототип", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &discount.Audit{
&discount.Audit{ UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, DeletedAt: &deletedAtProto,
DeletedAt: &deletedAtProto, Deleted: true,
Deleted: true, CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, },
}, transfer.AuditModelToProto(&models.Audit{
transfer.AuditModelToProto(&models.Audit{ UpdatedAt: time.Unix(1674546287, 0).UTC(),
UpdatedAt: time.Unix(1674546287, 0).UTC(), DeletedAt: &deletedAtModel,
DeletedAt: &deletedAtModel, Deleted: true,
Deleted: true, CreatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(), }),
}), )
) })
}) }
}
func TestAuditProtoToModel(t *testing.T) {
func TestAuditProtoToModel(t *testing.T) { deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtModel := time.Unix(1674546287, 0).UTC() deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод аудита с пустого прототипа в модель", func(t *testing.T) {
t.Run("Перевод аудита с пустого прототипа в модель", func(t *testing.T) { assert.Equal(t, &models.Audit{}, transfer.AuditProtoToModel(&discount.Audit{}))
assert.Equal(t, &models.Audit{}, transfer.AuditProtoToModel(&discount.Audit{})) })
})
t.Run("Перевод аудита с nil прототипа в модель", func(t *testing.T) {
t.Run("Перевод аудита с nil прототипа в модель", func(t *testing.T) { assert.Equal(t, &models.Audit{}, transfer.AuditProtoToModel(nil))
assert.Equal(t, &models.Audit{}, transfer.AuditProtoToModel(nil)) })
})
t.Run("Перевод аудита с прототипа без информации об удалении в модель", func(t *testing.T) {
t.Run("Перевод аудита с прототипа без информации об удалении в модель", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &models.Audit{
&models.Audit{ UpdatedAt: time.Unix(1674546287, 0).UTC(),
UpdatedAt: time.Unix(1674546287, 0).UTC(), CreatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(), Deleted: true,
Deleted: true, },
}, transfer.AuditProtoToModel(&discount.Audit{
transfer.AuditProtoToModel(&discount.Audit{ UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, Deleted: true,
Deleted: true, }),
}), )
) })
})
t.Run("Перевод аудита с прототипа в модель", func(t *testing.T) {
t.Run("Перевод аудита с прототипа в модель", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &models.Audit{
&models.Audit{ UpdatedAt: time.Unix(1674546287, 0).UTC(),
UpdatedAt: time.Unix(1674546287, 0).UTC(), DeletedAt: &deletedAtModel,
DeletedAt: &deletedAtModel, Deleted: true,
Deleted: true, CreatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(), },
}, transfer.AuditProtoToModel(&discount.Audit{
transfer.AuditProtoToModel(&discount.Audit{ UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, DeletedAt: &deletedAtProto,
DeletedAt: &deletedAtProto, Deleted: true,
Deleted: true, CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, }),
}), )
) })
}) }
}

@ -1,33 +1,33 @@
package transfer package transfer
import ( import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults"
) )
func DiscountCalculationTargetProtoToModel(discountTarget *proto.DiscountCalculationTarget) *models.DiscountCalculationTarget { func DiscountCalculationTargetProtoToModel(discountTarget *proto.DiscountCalculationTarget) *models.DiscountCalculationTarget {
if discountTarget == nil { if discountTarget == nil {
return &models.DiscountCalculationTarget{TargetScope: models.TargetSum} return &models.DiscountCalculationTarget{TargetScope: models.TargetSum}
} }
return &models.DiscountCalculationTarget{ return &models.DiscountCalculationTarget{
Products: ProductTargetsProtoToModel(discountTarget.Products), Products: ProductTargetsProtoToModel(discountTarget.Products),
Factor: discountTarget.Factor, Factor: discountTarget.Factor,
TargetScope: models.TargetScopeModelMap[defaults.GetDefaultValue(discountTarget.TargetScope, 0)], TargetScope: models.TargetScopeModelMap[defaults.GetDefaultValue(discountTarget.TargetScope, 0)],
TargetGroup: defaults.GetDefaultValue(discountTarget.TargetGroup, ""), TargetGroup: defaults.GetDefaultValue(discountTarget.TargetGroup, ""),
Overhelm: defaults.GetDefaultValue(discountTarget.Overhelm, false), Overhelm: defaults.GetDefaultValue(discountTarget.Overhelm, false),
} }
} }
func DiscountCalculationTargetModelToProto(discountTarget models.DiscountCalculationTarget) *proto.DiscountCalculationTarget { func DiscountCalculationTargetModelToProto(discountTarget models.DiscountCalculationTarget) *proto.DiscountCalculationTarget {
scope := models.TargetScopeProtoMap[discountTarget.TargetScope] scope := models.TargetScopeProtoMap[discountTarget.TargetScope]
return &proto.DiscountCalculationTarget{ return &proto.DiscountCalculationTarget{
Products: ProductTargetsModelToProto(discountTarget.Products), Products: ProductTargetsModelToProto(discountTarget.Products),
Factor: discountTarget.Factor, Factor: discountTarget.Factor,
TargetScope: &scope, TargetScope: &scope,
TargetGroup: &discountTarget.TargetGroup, TargetGroup: &discountTarget.TargetGroup,
Overhelm: &discountTarget.Overhelm, Overhelm: &discountTarget.Overhelm,
} }
} }

@ -1,93 +1,93 @@
package transfer_test package transfer_test
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
) )
func TestDiscountCalculationTargetProtoToModel(t *testing.T) { func TestDiscountCalculationTargetProtoToModel(t *testing.T) {
t.Run("Перевод цели высчитывания из proto в модель", func(t *testing.T) { t.Run("Перевод цели высчитывания из proto в модель", func(t *testing.T) {
assert.Equal(t, assert.Equal(t,
&models.DiscountCalculationTarget{ &models.DiscountCalculationTarget{
TargetScope: models.TargetSum, TargetScope: models.TargetSum,
Products: []models.ProductTarget{ Products: []models.ProductTarget{
{ID: "1", Factor: 20}, {ID: "1", Factor: 20},
{ID: "2", Factor: 20}, {ID: "2", Factor: 20},
{ID: "3", Factor: 10}, {ID: "3", Factor: 10},
}, },
Factor: 20, Factor: 20,
}, },
transfer.DiscountCalculationTargetProtoToModel(&discount.DiscountCalculationTarget{ transfer.DiscountCalculationTargetProtoToModel(&discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(), TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{ Products: []*discount.ProductTarget{
{ID: "1", Factor: 20}, {ID: "1", Factor: 20},
{ID: "2", Factor: 20}, {ID: "2", Factor: 20},
{ID: "3", Factor: 10}, {ID: "3", Factor: 10},
}, },
Factor: 20, Factor: 20,
}), }),
) )
}) })
t.Run("Перевод цели высчитывания из proto в модель с пустынми данными", func(t *testing.T) { t.Run("Перевод цели высчитывания из proto в модель с пустынми данными", func(t *testing.T) {
assert.Equal(t, assert.Equal(t,
&models.DiscountCalculationTarget{ &models.DiscountCalculationTarget{
Products: []models.ProductTarget{}, Products: []models.ProductTarget{},
TargetScope: models.TargetSum, TargetScope: models.TargetSum,
}, },
transfer.DiscountCalculationTargetProtoToModel(&discount.DiscountCalculationTarget{}), transfer.DiscountCalculationTargetProtoToModel(&discount.DiscountCalculationTarget{}),
) )
}) })
t.Run("Перевод цели высчитывания из proto в модель с nil", func(t *testing.T) { t.Run("Перевод цели высчитывания из proto в модель с nil", func(t *testing.T) {
assert.Equal(t, assert.Equal(t,
&models.DiscountCalculationTarget{TargetScope: models.TargetSum}, &models.DiscountCalculationTarget{TargetScope: models.TargetSum},
transfer.DiscountCalculationTargetProtoToModel(nil), transfer.DiscountCalculationTargetProtoToModel(nil),
) )
}) })
} }
func TestDiscountCalculationTargetModelToProto(t *testing.T) { func TestDiscountCalculationTargetModelToProto(t *testing.T) {
t.Run("Перевод цели высчитывания из модели в proto", func(t *testing.T) { t.Run("Перевод цели высчитывания из модели в proto", func(t *testing.T) {
assert.Equal(t, assert.Equal(t,
&discount.DiscountCalculationTarget{ &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(), TargetScope: discount.TargetScope_Sum.Enum(),
Overhelm: new(bool), Overhelm: new(bool),
TargetGroup: new(string), TargetGroup: new(string),
Products: []*discount.ProductTarget{ Products: []*discount.ProductTarget{
{ID: "1", Factor: 20, Overhelm: new(bool)}, {ID: "1", Factor: 20, Overhelm: new(bool)},
{ID: "2", Factor: 20, Overhelm: new(bool)}, {ID: "2", Factor: 20, Overhelm: new(bool)},
{ID: "3", Factor: 10, Overhelm: new(bool)}, {ID: "3", Factor: 10, Overhelm: new(bool)},
}, },
Factor: 20, Factor: 20,
}, },
transfer.DiscountCalculationTargetModelToProto(models.DiscountCalculationTarget{ transfer.DiscountCalculationTargetModelToProto(models.DiscountCalculationTarget{
TargetScope: models.TargetSum, TargetScope: models.TargetSum,
Products: []models.ProductTarget{ Products: []models.ProductTarget{
{ID: "1", Factor: 20}, {ID: "1", Factor: 20},
{ID: "2", Factor: 20}, {ID: "2", Factor: 20},
{ID: "3", Factor: 10}, {ID: "3", Factor: 10},
}, },
Factor: 20, Factor: 20,
}), }),
) )
}) })
t.Run("Перевод цели высчитывания из модели в proto с пустыми значениями", func(t *testing.T) { t.Run("Перевод цели высчитывания из модели в proto с пустыми значениями", func(t *testing.T) {
assert.Equal(t, assert.Equal(t,
&discount.DiscountCalculationTarget{ &discount.DiscountCalculationTarget{
TargetScope: discount.TargetScope_Sum.Enum(), TargetScope: discount.TargetScope_Sum.Enum(),
Products: []*discount.ProductTarget{}, Products: []*discount.ProductTarget{},
Overhelm: new(bool), Overhelm: new(bool),
TargetGroup: new(string), TargetGroup: new(string),
}, },
transfer.DiscountCalculationTargetModelToProto(models.DiscountCalculationTarget{ transfer.DiscountCalculationTargetModelToProto(models.DiscountCalculationTarget{
Products: []models.ProductTarget{}, Products: []models.ProductTarget{},
}), }),
) )
}) })
} }

@ -1,46 +1,46 @@
package transfer package transfer
import ( import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
) )
func DiscountConditionModelToProto(discountCondition *models.DiscountCondition) *proto.DiscountCondition { func DiscountConditionModelToProto(discountCondition *models.DiscountCondition) *proto.DiscountCondition {
if discountCondition == nil { if discountCondition == nil {
return nil return nil
} }
return &proto.DiscountCondition{ return &proto.DiscountCondition{
Period: PeriodModelToProto(discountCondition.Period), Period: PeriodModelToProto(discountCondition.Period),
User: discountCondition.User, User: discountCondition.User,
UserType: discountCondition.UserType, UserType: discountCondition.UserType,
Coupon: discountCondition.Coupon, Coupon: discountCondition.Coupon,
PurchasesAmount: discountCondition.PurchasesAmount, PurchasesAmount: discountCondition.PurchasesAmount,
CartPurchasesAmount: discountCondition.CartPurchasesAmount, CartPurchasesAmount: discountCondition.CartPurchasesAmount,
Term: discountCondition.Term, Term: discountCondition.Term,
Product: discountCondition.Product, Product: discountCondition.Product,
Usage: discountCondition.Usage, Usage: discountCondition.Usage,
PriceFrom: discountCondition.PriceFrom, PriceFrom: discountCondition.PriceFrom,
Group: discountCondition.Group, Group: discountCondition.Group,
} }
} }
func DiscountConditionProtoToModel(discountCondition *proto.DiscountCondition) *models.DiscountCondition { func DiscountConditionProtoToModel(discountCondition *proto.DiscountCondition) *models.DiscountCondition {
if discountCondition == nil { if discountCondition == nil {
return &models.DiscountCondition{} return &models.DiscountCondition{}
} }
return &models.DiscountCondition{ return &models.DiscountCondition{
Period: PeriodProtoToModel(discountCondition.GetPeriod()), Period: PeriodProtoToModel(discountCondition.GetPeriod()),
Product: discountCondition.Product, Product: discountCondition.Product,
User: discountCondition.User, User: discountCondition.User,
UserType: discountCondition.UserType, UserType: discountCondition.UserType,
Coupon: discountCondition.Coupon, Coupon: discountCondition.Coupon,
PurchasesAmount: discountCondition.PurchasesAmount, PurchasesAmount: discountCondition.PurchasesAmount,
CartPurchasesAmount: discountCondition.CartPurchasesAmount, CartPurchasesAmount: discountCondition.CartPurchasesAmount,
Term: discountCondition.Term, Term: discountCondition.Term,
Usage: discountCondition.Usage, Usage: discountCondition.Usage,
PriceFrom: discountCondition.PriceFrom, PriceFrom: discountCondition.PriceFrom,
Group: discountCondition.Group, Group: discountCondition.Group,
} }
} }

@ -1,142 +1,141 @@
package transfer_test package transfer_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer" )
)
func TestDiscountConditionProtoToModel(t *testing.T) {
func TestDiscountConditionProtoToModel(t *testing.T) { userID := "1"
userID := "1" userType := "nkvo11"
userType := "nkvo" coupon := "test22"
coupon := "test" product := "product13"
product := "product1" purchasesAmount := uint64(20000)
purchasesAmount := float64(20000) cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000) term := uint64(20)
term := uint64(20) usage := uint64(20)
usage := uint64(20) priceFrom := uint64(20000)
priceFrom := float64(20000) group := "testGroupo"
group := "testGroup"
t.Run("Перевод не до конца заполненных условий скидок с proto в модель", func(t *testing.T) {
t.Run("Перевод не до конца заполненных условий скидок с proto в модель", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &models.DiscountCondition{CartPurchasesAmount: &cartPurchasesAmount},
&models.DiscountCondition{CartPurchasesAmount: &cartPurchasesAmount}, transfer.DiscountConditionProtoToModel(&discount.DiscountCondition{
transfer.DiscountConditionProtoToModel(&discount.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, }),
}), )
) })
})
t.Run("Перевод скидок с nil proto в модель", func(t *testing.T) {
t.Run("Перевод скидок с nil proto в модель", func(t *testing.T) { assert.Equal(t, &models.DiscountCondition{}, transfer.DiscountConditionProtoToModel(nil))
assert.Equal(t, &models.DiscountCondition{}, transfer.DiscountConditionProtoToModel(nil)) })
})
t.Run("Перевод условий скидок с proto в модель", func(t *testing.T) {
t.Run("Перевод условий скидок с proto в модель", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &models.DiscountCondition{
&models.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, User: &userID,
User: &userID, UserType: &userType,
UserType: &userType, PurchasesAmount: &purchasesAmount,
PurchasesAmount: &purchasesAmount, Coupon: &coupon,
Coupon: &coupon, Product: &product,
Product: &product, Term: &term,
Term: &term, Usage: &usage,
Usage: &usage, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, Group: &group,
Group: &group, Period: &models.PeriodCondition{
Period: &models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, },
}, transfer.DiscountConditionProtoToModel(&discount.DiscountCondition{
transfer.DiscountConditionProtoToModel(&discount.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, User: &userID,
User: &userID, UserType: &userType,
UserType: &userType, PurchasesAmount: &purchasesAmount,
PurchasesAmount: &purchasesAmount, Coupon: &coupon,
Coupon: &coupon, Product: &product,
Product: &product, Term: &term,
Term: &term, Usage: &usage,
Usage: &usage, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, Group: &group,
Group: &group, Period: &discount.PeriodCondition{
Period: &discount.PeriodCondition{ From: &timestamppb.Timestamp{
From: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, To: &timestamppb.Timestamp{
To: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, },
}, }),
}), )
) })
}) }
}
func TestDiscountConditionModelToProto(t *testing.T) {
func TestDiscountConditionModelToProto(t *testing.T) { userID := "1"
userID := "1" userType := "nkvo"
userType := "nkvo" coupon := "test"
coupon := "test" product := "product1"
product := "product1" purchasesAmount := uint64(20000)
purchasesAmount := float64(20000) cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000) term := uint64(20)
term := uint64(20) usage := uint64(20)
usage := uint64(20) priceFrom := uint64(20000)
priceFrom := float64(20000) group := "testGroupbgfbgbgb"
group := "testGroup"
t.Run("Перевод условий скидки с модели в proto", func(t *testing.T) {
t.Run("Перевод условий скидки с модели в proto", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &discount.DiscountCondition{
&discount.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, User: &userID,
User: &userID, UserType: &userType,
UserType: &userType, PurchasesAmount: &purchasesAmount,
PurchasesAmount: &purchasesAmount, Coupon: &coupon,
Coupon: &coupon, Product: &product,
Product: &product, Term: &term,
Term: &term, Usage: &usage,
Usage: &usage, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, Group: &group,
Group: &group, Period: &discount.PeriodCondition{
Period: &discount.PeriodCondition{ From: &timestamppb.Timestamp{
From: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, To: &timestamppb.Timestamp{
To: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, },
}, },
}, transfer.DiscountConditionModelToProto(&models.DiscountCondition{
transfer.DiscountConditionModelToProto(&models.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, User: &userID,
User: &userID, UserType: &userType,
UserType: &userType, PurchasesAmount: &purchasesAmount,
PurchasesAmount: &purchasesAmount, Coupon: &coupon,
Coupon: &coupon, Product: &product,
Product: &product, Term: &term,
Term: &term, Usage: &usage,
Usage: &usage, PriceFrom: &priceFrom,
PriceFrom: &priceFrom, Group: &group,
Group: &group, Period: &models.PeriodCondition{
Period: &models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, }),
}), )
) })
})
t.Run("Перевод условий скидки с nil модели в proto", func(t *testing.T) {
t.Run("Перевод условий скидки с nil модели в proto", func(t *testing.T) { assert.Nil(t, transfer.DiscountConditionModelToProto(nil))
assert.Nil(t, transfer.DiscountConditionModelToProto(nil)) })
}) }
}

@ -1,60 +1,60 @@
package transfer package transfer
import ( import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
) )
func DiscountsProtoToModel(discounts *proto.Discounts) []*models.Discount { func DiscountsProtoToModel(discounts *proto.Discounts) []*models.Discount {
if discounts == nil { if discounts == nil {
return []*models.Discount{} return []*models.Discount{}
} }
modelDiscounts := make([]*models.Discount, len(discounts.Discounts)) modelDiscounts := make([]*models.Discount, len(discounts.Discounts))
for index, discount := range discounts.Discounts { for index, discount := range discounts.Discounts {
modelDiscounts[index] = DiscountProtoToModel(discount) modelDiscounts[index] = DiscountProtoToModel(discount)
} }
return modelDiscounts return modelDiscounts
} }
func DiscountProtoToModel(discount *proto.Discount) *models.Discount { func DiscountProtoToModel(discount *proto.Discount) *models.Discount {
if discount == nil { if discount == nil {
return nil return nil
} }
return &models.Discount{ return &models.Discount{
ID: discount.GetID(), ID: discount.GetID(),
Name: discount.GetName(), Name: discount.GetName(),
Layer: discount.GetLayer(), Layer: discount.GetLayer(),
Description: discount.GetDescription(), Description: discount.GetDescription(),
Condition: *DiscountConditionProtoToModel(discount.GetCondition()), Condition: *DiscountConditionProtoToModel(discount.GetCondition()),
Target: *DiscountCalculationTargetProtoToModel(discount.GetTarget()), Target: *DiscountCalculationTargetProtoToModel(discount.GetTarget()),
Audit: *AuditProtoToModel(discount.GetAudit()), Audit: *AuditProtoToModel(discount.GetAudit()),
} }
} }
func DiscountsModelToProto(discounts []models.Discount) *proto.Discounts { func DiscountsModelToProto(discounts []models.Discount) *proto.Discounts {
protoDiscounts := make([]*proto.Discount, len(discounts)) protoDiscounts := make([]*proto.Discount, len(discounts))
for index, discount := range discounts { for index, discount := range discounts {
protoDiscounts[index] = DiscountModelToProto(discount) protoDiscounts[index] = DiscountModelToProto(discount)
} }
return &proto.Discounts{ return &proto.Discounts{
Discounts: protoDiscounts, Discounts: protoDiscounts,
} }
} }
func DiscountModelToProto(discount models.Discount) *proto.Discount { func DiscountModelToProto(discount models.Discount) *proto.Discount {
return &proto.Discount{ return &proto.Discount{
ID: discount.ID, ID: discount.ID,
Name: discount.Name, Name: discount.Name,
Layer: discount.Layer, Layer: discount.Layer,
Description: discount.Description, Description: discount.Description,
Condition: DiscountConditionModelToProto(&discount.Condition), Condition: DiscountConditionModelToProto(&discount.Condition),
Target: DiscountCalculationTargetModelToProto(discount.Target), Target: DiscountCalculationTargetModelToProto(discount.Target),
Audit: AuditModelToProto(&discount.Audit), Audit: AuditModelToProto(&discount.Audit),
} }
} }

@ -1,243 +1,242 @@
package transfer_test package transfer_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer" )
)
func TestDiscountsProtoToModel(t *testing.T) {
func TestDiscountsProtoToModel(t *testing.T) { cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000) purchasesAmount := uint64(20000)
purchasesAmount := float64(20000) userID := string("13")
userID := string("13") userType := string("type")
userType := string("type") coupon := string("coupon1")
coupon := string("coupon1") product := string("product1")
product := string("product1") term := uint64(20)
term := uint64(20)
t.Run("перевод массива скидок с proto в модель с nil", func(t *testing.T) {
t.Run("перевод массива скидок с proto в модель с nil", func(t *testing.T) { assert.Equal(t, []*models.Discount{}, transfer.DiscountsProtoToModel(nil))
assert.Equal(t, []*models.Discount{}, transfer.DiscountsProtoToModel(nil)) })
})
t.Run("перевод массива скидок с proto в модель", func(t *testing.T) {
t.Run("перевод массива скидок с proto в модель", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, []*models.Discount{
[]*models.Discount{ {
{ ID: "1",
ID: "1", Name: "Скидка на объём в корзине 1",
Name: "Скидка на объём в корзине 1", Layer: 8,
Layer: 8, Description: "",
Description: "", Condition: models.DiscountCondition{
Condition: models.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, PurchasesAmount: &purchasesAmount,
PurchasesAmount: &purchasesAmount, User: &userID,
User: &userID, UserType: &userType,
UserType: &userType, Coupon: &coupon,
Coupon: &coupon, Product: &product,
Product: &product, Term: &term,
Term: &term, Period: &models.PeriodCondition{
Period: &models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(0, 0).UTC(),
To: time.Unix(0, 0).UTC(), },
}, },
}, Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Products: []models.ProductTarget{},
Products: []models.ProductTarget{}, Factor: 20,
Factor: 20, },
}, },
}, },
}, transfer.DiscountsProtoToModel(&discount.Discounts{
transfer.DiscountsProtoToModel(&discount.Discounts{ Discounts: []*discount.Discount{
Discounts: []*discount.Discount{ {
{ ID: "1",
ID: "1", Name: "Скидка на объём в корзине 1",
Name: "Скидка на объём в корзине 1", Layer: 8,
Layer: 8, Description: "",
Description: "", Condition: &discount.DiscountCondition{
Condition: &discount.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, PurchasesAmount: &purchasesAmount,
PurchasesAmount: &purchasesAmount, User: &userID,
User: &userID, UserType: &userType,
UserType: &userType, Coupon: &coupon,
Coupon: &coupon, Product: &product,
Product: &product, Term: &term,
Term: &term, Period: &discount.PeriodCondition{
Period: &discount.PeriodCondition{ From: &timestamppb.Timestamp{
From: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, },
}, To: &timestamppb.Timestamp{},
To: &timestamppb.Timestamp{}, },
}, },
}, Target: &discount.DiscountCalculationTarget{
Target: &discount.DiscountCalculationTarget{ TargetScope: discount.TargetScope_Sum.Enum(),
TargetScope: discount.TargetScope_Sum.Enum(), Products: []*discount.ProductTarget{},
Products: []*discount.ProductTarget{}, Factor: 20,
Factor: 20, },
}, },
}, },
}, }),
}), )
) })
}) }
}
func TestDiscountProtoToModel(t *testing.T) {
func TestDiscountProtoToModel(t *testing.T) { cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000) deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtModel := time.Unix(1674546287, 0).UTC() deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод скидки с proto в модель", func(t *testing.T) {
t.Run("Перевод скидки с proto в модель", func(t *testing.T) { assert.Nil(t, transfer.DiscountProtoToModel(nil))
assert.Nil(t, transfer.DiscountProtoToModel(nil)) })
})
t.Run("Перевод скидки с proto в модель", func(t *testing.T) {
t.Run("Перевод скидки с proto в модель", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &models.Discount{
&models.Discount{ ID: "1",
ID: "1", Name: "Скидка на объём в корзине 4",
Name: "Скидка на объём в корзине 4", Layer: 8,
Layer: 8, Description: "",
Description: "", Condition: models.DiscountCondition{
Condition: models.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, },
}, Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Products: []models.ProductTarget{},
Products: []models.ProductTarget{}, Factor: 20,
Factor: 20, },
}, Audit: models.Audit{
Audit: models.Audit{ UpdatedAt: time.Unix(1674546287, 0).UTC(),
UpdatedAt: time.Unix(1674546287, 0).UTC(), DeletedAt: &deletedAtModel,
DeletedAt: &deletedAtModel, Deleted: true,
Deleted: true, CreatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(), },
}, },
}, transfer.DiscountProtoToModel(&discount.Discount{
transfer.DiscountProtoToModel(&discount.Discount{ ID: "1",
ID: "1", Name: "Скидка на объём в корзине 4",
Name: "Скидка на объём в корзине 4", Layer: 8,
Layer: 8, Description: "",
Description: "", Condition: &discount.DiscountCondition{
Condition: &discount.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, },
}, Target: &discount.DiscountCalculationTarget{
Target: &discount.DiscountCalculationTarget{ TargetScope: discount.TargetScope_Sum.Enum(),
TargetScope: discount.TargetScope_Sum.Enum(), Products: []*discount.ProductTarget{},
Products: []*discount.ProductTarget{}, Factor: 20,
Factor: 20, },
}, Audit: &discount.Audit{
Audit: &discount.Audit{ UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, DeletedAt: &deletedAtProto,
DeletedAt: &deletedAtProto, Deleted: true,
Deleted: true, CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, },
}, }),
}), )
) })
}) }
}
func TestDiscountsModelToProto(t *testing.T) {
func TestDiscountsModelToProto(t *testing.T) { cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000) deletedAtModel := time.Unix(1674546287, 0).UTC()
deletedAtModel := time.Unix(1674546287, 0).UTC() deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
deletedAtProto := timestamppb.Timestamp{Seconds: 1674546287}
t.Run("Перевод массива скидок из модели в proto", func(t *testing.T) {
t.Run("Перевод массива скидок из модели в proto", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &discount.Discounts{Discounts: []*discount.Discount{
&discount.Discounts{Discounts: []*discount.Discount{ {
{ ID: "1",
ID: "1", Name: "Скидка на объём в корзине 5",
Name: "Скидка на объём в корзине 5", Layer: 8,
Layer: 8, Description: "",
Description: "", Condition: &discount.DiscountCondition{
Condition: &discount.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, },
}, Target: &discount.DiscountCalculationTarget{
Target: &discount.DiscountCalculationTarget{ TargetScope: discount.TargetScope_Sum.Enum(),
TargetScope: discount.TargetScope_Sum.Enum(), Products: []*discount.ProductTarget{},
Products: []*discount.ProductTarget{}, Factor: 20,
Factor: 20, Overhelm: new(bool),
Overhelm: new(bool), TargetGroup: new(string),
TargetGroup: new(string), },
}, Audit: &discount.Audit{
Audit: &discount.Audit{ UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
UpdatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, DeletedAt: &deletedAtProto,
DeletedAt: &deletedAtProto, Deleted: true,
Deleted: true, CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287},
CreatedAt: &timestamppb.Timestamp{Seconds: 1674546287}, },
}, },
}, }},
}}, transfer.DiscountsModelToProto([]models.Discount{
transfer.DiscountsModelToProto([]models.Discount{ {
{ ID: "1",
ID: "1", Name: "Скидка на объём в корзине 5",
Name: "Скидка на объём в корзине 5", Layer: 8,
Layer: 8, Description: "",
Description: "", Condition: models.DiscountCondition{
Condition: models.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, },
}, Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Products: []models.ProductTarget{},
Products: []models.ProductTarget{}, Factor: 20,
Factor: 20, },
}, Audit: models.Audit{
Audit: models.Audit{ UpdatedAt: time.Unix(1674546287, 0).UTC(),
UpdatedAt: time.Unix(1674546287, 0).UTC(), DeletedAt: &deletedAtModel,
DeletedAt: &deletedAtModel, Deleted: true,
Deleted: true, CreatedAt: time.Unix(1674546287, 0).UTC(),
CreatedAt: time.Unix(1674546287, 0).UTC(), },
}, },
}, }),
}), )
) })
}) }
}
func TestDiscountModelToProto(t *testing.T) {
func TestDiscountModelToProto(t *testing.T) { cartPurchasesAmount := uint64(20000)
cartPurchasesAmount := float64(20000)
t.Run("Перевод скидки из модели в proto", func(t *testing.T) {
t.Run("Перевод скидки из модели в proto", func(t *testing.T) { assert.Equal(t,
assert.Equal(t, &discount.Discount{
&discount.Discount{ ID: "1",
ID: "1", Name: "Скидка на объём в корзине 8",
Name: "Скидка на объём в корзине 8", Layer: 8,
Layer: 8, Description: "",
Description: "", Condition: &discount.DiscountCondition{
Condition: &discount.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, },
}, Target: &discount.DiscountCalculationTarget{
Target: &discount.DiscountCalculationTarget{ TargetScope: discount.TargetScope_Sum.Enum(),
TargetScope: discount.TargetScope_Sum.Enum(), Products: []*discount.ProductTarget{},
Products: []*discount.ProductTarget{}, Factor: 20,
Factor: 20, Overhelm: new(bool),
Overhelm: new(bool), TargetGroup: new(string),
TargetGroup: new(string), },
}, Audit: &discount.Audit{
Audit: &discount.Audit{ CreatedAt: timestamppb.New(time.Time{}),
CreatedAt: timestamppb.New(time.Time{}), UpdatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}), },
}, },
}, transfer.DiscountModelToProto(models.Discount{
transfer.DiscountModelToProto(models.Discount{ ID: "1",
ID: "1", Name: "Скидка на объём в корзине 8",
Name: "Скидка на объём в корзине 8", Layer: 8,
Layer: 8, Description: "",
Description: "", Condition: models.DiscountCondition{
Condition: models.DiscountCondition{ CartPurchasesAmount: &cartPurchasesAmount,
CartPurchasesAmount: &cartPurchasesAmount, },
}, Target: models.DiscountCalculationTarget{
Target: models.DiscountCalculationTarget{ TargetScope: models.TargetSum,
TargetScope: models.TargetSum, Products: []models.ProductTarget{},
Products: []models.ProductTarget{}, Factor: 20,
Factor: 20, },
}, }),
}), )
) })
}) }
}

@ -1,30 +1,29 @@
package transfer package transfer
import ( import (
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" )
)
func PeriodModelToProto(perion *models.PeriodCondition) *proto.PeriodCondition {
func PeriodModelToProto(perion *models.PeriodCondition) *proto.PeriodCondition { if perion == nil {
if perion == nil { return nil
return nil }
}
return &proto.PeriodCondition{
return &proto.PeriodCondition{ From: timestamppb.New(perion.From),
From: timestamppb.New(perion.From), To: timestamppb.New(perion.To),
To: timestamppb.New(perion.To), }
} }
}
func PeriodProtoToModel(perion *proto.PeriodCondition) *models.PeriodCondition {
func PeriodProtoToModel(perion *proto.PeriodCondition) *models.PeriodCondition { if perion == nil {
if perion == nil { return nil
return nil }
}
return &models.PeriodCondition{
return &models.PeriodCondition{ From: perion.GetFrom().AsTime(),
From: perion.GetFrom().AsTime(), To: perion.GetTo().AsTime(),
To: perion.GetTo().AsTime(), }
} }
}

@ -1,129 +1,128 @@
package transfer_test package transfer_test
import ( import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer" )
)
/*
/* TODO:
TODO:
Переписать тесты с testCases в обычный список t.Run тестов
Переписать тесты с testCases в обычный список t.Run тестов */
*/
func TestPeriodProtoToModel(t *testing.T) {
func TestPeriodProtoToModel(t *testing.T) { testCases := []struct {
testCases := []struct { name string
name string proto *discount.PeriodCondition
proto *discount.PeriodCondition model *models.PeriodCondition
model *models.PeriodCondition }{
}{ {
{ name: "correct transfer period from proto to model",
name: "correct transfer period from proto to model", proto: &discount.PeriodCondition{
proto: &discount.PeriodCondition{ From: &timestamppb.Timestamp{
From: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, To: &timestamppb.Timestamp{
To: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, },
}, model: &models.PeriodCondition{
model: &models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, },
}, {
{ name: "correct transfer period with empty milliseconds from proto to model",
name: "correct transfer period with empty milliseconds from proto to model", proto: &discount.PeriodCondition{
proto: &discount.PeriodCondition{ From: &timestamppb.Timestamp{
From: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, },
}, To: &timestamppb.Timestamp{},
To: &timestamppb.Timestamp{}, },
}, model: &models.PeriodCondition{
model: &models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(0, 0).UTC(),
To: time.Unix(0, 0).UTC(), },
}, },
}, {
{ name: "correct transfer period with empty fields from proto to model",
name: "correct transfer period with empty fields from proto to model", proto: &discount.PeriodCondition{
proto: &discount.PeriodCondition{ From: &timestamppb.Timestamp{},
From: &timestamppb.Timestamp{}, },
}, model: &models.PeriodCondition{
model: &models.PeriodCondition{ From: time.Unix(0, 0).UTC(),
From: time.Unix(0, 0).UTC(), To: time.Unix(0, 0).UTC(),
To: time.Unix(0, 0).UTC(), },
}, },
}, }
}
for _, testCase := range testCases {
for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) { assert.Equal(t, testCase.model, transfer.PeriodProtoToModel(testCase.proto))
assert.Equal(t, testCase.model, transfer.PeriodProtoToModel(testCase.proto)) })
}) }
} }
}
func TestPeriodModelToProto(t *testing.T) {
func TestPeriodModelToProto(t *testing.T) { testCases := []struct {
testCases := []struct { name string
name string proto *discount.PeriodCondition
proto *discount.PeriodCondition model *models.PeriodCondition
model *models.PeriodCondition }{
}{ {
{ name: "correct transfer period from model to proto",
name: "correct transfer period from model to proto", proto: &discount.PeriodCondition{
proto: &discount.PeriodCondition{ From: &timestamppb.Timestamp{
From: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, To: &timestamppb.Timestamp{
To: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, Nanos: 0,
Nanos: 0, },
}, },
}, model: &models.PeriodCondition{
model: &models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(1674546287, 0).UTC(),
To: time.Unix(1674546287, 0).UTC(), },
}, },
}, {
{ name: "correct transfer period with empty milliseconds from model to proto",
name: "correct transfer period with empty milliseconds from model to proto", proto: &discount.PeriodCondition{
proto: &discount.PeriodCondition{ From: &timestamppb.Timestamp{
From: &timestamppb.Timestamp{ Seconds: 1674546287,
Seconds: 1674546287, },
}, To: &timestamppb.Timestamp{},
To: &timestamppb.Timestamp{}, },
}, model: &models.PeriodCondition{
model: &models.PeriodCondition{ From: time.Unix(1674546287, 0).UTC(),
From: time.Unix(1674546287, 0).UTC(), To: time.Unix(0, 0).UTC(),
To: time.Unix(0, 0).UTC(), },
}, },
}, {
{ name: "correct transfer period with empty fields from model to proto",
name: "correct transfer period with empty fields from model to proto", proto: &discount.PeriodCondition{
proto: &discount.PeriodCondition{ From: &timestamppb.Timestamp{},
From: &timestamppb.Timestamp{}, To: timestamppb.New(time.Time{}),
To: timestamppb.New(time.Time{}), },
}, model: &models.PeriodCondition{
model: &models.PeriodCondition{ From: time.Unix(0, 0).UTC(),
From: time.Unix(0, 0).UTC(), },
}, },
}, }
}
for _, testCase := range testCases {
for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) { assert.Equal(t, testCase.proto, transfer.PeriodModelToProto(testCase.model))
assert.Equal(t, testCase.proto, transfer.PeriodModelToProto(testCase.model)) })
}) }
} }
}

@ -1,85 +1,85 @@
package transfer package transfer
import ( import (
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" proto "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
) )
func ProductTargetsProtoToModel(products []*proto.ProductTarget) []models.ProductTarget { func ProductTargetsProtoToModel(products []*proto.ProductTarget) []models.ProductTarget {
modelProducts := make([]models.ProductTarget, len(products)) modelProducts := make([]models.ProductTarget, len(products))
for index, product := range products { for index, product := range products {
modelProducts[index] = *ProductTargetProtoToModel(product) modelProducts[index] = *ProductTargetProtoToModel(product)
} }
return modelProducts return modelProducts
} }
func ProductTargetProtoToModel(product *proto.ProductTarget) *models.ProductTarget { func ProductTargetProtoToModel(product *proto.ProductTarget) *models.ProductTarget {
if product == nil { if product == nil {
return nil return nil
} }
receiveOverhelm := func() bool { receiveOverhelm := func() bool {
if product.Overhelm != nil { if product.Overhelm != nil {
return *product.Overhelm return *product.Overhelm
} }
return false return false
} }
return &models.ProductTarget{ return &models.ProductTarget{
ID: product.ID, ID: product.ID,
Factor: product.Factor, Factor: product.Factor,
Overhelm: receiveOverhelm(), Overhelm: receiveOverhelm(),
} }
} }
func ProductTargetsModelToProto(products []models.ProductTarget) []*proto.ProductTarget { func ProductTargetsModelToProto(products []models.ProductTarget) []*proto.ProductTarget {
protoProducts := make([]*proto.ProductTarget, len(products)) protoProducts := make([]*proto.ProductTarget, len(products))
for index, product := range products { for index, product := range products {
protoProducts[index] = ProductTargetModelToProto(product) protoProducts[index] = ProductTargetModelToProto(product)
} }
return protoProducts return protoProducts
} }
func ProductTargetModelToProto(product models.ProductTarget) *proto.ProductTarget { func ProductTargetModelToProto(product models.ProductTarget) *proto.ProductTarget {
return &proto.ProductTarget{ return &proto.ProductTarget{
ID: product.ID, ID: product.ID,
Factor: product.Factor, Factor: product.Factor,
Overhelm: &product.Overhelm, Overhelm: &product.Overhelm,
} }
} }
func ProductsProtoToCore(productInformations []*proto.ProductInformation) []core.Product { func ProductsProtoToCore(productInformations []*proto.ProductInformation) []core.Product {
products := make([]core.Product, len(productInformations)) products := make([]core.Product, len(productInformations))
if len(productInformations) < 1 { if len(productInformations) < 1 {
return []core.Product{} return []core.Product{}
} }
receiveGroup := func(product *proto.ProductInformation) string { receiveGroup := func(product *proto.ProductInformation) string {
if product == nil || product.Group == nil { if product == nil || product.Group == nil {
return "" return ""
} }
return *product.Group return *product.Group
} }
for index, product := range productInformations { for index, product := range productInformations {
if product == nil { if product == nil {
continue continue
} }
products[index] = core.Product{ products[index] = core.Product{
ID: product.ID, ID: product.ID,
Price: product.Price, Price: float64(product.Price),
Group: receiveGroup(product), Group: receiveGroup(product),
} }
} }
return products return products
} }

@ -1,241 +1,240 @@
package transfer_test package transfer_test
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/core" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/models" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/proto/discount" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/internal/utils/transfer" )
)
/*
/* TODO:
TODO:
1) Переписать тесты с testCases в обычный список t.Run тестов
1) Переписать тесты с testCases в обычный список t.Run тестов 2) Отформатировать массив объектов
2) Отформатировать массив объектов 3) Убрать глобальные переменные касающиеся других пакетов
3) Убрать глобальные переменные касающиеся других пакетов */
*/
var (
var ( overhelmTrue = true
overhelmTrue = true )
)
func TestProductTargetsProtoToModel(t *testing.T) {
func TestProductTargetsProtoToModel(t *testing.T) { testName := "correct transfer product tagrets from proto to model"
testName := "correct transfer product tagrets from proto to model" proto := []*discount.ProductTarget{
proto := []*discount.ProductTarget{ {
{ ID: "1",
ID: "1", Factor: 20,
Factor: 20, },
}, {
{ ID: "2",
ID: "2", Factor: 20,
Factor: 20, Overhelm: &overhelmTrue,
Overhelm: &overhelmTrue, },
}, {
{ ID: "3",
ID: "3", Factor: 10,
Factor: 10, },
}, }
} model := []models.ProductTarget{
model := []models.ProductTarget{ {
{ ID: "1",
ID: "1", Factor: 20,
Factor: 20, },
}, {
{ ID: "2",
ID: "2", Factor: 20,
Factor: 20, Overhelm: true,
Overhelm: true, },
}, {
{ ID: "3",
ID: "3", Factor: 10,
Factor: 10, },
}, }
}
t.Run(testName, func(t *testing.T) {
t.Run(testName, func(t *testing.T) { assert.Equal(t, model, transfer.ProductTargetsProtoToModel(proto))
assert.Equal(t, model, transfer.ProductTargetsProtoToModel(proto)) })
}) }
}
func TestProductTargetProtoToModel(t *testing.T) {
func TestProductTargetProtoToModel(t *testing.T) { testCases := []struct {
testCases := []struct { name string
name string proto *discount.ProductTarget
proto *discount.ProductTarget model *models.ProductTarget
model *models.ProductTarget }{
}{ {
{ name: "correct transfer product tagret from proto to model",
name: "correct transfer product tagret from proto to model", proto: &discount.ProductTarget{
proto: &discount.ProductTarget{ ID: "1",
ID: "1", Factor: 20,
Factor: 20, },
}, model: &models.ProductTarget{
model: &models.ProductTarget{ ID: "1",
ID: "1", Factor: 20,
Factor: 20, },
}, },
}, {
{ name: "correct transfer nil product tagret from proto to model",
name: "correct transfer nil product tagret from proto to model", proto: nil,
proto: nil, model: nil,
model: nil, },
}, }
}
for _, testCase := range testCases {
for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) { assert.Equal(t, testCase.model, transfer.ProductTargetProtoToModel(testCase.proto))
assert.Equal(t, testCase.model, transfer.ProductTargetProtoToModel(testCase.proto)) })
}) }
} }
}
func TestProductTargetsModelToProto(t *testing.T) {
func TestProductTargetsModelToProto(t *testing.T) { testName := "correct transfer product tagrets from model to proto"
testName := "correct transfer product tagrets from model to proto" proto := []*discount.ProductTarget{
proto := []*discount.ProductTarget{ {
{ ID: "1",
ID: "1", Factor: 50,
Factor: 50, Overhelm: &overhelmTrue,
Overhelm: &overhelmTrue, },
}, {
{ ID: "2",
ID: "2", Factor: 20,
Factor: 20, Overhelm: new(bool),
Overhelm: new(bool), },
}, {
{ ID: "3",
ID: "3", Factor: 10,
Factor: 10, Overhelm: new(bool),
Overhelm: new(bool), },
}, }
} model := []models.ProductTarget{
model := []models.ProductTarget{ {
{ ID: "1",
ID: "1", Factor: 50,
Factor: 50, Overhelm: true,
Overhelm: true, },
}, {
{ ID: "2",
ID: "2", Factor: 20,
Factor: 20, },
}, {
{ ID: "3",
ID: "3", Factor: 10,
Factor: 10, },
}, }
}
t.Run(testName, func(t *testing.T) {
t.Run(testName, func(t *testing.T) { assert.Equal(t, proto, transfer.ProductTargetsModelToProto(model))
assert.Equal(t, proto, transfer.ProductTargetsModelToProto(model)) })
}) }
}
func TestProductTargetModelToProto(t *testing.T) {
func TestProductTargetModelToProto(t *testing.T) { testCases := []struct {
testCases := []struct { name string
name string proto *discount.ProductTarget
proto *discount.ProductTarget model models.ProductTarget
model models.ProductTarget }{
}{ {
{ name: "correct transfer product tagret from model to proto",
name: "correct transfer product tagret from model to proto", proto: &discount.ProductTarget{
proto: &discount.ProductTarget{ ID: "1",
ID: "1", Factor: 20,
Factor: 20, Overhelm: new(bool),
Overhelm: new(bool), },
}, model: models.ProductTarget{
model: models.ProductTarget{ ID: "1",
ID: "1", Factor: 20,
Factor: 20, },
}, },
}, {
{ name: "correct transfer nil product tagret from model to proto",
name: "correct transfer nil product tagret from model to proto", proto: &discount.ProductTarget{
proto: &discount.ProductTarget{ Overhelm: new(bool),
Overhelm: new(bool), },
}, model: models.ProductTarget{},
model: models.ProductTarget{}, },
}, }
}
for _, testCase := range testCases {
for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) { assert.Equal(t, testCase.proto, transfer.ProductTargetModelToProto(testCase.model))
assert.Equal(t, testCase.proto, transfer.ProductTargetModelToProto(testCase.model)) })
}) }
} }
}
func TestProductsProtoToCore(t *testing.T) {
func TestProductsProtoToCore(t *testing.T) { group := "testGroup"
group := "testGroup"
testCases := []struct {
testCases := []struct { name string
name string proto []*discount.ProductInformation
proto []*discount.ProductInformation core []core.Product
core []core.Product }{
}{ {
{ name: "transfer product informations from proto to core",
name: "transfer product informations from proto to core", proto: []*discount.ProductInformation{
proto: []*discount.ProductInformation{ {
{ ID: "1",
ID: "1", Price: 20,
Price: 20, Group: &group,
Group: &group, },
}, {
{ ID: "2",
ID: "2", Price: 30,
Price: 30, },
}, },
}, core: []core.Product{
core: []core.Product{ {
{ ID: "1",
ID: "1", Price: 20,
Price: 20, Group: group,
Group: group, },
}, {
{ ID: "2",
ID: "2", Price: 30,
Price: 30, },
}, },
}, },
}, {
{ name: "transfer empty product informations from proto to core",
name: "transfer empty product informations from proto to core", proto: []*discount.ProductInformation{},
proto: []*discount.ProductInformation{}, core: []core.Product{},
core: []core.Product{}, },
}, {
{ name: "transfer product informations from proto to core with nil proto",
name: "transfer product informations from proto to core with nil proto", proto: []*discount.ProductInformation{
proto: []*discount.ProductInformation{ {
{ ID: "1",
ID: "1", Price: 20,
Price: 20, Group: &group,
Group: &group, },
}, {
{ ID: "2",
ID: "2", Price: 30,
Price: 30, },
}, nil,
nil, },
}, core: []core.Product{
core: []core.Product{ {
{ ID: "1",
ID: "1", Price: 20,
Price: 20, Group: group,
Group: group, },
}, {
{ ID: "2",
ID: "2", Price: 30,
Price: 30, },
}, {},
{}, },
}, },
}, }
}
for _, testCase := range testCases {
for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) { assert.Equal(t, testCase.core, transfer.ProductsProtoToCore(testCase.proto))
assert.Equal(t, testCase.core, transfer.ProductsProtoToCore(testCase.proto)) })
}) }
} }
}

@ -1 +1 @@
[{ "delete": "discounts", "deletes": [{ "q": {} }] }] [{ "delete": "discounts", "deletes": [{ "q": {} }] }]

@ -1,486 +1,486 @@
[ [
{ {
"insert": "discounts", "insert": "discounts",
"ordered": true, "ordered": true,
"documents": [ "documents": [
{ {
"layer": 4, "layer": 4,
"name": "Лояльность 1", "name": "Лояльность 1",
"description": "постоянная скидка для юзеров, внёсших на проект от 10 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине", "description": "постоянная скидка для юзеров, внёсших на проект от 10 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине",
"condition": { "condition": {
"purchasesAmount": 10000 "purchasesAmount": 10000
}, },
"target": { "target": {
"factor": 0.99 "factor": 0.99
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Лояльность 2", "name": "Лояльность 2",
"description": "постоянная скидка для юзеров, внёсших на проект от 25 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине", "description": "постоянная скидка для юзеров, внёсших на проект от 25 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине",
"layer": 4, "layer": 4,
"condition": { "condition": {
"purchasesAmount": 25000 "purchasesAmount": 25000
}, },
"target": { "target": {
"factor": 0.99 "factor": 0.99
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Лояльность 3", "name": "Лояльность 3",
"description": "постоянная скидка для юзеров, внёсших на проект от 50 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине", "description": "постоянная скидка для юзеров, внёсших на проект от 50 000 рублей. Применяется на итоговую сумму, после скидок за сумму в корзине",
"layer": 4, "layer": 4,
"condition": { "condition": {
"purchasesAmount": 50000 "purchasesAmount": 50000
}, },
"target": { "target": {
"factor": 0.975 "factor": 0.975
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Корзина 1", "name": "Корзина 1",
"description": "Скидка на размер корзины от 5 000 р. Применяется на итоговую сумму, после суммирования корзины", "description": "Скидка на размер корзины от 5 000 р. Применяется на итоговую сумму, после суммирования корзины",
"layer": 3, "layer": 3,
"condition": { "condition": {
"cartPurchasesAmount": 5000 "cartPurchasesAmount": 5000
}, },
"target": { "target": {
"scope": "sum", "scope": "sum",
"factor": 0.985 "factor": 0.985
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Корзина 2", "name": "Корзина 2",
"description": "Скидка на размер корзины от 50 000 р. Применяется на итоговую сумму, после суммирования корзины", "description": "Скидка на размер корзины от 50 000 р. Применяется на итоговую сумму, после суммирования корзины",
"layer": 3, "layer": 3,
"condition": { "condition": {
"cartPurchasesAmount": 50000 "cartPurchasesAmount": 50000
}, },
"target": { "target": {
"scope": "sum", "scope": "sum",
"factor": 0.965 "factor": 0.965
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "НКО", "name": "НКО",
"description": "Скидка всем подтвердившим статус НКО. Перекрывает ВСЕ остальные скидки. Если эта скидка срабатывает, остальные можно не вычислять. Т.е. если на уровне 0 находится какая-лидо скидка для выданных условий, просто суммируем корзину и применяем к сумме указанный множитель, после чего прекращаем процесс рассчёта", "description": "Скидка всем подтвердившим статус НКО. Перекрывает ВСЕ остальные скидки. Если эта скидка срабатывает, остальные можно не вычислять. Т.е. если на уровне 0 находится какая-лидо скидка для выданных условий, просто суммируем корзину и применяем к сумме указанный множитель, после чего прекращаем процесс рассчёта",
"layer": 1, "layer": 1,
"condition": { "condition": {
"userType": "nko" "userType": "nko"
}, },
"target": { "target": {
"scope": "sum", "scope": "sum",
"factor": 0.2, "factor": 0.2,
"overhelm": true "overhelm": true
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Промокод На АБ тесты", "name": "Промокод На АБ тесты",
"description": "Скидка, полученная конкретным юзером, после введения промокода. Заменяет собой не промокодовую", "description": "Скидка, полученная конкретным юзером, после введения промокода. Заменяет собой не промокодовую",
"layer": 2, "layer": 2,
"condition": { "condition": {
"coupon": "coupon1", "coupon": "coupon1",
"user": "buddy" "user": "buddy"
}, },
"target": { "target": {
"overhelm": true, "overhelm": true,
"scope": "each", "scope": "each",
"products": [ "products": [
{ {
"productId": "p6", "productId": "p6",
"factor": 0.5 "factor": 0.5
} }
] ]
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Новогодняя скидка", "name": "Новогодняя скидка",
"description": "", "description": "",
"layer": 3, "layer": 3,
"condition": { "condition": {
"period": { "period": {
"from": "2022-12-31T00:00:00.000Z", "from": "2022-12-31T00:00:00.000Z",
"to": "2024-01-01T00:00:00.000Z" "to": "2024-01-01T00:00:00.000Z"
} }
}, },
"target": { "target": {
"scope": "each", "scope": "each",
"products": [ "products": [
{ {
"productId": "p1", "productId": "p1",
"factor": 0.965 "factor": 0.965
}, },
{ {
"productId": "p2", "productId": "p2",
"factor": 0.5 "factor": 0.5
}, },
{ {
"productId": "p3", "productId": "p3",
"factor": 0.8 "factor": 0.8
}, },
{ {
"productId": "p4", "productId": "p4",
"factor": 0.95 "factor": 0.95
} }
] ]
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Скидка на определённого пользователя", "name": "Скидка на определённого пользователя",
"description": "", "description": "",
"layer": 3, "layer": 3,
"condition": { "condition": {
"user": "another_buddy" "user": "another_buddy"
}, },
"target": { "target": {
"scope": "sum", "scope": "sum",
"factor": 0.95 "factor": 0.95
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Анлим Шабло 1", "name": "Анлим Шабло 1",
"description": "Скидка на количество безлимитных дней работы от 30 дней", "description": "Скидка на количество безлимитных дней работы от 30 дней",
"layer": 3, "layer": 3,
"condition": { "condition": {
"term": 30, "term": 30,
"product": "p1" "product": "p1"
}, },
"target": { "target": {
"scope": "each", "scope": "each",
"products": [ "products": [
{ {
"productId": "p1", "productId": "p1",
"factor": 0.975 "factor": 0.975
} }
] ]
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Анлим Шабло 2", "name": "Анлим Шабло 2",
"description": "Скидка на количество безлимитных дней работы от 90 дней", "description": "Скидка на количество безлимитных дней работы от 90 дней",
"layer": 3, "layer": 3,
"condition": { "condition": {
"term": 90, "term": 90,
"product": "p1" "product": "p1"
}, },
"target": { "target": {
"scope": "each", "scope": "each",
"products": [ "products": [
{ {
"productId": "p1", "productId": "p1",
"factor": 0.975 "factor": 0.975
} }
] ]
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Анлим Шабло 3", "name": "Анлим Шабло 3",
"description": "Скидка на количество безлимитных дней работы от 180 дней", "description": "Скидка на количество безлимитных дней работы от 180 дней",
"layer": 1, "layer": 1,
"condition": { "condition": {
"term": 180, "term": 180,
"product": "p1" "product": "p1"
}, },
"target": { "target": {
"scope": "each", "scope": "each",
"products": [ "products": [
{ {
"productId": "p1", "productId": "p1",
"factor": 0.93 "factor": 0.93
} }
] ]
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Генерации Шабло 1", "name": "Генерации Шабло 1",
"description": "Скидка на количество генераций от 100 шт", "description": "Скидка на количество генераций от 100 шт",
"layer": 3, "layer": 3,
"condition": { "condition": {
"usage": 100, "usage": 100,
"product": "p2" "product": "p2"
}, },
"target": { "target": {
"scope": "each", "scope": "each",
"products": [ "products": [
{ {
"productId": "p2", "productId": "p2",
"factor": 0.995 "factor": 0.995
} }
] ]
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Генерации Шабло 2", "name": "Генерации Шабло 2",
"description": "Скидка на количество генераций от 350 шт", "description": "Скидка на количество генераций от 350 шт",
"layer": 3, "layer": 3,
"condition": { "condition": {
"usage": 350, "usage": 350,
"product": "p2" "product": "p2"
}, },
"target": { "target": {
"scope": "each", "scope": "each",
"products": [ "products": [
{ {
"productId": "p2", "productId": "p2",
"factor": 0.98 "factor": 0.98
} }
] ]
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Генерации Шабло 3", "name": "Генерации Шабло 3",
"description": "Скидка на количество генераций от 500 шт", "description": "Скидка на количество генераций от 500 шт",
"layer": 3, "layer": 3,
"condition": { "condition": {
"usage": 500, "usage": 500,
"product": "p2" "product": "p2"
}, },
"target": { "target": {
"scope": "each", "scope": "each",
"products": [ "products": [
{ {
"productId": "p2", "productId": "p2",
"factor": 0.945 "factor": 0.945
} }
] ]
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Шаблонизатор 1", "name": "Шаблонизатор 1",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 1000 р", "description": "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 1000 р",
"layer": 3, "layer": 3,
"condition": { "condition": {
"group": "templategen", "group": "templategen",
"priceFrom": 1000 "priceFrom": 1000
}, },
"target": { "target": {
"scope": "group", "scope": "group",
"group": "templategen", "group": "templategen",
"factor": 0.996 "factor": 0.996
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Шаблонизатор 2", "name": "Шаблонизатор 2",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 5000 р", "description": "Скидка на сумму стоимостей товаров, принадлежащих сервису шаблонизации, от 5000 р",
"layer": 3, "layer": 3,
"condition": { "condition": {
"group": "templategen", "group": "templategen",
"priceFrom": 5000 "priceFrom": 5000
}, },
"target": { "target": {
"scope": "group", "scope": "group",
"group": "templategen", "group": "templategen",
"factor": 0.983 "factor": 0.983
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Опросник 1", "name": "Опросник 1",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 2000 р", "description": "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 2000 р",
"layer": 3, "layer": 3,
"condition": { "condition": {
"group": "squiz", "group": "squiz",
"priceFrom": 2000 "priceFrom": 2000
}, },
"target": { "target": {
"scope": "group", "scope": "group",
"group": "squiz", "group": "squiz",
"factor": 0.983 "factor": 0.983
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Опросник 2", "name": "Опросник 2",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 6000 р", "description": "Скидка на сумму стоимостей товаров, принадлежащих сервису опросника, от 6000 р",
"layer": 3, "layer": 3,
"condition": { "condition": {
"group": "squiz", "group": "squiz",
"priceFrom": 6000 "priceFrom": 6000
}, },
"target": { "target": {
"scope": "group", "scope": "group",
"group": "squiz", "group": "squiz",
"factor": 0.969 "factor": 0.969
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Сокращатель 1", "name": "Сокращатель 1",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 500 р", "description": "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 500 р",
"layer": 3, "layer": 3,
"condition": { "condition": {
"group": "dwarfener", "group": "dwarfener",
"priceFrom": 500 "priceFrom": 500
}, },
"target": { "target": {
"scope": "group", "scope": "group",
"group": "dwarfener", "group": "dwarfener",
"factor": 0.99 "factor": 0.99
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"name": "Сокращатель 2", "name": "Сокращатель 2",
"description": "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 2500 р", "description": "Скидка на сумму стоимостей товаров, принадлежащих сервису сокращателя, от 2500 р",
"layer": 3, "layer": 3,
"condition": { "condition": {
"group": "dwarfener", "group": "dwarfener",
"priceFrom": 2500 "priceFrom": 2500
}, },
"target": { "target": {
"scope": "group", "scope": "group",
"group": "dwarfener", "group": "dwarfener",
"factor": 0.96 "factor": 0.96
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
}, },
{ {
"_id": { "_id": {
"$oid": "641b2d73e0e07a7e90b59616" "$oid": "641b2d73e0e07a7e90b59616"
}, },
"name": "Анлим Квиз 1", "name": "Анлим Квиз 1",
"description": "Скидка на количество дней безлимитного использования опросника, от 30 дней", "description": "Скидка на количество дней безлимитного использования опросника, от 30 дней",
"layer": 3, "layer": 3,
"condition": { "condition": {
"term": 30, "term": 30,
"product": "p3" "product": "p3"
}, },
"target": { "target": {
"scope": "each", "scope": "each",
"products": [ "products": [
{ {
"productId": "p3", "productId": "p3",
"factor": 0.97 "factor": 0.97
} }
] ]
}, },
"deprecated": false, "deprecated": false,
"audit": { "audit": {
"createdAt": "2022-12-31T00:00:00.000Z", "createdAt": "2022-12-31T00:00:00.000Z",
"updatedAt": "2022-12-31T00:00:00.000Z", "updatedAt": "2022-12-31T00:00:00.000Z",
"deleted": false "deleted": false
} }
} }
] ]
} }
] ]

@ -1,15 +1,15 @@
package array package array
func Contains[T any](array []T, callback func(T, int, []T) bool) bool { func Contains[T any](array []T, callback func(T, int, []T) bool) bool {
isContains := false isContains := false
for index, element := range array { for index, element := range array {
if callback(element, index, array) { if callback(element, index, array) {
isContains = true isContains = true
break break
} }
} }
return isContains return isContains
} }

@ -1,153 +1,153 @@
package array_test package array_test
import ( import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array"
) )
func TestContains(t *testing.T) { func TestContains(t *testing.T) {
testCasesWithPrimitives := []struct { testCasesWithPrimitives := []struct {
name string name string
inputArray []any inputArray []any
inputCallback func(any, int, []any) bool inputCallback func(any, int, []any) bool
expect bool expect bool
}{ }{
{ {
name: "Проверка наличие строк по значению", name: "Проверка наличие строк по значению",
inputArray: []any{"test1", "test2"}, inputArray: []any{"test1245", "test2"},
inputCallback: func(element any, index int, array []any) bool { inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"}) assert.Equal(t, array, []any{"test1245", "test2"})
return element == "test1" return element == "test1245"
}, },
expect: true, expect: true,
}, },
{ {
name: "Проверка наличие строк по значению (неудачная)", name: "Проверка наличие строк по значению (неудачная)",
inputArray: []any{"test1", "test2"}, inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool { inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"}) assert.Equal(t, array, []any{"test1", "test2"})
return element == "tttt" return element == "fdadadfadfadf"
}, },
expect: false, expect: false,
}, },
{ {
name: "Проверка наличие строк по индексу", name: "Проверка наличие строк по индексу",
inputArray: []any{"test1", "test2"}, inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool { inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"}) assert.Equal(t, array, []any{"test1", "test2"})
return index == 1 return index == 1
}, },
expect: true, expect: true,
}, },
{ {
name: "Проверка наличие чисел по значению", name: "Проверка наличие чисел по значению",
inputArray: []any{1, 4}, inputArray: []any{1, 4},
inputCallback: func(element any, index int, array []any) bool { inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{1, 4}) assert.Equal(t, array, []any{1, 4})
return element == 1 return element == 1
}, },
expect: true, expect: true,
}, },
{ {
name: "Проверка наличие строк по значению с несколькими схожими значениями", name: "Проверка наличие строк по значению с несколькими схожими значениями",
inputArray: []any{"test1", "test2"}, inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool { inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"}) assert.Equal(t, array, []any{"test1", "test2"})
return strings.Contains(element.(string), "test") return strings.Contains(element.(string), "test")
}, },
expect: true, expect: true,
}, },
} }
testCasesWithObjects := []struct { testCasesWithObjects := []struct {
name string name string
inputArray []struct{ Name string } inputArray []struct{ Name string }
inputCallback func(struct{ Name string }, int, []struct{ Name string }) bool inputCallback func(struct{ Name string }, int, []struct{ Name string }) bool
expect bool expect bool
}{ }{
{ {
name: "Проверка наличие объектов по индексу", name: "Проверка наличие объектов по индексу",
inputArray: []struct{ Name string }{ inputArray: []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}, },
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool { inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{ assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}) })
return index == 1 return index == 1
}, },
expect: true, expect: true,
}, },
{ {
name: "Проверка наличие объектов по имени (неудачная)", name: "Проверка наличие объектов по имени (неудачная)",
inputArray: []struct{ Name string }{ inputArray: []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}, },
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool { inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{ assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}) })
return element.Name == "tttt" return element.Name == "tttt"
}, },
expect: false, expect: false,
}, },
{ {
name: "Проверка наличие объектов по значению поля", name: "Проверка наличие объектов по значению поля",
inputArray: []struct{ Name string }{ inputArray: []struct{ Name string }{
{Name: "test1"}, {Name: "test19999"},
{Name: "test2"}, {Name: "test2"},
}, },
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool { inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{ assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"}, {Name: "test19999"},
{Name: "test2"}, {Name: "test2"},
}) })
return element.Name == "test1" return element.Name == "test19999"
}, },
expect: true, expect: true,
}, },
{ {
name: "Проверка наличие объектов по совпадению значения поля", name: "Проверка наличие объектов по совпадению значения поля",
inputArray: []struct{ Name string }{ inputArray: []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}, },
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool { inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{ assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}) })
return strings.Contains(element.Name, "test") return strings.Contains(element.Name, "test")
}, },
expect: true, expect: true,
}, },
} }
for _, test := range testCasesWithPrimitives { for _, test := range testCasesWithPrimitives {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expect, array.Contains(test.inputArray, test.inputCallback)) assert.Equal(t, test.expect, array.Contains(test.inputArray, test.inputCallback))
}) })
} }
for _, test := range testCasesWithObjects { for _, test := range testCasesWithObjects {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expect, array.Contains(test.inputArray, test.inputCallback)) assert.Equal(t, test.expect, array.Contains(test.inputArray, test.inputCallback))
}) })
} }
} }

@ -11,4 +11,3 @@ func Filter[T any](array []T, callback func(T, int, []T) bool) []T {
return filteredArray return filteredArray
} }

@ -1,134 +1,134 @@
package array_test package array_test
import ( import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array"
) )
func TestFilter(t *testing.T) { func TestFilter(t *testing.T) {
testCasesWithPrimitives := []struct { testCasesWithPrimitives := []struct {
name string name string
inputArray []any inputArray []any
inputCallback func(any, int, []any) bool inputCallback func(any, int, []any) bool
expect []any expect []any
}{ }{
{ {
name: "Фильтрация массива строк по значению", name: "Фильтрация массива строк по значению",
inputArray: []any{"test1", "test2"}, inputArray: []any{"test10130153", "test2"},
inputCallback: func(element any, index int, array []any) bool { inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"}) assert.Equal(t, array, []any{"test10130153", "test2"})
return element == "test1" return element == "test10130153"
}, },
expect: []any{"test1"}, expect: []any{"test10130153"},
}, },
{ {
name: "Фильтрация массива строк по индексу", name: "Фильтрация массива строк по индексу",
inputArray: []any{"test1", "test2"}, inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool { inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"}) assert.Equal(t, array, []any{"test1", "test2"})
return index == 1 return index == 1
}, },
expect: []any{"test2"}, expect: []any{"test2"},
}, },
{ {
name: "Фильтрация массива чисел по значению", name: "Фильтрация массива чисел по значению",
inputArray: []any{1, 4}, inputArray: []any{1, 4},
inputCallback: func(element any, index int, array []any) bool { inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{1, 4}) assert.Equal(t, array, []any{1, 4})
return element == 1 return element == 1
}, },
expect: []any{1}, expect: []any{1},
}, },
{ {
name: "Фильтрация массива строк по значению с несколькими схожими значениями", name: "Фильтрация массива строк по значению с несколькими схожими значениями",
inputArray: []any{"test1", "test2"}, inputArray: []any{"test1", "test2"},
inputCallback: func(element any, index int, array []any) bool { inputCallback: func(element any, index int, array []any) bool {
assert.Equal(t, array, []any{"test1", "test2"}) assert.Equal(t, array, []any{"test1", "test2"})
return strings.Contains(element.(string), "test") return strings.Contains(element.(string), "test")
}, },
expect: []any{"test1", "test2"}, expect: []any{"test1", "test2"},
}, },
} }
testCasesWithObjects := []struct { testCasesWithObjects := []struct {
name string name string
inputArray []struct{ Name string } inputArray []struct{ Name string }
inputCallback func(struct{ Name string }, int, []struct{ Name string }) bool inputCallback func(struct{ Name string }, int, []struct{ Name string }) bool
expect []struct{ Name string } expect []struct{ Name string }
}{ }{
{ {
name: "Фильтрация массива объектов по индексу", name: "Фильтрация массива объектов по индексу",
inputArray: []struct{ Name string }{ inputArray: []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}, },
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool { inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{ assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}) })
return index == 1 return index == 1
}, },
expect: []struct{ Name string }{ expect: []struct{ Name string }{
{Name: "test2"}, {Name: "test2"},
}, },
}, },
{ {
name: "Фильтрация массива объектов по значению поля", name: "Фильтрация массива объектов по значению поля",
inputArray: []struct{ Name string }{ inputArray: []struct{ Name string }{
{Name: "test1"}, {Name: "test5313131"},
{Name: "test2"}, {Name: "test2"},
}, },
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool { inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{ assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"}, {Name: "test5313131"},
{Name: "test2"}, {Name: "test2"},
}) })
return element.Name == "test1" return element.Name == "test5313131"
}, },
expect: []struct{ Name string }{ expect: []struct{ Name string }{
{Name: "test1"}, {Name: "test5313131"},
}, },
}, },
{ {
name: "Фильтрация массива объектов по совпадению значения поля", name: "Фильтрация массива объектов по совпадению значения поля",
inputArray: []struct{ Name string }{ inputArray: []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}, },
inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool { inputCallback: func(element struct{ Name string }, index int, array []struct{ Name string }) bool {
assert.Equal(t, array, []struct{ Name string }{ assert.Equal(t, array, []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}) })
return strings.Contains(element.Name, "test") return strings.Contains(element.Name, "test")
}, },
expect: []struct{ Name string }{ expect: []struct{ Name string }{
{Name: "test1"}, {Name: "test1"},
{Name: "test2"}, {Name: "test2"},
}, },
}, },
} }
for _, test := range testCasesWithPrimitives { for _, test := range testCasesWithPrimitives {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expect, array.Filter(test.inputArray, test.inputCallback)) assert.Equal(t, test.expect, array.Filter(test.inputArray, test.inputCallback))
}) })
} }
for _, test := range testCasesWithObjects { for _, test := range testCasesWithObjects {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.expect, array.Filter(test.inputArray, test.inputCallback)) assert.ElementsMatch(t, test.expect, array.Filter(test.inputArray, test.inputCallback))
}) })
} }
} }

11
pkg/array/find.go Normal file

@ -0,0 +1,11 @@
package array
func Find[T any](array []T, callback func(T, int, []T) bool) *T {
for index, element := range array {
if callback(element, index, array) {
return &element
}
}
return nil
}

32
pkg/array/find_test.go Normal file

@ -0,0 +1,32 @@
package array_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/array"
)
func TestFind(t *testing.T) {
t.Run("Find element in array (success)", func(t *testing.T) {
result := "test1"
callback := func(element string, index int, array []string) bool {
assert.Equal(t, array, []string{"test1", "test2"})
return element == result
}
assert.Equal(t, &result, array.Find([]string{"test1", "test2"}, callback))
})
t.Run("Find element in array (failure)", func(t *testing.T) {
result := "test"
callback := func(element string, index int, array []string) bool {
assert.Equal(t, array, []string{"test1", "test2"})
return element == result
}
assert.Nil(t, array.Find([]string{"test1", "test2"}, callback))
})
}

50
pkg/closer/closer.go Normal file

@ -0,0 +1,50 @@
package closer
import (
"context"
"fmt"
"log"
"sync"
)
type Callback func(ctx context.Context) error
type Closer struct {
mutex sync.Mutex
callbacks []Callback
}
func New() *Closer {
return &Closer{}
}
func (receiver *Closer) Add(callback Callback) {
receiver.mutex.Lock()
defer receiver.mutex.Unlock()
receiver.callbacks = append(receiver.callbacks, callback)
}
func (receiver *Closer) Close(ctx context.Context) error {
receiver.mutex.Lock()
defer receiver.mutex.Unlock()
complete := make(chan struct{}, 1)
go func() {
for index, callback := range receiver.callbacks {
if err := callback(ctx); err != nil {
log.Printf("[! (%d)] %v", index, err)
}
}
complete <- struct{}{}
}()
select {
case <-complete:
return nil
case <-ctx.Done():
return fmt.Errorf("shutdown cancelled: %v", ctx.Err())
}
}

@ -1,29 +1,29 @@
package convert package convert
import "strconv" import "strconv"
func StringArrayToIntSlice(stringArray []string) []int { func StringArrayToIntSlice(stringArray []string) []int {
intArray := make([]int, 0, len(stringArray)) intArray := make([]int, 0, len(stringArray))
for _, stringValue := range stringArray { for _, stringValue := range stringArray {
intValue, err := strconv.Atoi(stringValue) intValue, err := strconv.Atoi(stringValue)
if err != nil { if err != nil {
continue continue
} }
intArray = append(intArray, intValue) intArray = append(intArray, intValue)
} }
return intArray return intArray
} }
func MapToArray[T any, V comparable](currentMap map[V]T) []T { func MapToArray[T any, V comparable](currentMap map[V]T) []T {
result := make([]T, len(currentMap)) result := make([]T, len(currentMap))
index := 0 index := 0
for _, element := range currentMap { for _, element := range currentMap {
result[index] = element result[index] = element
index++ index++
} }
return result return result
} }

@ -1,22 +1,22 @@
package convert package convert
func ArrayToMap[T any, V comparable](array []T, key func(T) V) map[V]T { func ArrayToMap[T any, V comparable](array []T, key func(T) V) map[V]T {
result := make(map[V]T, len(array)) result := make(map[V]T, len(array))
for _, element := range array { for _, element := range array {
result[key(element)] = element result[key(element)] = element
} }
return result return result
} }
func MapToMapArray[T any, V comparable](currentMap map[V]T, key func(T) V) map[V][]T { func MapToMapArray[T any, V comparable](currentMap map[V]T, key func(T) V) map[V][]T {
result := make(map[V][]T) result := make(map[V][]T)
for _, element := range currentMap { for _, element := range currentMap {
currentKey := key(element) currentKey := key(element)
result[currentKey] = append(result[currentKey], element) result[currentKey] = append(result[currentKey], element)
} }
return result return result
} }

74
pkg/convert/map_test.go Normal file

@ -0,0 +1,74 @@
package convert_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
func TestArrayToMap(t *testing.T) {
type Product struct {
ID string
Price float64
Group string
}
t.Run("Успешная конвертация массива в карту по ID", func(t *testing.T) {
products := []Product{
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
}
assert.Equal(t,
map[string]Product{
"p1": {ID: "p1", Price: 1500, Group: "templategen"},
"p2": {ID: "p2", Price: 300, Group: "templategen"},
"p5": {ID: "p5", Price: 30, Group: "dwarfener"},
"p6": {ID: "p6", Price: 70, Group: "dwarfener"},
"p7": {ID: "p7", Price: 800, Group: "dwarfener"},
},
convert.ArrayToMap(products, func(product Product) string {
return product.ID
}),
)
})
}
func TestMapToMapArray(t *testing.T) {
type Product struct {
ID string
Price float64
Group string
}
t.Run("Успешная конвертация массива в карту из массивов", func(t *testing.T) {
productsMap := map[string]Product{
"p1": {ID: "p1", Price: 1500, Group: "templategen"},
"p2": {ID: "p2", Price: 300, Group: "templategen"},
"p5": {ID: "p5", Price: 30, Group: "dwarfener"},
"p6": {ID: "p6", Price: 70, Group: "dwarfener"},
"p7": {ID: "p7", Price: 800, Group: "dwarfener"},
}
assert.EqualValues(t,
map[string][]Product{
"templategen": {
{ID: "p1", Price: 1500, Group: "templategen"},
{ID: "p2", Price: 300, Group: "templategen"},
},
"dwarfener": {
{ID: "p5", Price: 30, Group: "dwarfener"},
{ID: "p6", Price: 70, Group: "dwarfener"},
{ID: "p7", Price: 800, Group: "dwarfener"},
},
},
convert.MapToMapArray(productsMap, func(product Product) string {
return product.Group
}),
)
})
}

@ -1,60 +1,58 @@
package convert package convert
import ( import (
"fmt" "fmt"
"reflect" "reflect"
) )
func ObjectToMap[T ~map[string]interface{}](object interface{}, tagName string) (T, error) { func ObjectToMap[T ~map[string]interface{}](object interface{}, tagName string) (T, error) {
defer func() { defer func() {
if recoveredError := recover(); recoveredError != nil { if recoveredError := recover(); recoveredError != nil {
fmt.Println("recovered from ObjectToMap: ", recoveredError) fmt.Println("recovered from ObjectToMap: ", recoveredError)
} }
}() }()
values := reflect.ValueOf(object) values := reflect.ValueOf(object)
if values.Kind() == reflect.Ptr { if values.Kind() == reflect.Ptr {
values = values.Elem() values = values.Elem()
} }
if values.Kind() != reflect.Struct { if values.Kind() != reflect.Struct {
return nil, fmt.Errorf("ObjectToMap only accepts structs; got %T", values) return nil, fmt.Errorf("ObjectToMap only accepts structs; got %T", values)
} }
fieldsCount := values.NumField() fieldsCount := values.NumField()
resultMap := make(T, fieldsCount) resultMap := make(T, fieldsCount)
valueType := values.Type() valueType := values.Type()
for index := 0; index < fieldsCount; index++ { for index := 0; index < fieldsCount; index++ {
fieldType := valueType.Field(index) fieldType := valueType.Field(index)
field := values.Field(index) field := values.Field(index)
if field.IsZero() { if field.IsZero() {
continue continue
} }
if tagValue := fieldType.Tag.Get(tagName); tagValue != "" { if tagValue := fieldType.Tag.Get(tagName); tagValue != "" {
resultMap[tagValue] = field.Interface() resultMap[tagValue] = field.Interface()
} }
} }
return resultMap, nil return resultMap, nil
} }
func ObjectToStringMap(object interface{}, tagName string) (map[string]string, error) { func ObjectToStringMap(object interface{}, tagName string) (map[string]string, error) {
mapObject, err := ObjectToMap[map[string]interface{}](object, tagName) mapObject, err := ObjectToMap[map[string]interface{}](object, tagName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
mapString := make(map[string]string, len(mapObject)) mapString := make(map[string]string, len(mapObject))
for key, value := range mapObject { for key, value := range mapObject {
strKey := fmt.Sprintf("%v", key) strValue := fmt.Sprintf("%v", value)
strValue := fmt.Sprintf("%v", value) mapString[key] = strValue
}
mapString[strKey] = strValue
} return mapString, nil
}
return mapString, nil
}

11
pkg/convert/uint.go Normal file

@ -0,0 +1,11 @@
package convert
import (
"math"
"golang.org/x/exp/constraints"
)
func FloatToUint64[T constraints.Float](number T) uint64 {
return uint64(math.Floor(float64(number)))
}

22
pkg/convert/uint_test.go Normal file

@ -0,0 +1,22 @@
package convert_test
import (
"testing"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/convert"
)
func TestFloatToUint64(t *testing.T) {
t.Run("Успешная конвертация float32 в uint32", func(t *testing.T) {
assert.Equal(t, uint64(10), convert.FloatToUint64(float32(10.6)))
})
t.Run("Успешная конвертация float64 в uint32", func(t *testing.T) {
assert.Equal(t, uint64(10), convert.FloatToUint64(float64(10.6)))
})
t.Run("Успешная конвертация float64 в uint32 (2)", func(t *testing.T) {
assert.Equal(t, uint64(10), convert.FloatToUint64(float64(10)))
})
}

@ -1,9 +1,9 @@
package defaults package defaults
func GetDefaultValue[T any](value *T, defaultValue T) T { func GetDefaultValue[T any](value *T, defaultValue T) T {
if value == nil { if value == nil {
return defaultValue return defaultValue
} }
return *value return *value
} }

@ -1,25 +1,24 @@
package defaults_test package defaults_test
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/defaults"
) )
func TestGetDefaultValue(t *testing.T) { func TestGetDefaultValue(t *testing.T) {
t.Run("Стандартное значение nil", func(t *testing.T) {
t.Run("Стандартное значение nil", func(t *testing.T) { assert.Equal(t, 0, defaults.GetDefaultValue(nil, 0))
assert.Equal(t, 0, defaults.GetDefaultValue(nil, 0)) })
})
t.Run("Стандартное значение float64", func(t *testing.T) {
t.Run("Стандартное значение float64", func(t *testing.T) { assert.Equal(t, float64(0), defaults.GetDefaultValue(nil, float64(0)))
assert.Equal(t, float64(0), defaults.GetDefaultValue(nil, float64(0))) })
})
t.Run("Стандартное значение строки", func(t *testing.T) {
t.Run("Стандартное значение строки", func(t *testing.T) { testString := "test"
testString := "test"
assert.Equal(t, "test", defaults.GetDefaultValue(&testString, ""))
assert.Equal(t, "test", defaults.GetDefaultValue(&testString, "")) })
}) }
}

2
pkg/env/parse.go vendored

@ -9,7 +9,7 @@ import (
envParser "github.com/sethvargo/go-envconfig" envParser "github.com/sethvargo/go-envconfig"
) )
// Parsing default env file or env context and returns struct pointer by generic // Parse parsing default env file or env context and returns struct pointer by generic.
func Parse[T interface{}](envFilePath string) (*T, error) { func Parse[T interface{}](envFilePath string) (*T, error) {
var configuration T var configuration T

@ -1,22 +1,22 @@
package mongo package mongo
import ( import (
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
) )
type Configuration struct { type Configuration struct {
Host string `env:"MONGO_HOST,default=localhost"` Host string `env:"MONGO_HOST,default=localhost"`
Port string `env:"MONGO_PORT,default=27017"` Port string `env:"MONGO_PORT,default=27017"`
User string `env:"MONGO_USER,required"` User string `env:"MONGO_USER,required"`
Password string `env:"MONGO_PASSWORD,required"` Password string `env:"MONGO_PASSWORD,required"`
Auth string `env:"MONGO_AUTH,required"` Auth string `env:"MONGO_AUTH,required"`
DatabaseName string `env:"MONGO_DB_NAME,required"` DatabaseName string `env:"MONGO_DB_NAME,required"`
} }
type RequestSettings struct { type RequestSettings struct {
Driver *mongo.Collection Driver *mongo.Collection
Options *options.FindOptions Options *options.FindOptions
Filter primitive.M Filter primitive.M
} }

@ -1,71 +1,73 @@
package mongo package mongo
import ( import (
"context" "context"
"fmt" "fmt"
"log" "log"
"net" "net"
"net/url" "net/url"
"time" "time"
"go.mongodb.org/mongo-driver/event" "go.mongodb.org/mongo-driver/event"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
) )
type ConnectDeps struct { type ConnectDeps struct {
Configuration *Configuration Configuration *Configuration
Timeout time.Duration Timeout time.Duration
} }
func Connect(ctx context.Context, deps *ConnectDeps) (*mongo.Database, error) { func Connect(ctx context.Context, deps *ConnectDeps) (*mongo.Database, error) {
if deps == nil { if deps == nil {
return nil, ErrEmptyArgs return nil, ErrEmptyArgs
} }
mongoURI := &url.URL{ mongoURI := &url.URL{
Scheme: "mongodb", Scheme: "mongodb",
Host: net.JoinHostPort(deps.Configuration.Host, deps.Configuration.Port), Host: net.JoinHostPort(deps.Configuration.Host, deps.Configuration.Port),
} }
cmdMonitor := &event.CommandMonitor{ cmdMonitor := &event.CommandMonitor{
Started: func(_ context.Context, evt *event.CommandStartedEvent) { Started: func(_ context.Context, evt *event.CommandStartedEvent) {
log.Println(evt.Command) log.Println(evt.Command)
}, },
Succeeded: func(_ context.Context, evt *event.CommandSucceededEvent) { Succeeded: func(_ context.Context, evt *event.CommandSucceededEvent) {
log.Println(evt.Reply) log.Println(evt.Reply)
}, },
Failed: func(_ context.Context, evt *event.CommandFailedEvent) { Failed: func(_ context.Context, evt *event.CommandFailedEvent) {
log.Println(evt.Failure) log.Println(evt.Failure)
}, },
} }
connectionOptions := options.Client(). connectionOptions := options.Client().
ApplyURI(mongoURI.String()). ApplyURI(mongoURI.String()).
SetAuth(options.Credential{ SetAuth(options.Credential{
AuthMechanism: "SCRAM-SHA-1", AuthMechanism: "SCRAM-SHA-1",
AuthSource: deps.Configuration.Auth, AuthSource: deps.Configuration.Auth,
Username: deps.Configuration.User, Username: deps.Configuration.User,
Password: deps.Configuration.Password, Password: deps.Configuration.Password,
}). }).
SetMonitor(cmdMonitor) SetMonitor(cmdMonitor)
ticker := time.NewTicker(1 * time.Second) ticker := time.NewTicker(1 * time.Second)
timeoutExceeded := time.After(deps.Timeout) timeoutExceeded := time.After(deps.Timeout)
defer ticker.Stop() defer ticker.Stop()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
connection, err := mongo.Connect(ctx, connectionOptions) connection, err := mongo.Connect(ctx, connectionOptions)
if err == nil { if err == nil {
return connection.Database(deps.Configuration.DatabaseName), nil return connection.Database(deps.Configuration.DatabaseName), nil
} }
log.Printf("failed to connect to db <%s>: %s", mongoURI.String(), err.Error()) log.Printf("failed to connect to db <%s>: %s", mongoURI.String(), err.Error())
case <-timeoutExceeded: case <-timeoutExceeded:
return nil, fmt.Errorf("db connection <%s> failed after %d timeout", mongoURI, deps.Timeout) return nil, fmt.Errorf("db connection <%s> failed after %d timeout", mongoURI.String(), deps.Timeout)
} default:
} time.Sleep(1 * time.Second)
} }
}
}

@ -1,7 +1,7 @@
package mongo package mongo
import "errors" import "errors"
var ( var (
ErrEmptyArgs = errors.New("arguments are empty") ErrEmptyArgs = errors.New("arguments are empty")
) )

@ -1,52 +1,55 @@
package mongo package mongo
import ( import (
"context" "context"
) "log"
)
func Find[T any](ctx context.Context, settings *RequestSettings) ([]T, error) {
func Find[T any](ctx context.Context, settings *RequestSettings) ([]T, error) { if settings == nil {
if settings == nil { return []T{}, ErrEmptyArgs
return []T{}, ErrEmptyArgs }
}
results := make([]T, 0)
results := make([]T, 0)
cursor, err := settings.Driver.Find(ctx, settings.Filter, settings.Options)
cursor, err := settings.Driver.Find(ctx, settings.Filter) if err != nil {
if err != nil { return []T{}, err
return []T{}, err }
}
defer func() {
defer cursor.Close(ctx) if err := cursor.Close(ctx); err != nil {
log.Printf("failed to close cursor: %v", err)
for cursor.Next(ctx) { }
result := new(T) }()
if err := cursor.Decode(result); err != nil { for cursor.Next(ctx) {
return []T{}, err result := new(T)
}
if err := cursor.Decode(result); err != nil {
results = append(results, *result) return []T{}, err
} }
if err := cursor.Err(); err != nil { results = append(results, *result)
return []T{}, err }
}
if err := cursor.Err(); err != nil {
return results, nil return []T{}, err
} }
func FindOne[T any](ctx context.Context, settings *RequestSettings) (*T, error) { return results, nil
if settings == nil { }
return nil, ErrEmptyArgs
} func FindOne[T any](ctx context.Context, settings *RequestSettings) (*T, error) {
if settings == nil {
result := new(T) return nil, ErrEmptyArgs
}
if err := settings.Driver.FindOne(ctx, settings.Filter).Decode(result); err != nil {
return nil, err result := new(T)
}
if err := settings.Driver.FindOne(ctx, settings.Filter).Decode(result); err != nil {
return result, nil return nil, err
} }
return result, nil
}

@ -1,35 +1,35 @@
package utils package utils
import ( import (
"reflect" "reflect"
) )
func GetFilledFieldsCount(object interface{}) int { func GetFilledFieldsCount(object interface{}) int {
filledFieldsCount := int(0) filledFieldsCount := int(0)
if object == nil { if object == nil {
return 0 return 0
} }
value := reflect.ValueOf(object) value := reflect.ValueOf(object)
if value.Kind() == reflect.Ptr { if value.Kind() == reflect.Ptr {
value = value.Elem() value = value.Elem()
} }
fieldsCount := value.NumField() fieldsCount := value.NumField()
if fieldsCount < 1 { if fieldsCount < 1 {
return 0 return 0
} }
for index := 0; index < fieldsCount; index++ { for index := 0; index < fieldsCount; index++ {
field := value.Field(index) field := value.Field(index)
if field.IsZero() { if field.IsZero() {
continue continue
} }
filledFieldsCount++ filledFieldsCount++
} }
return filledFieldsCount return filledFieldsCount
} }

@ -1,65 +1,64 @@
package utils_test package utils_test
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils" )
)
func TestGetFilledFieldsCount(t *testing.T) {
func TestGetFilledFieldsCount(t *testing.T) { name := "name"
name := "name" age := uint(10)
age := uint(10)
t.Run("Получение количества заполненных полей структуры с пустой структурой", func(t *testing.T) {
t.Run("Получение количества заполненных полей структуры с пустой структурой", func(t *testing.T) { assert.Equal(t, 0, utils.GetFilledFieldsCount(struct{}{}))
assert.Equal(t, 0, utils.GetFilledFieldsCount(struct{}{})) })
})
t.Run("Получение количества заполненных полей структуры с nil", func(t *testing.T) {
t.Run("Получение количества заполненных полей структуры с nil", func(t *testing.T) { assert.Equal(t, 0, utils.GetFilledFieldsCount(nil))
assert.Equal(t, 0, utils.GetFilledFieldsCount(nil)) })
})
t.Run("Получение количества заполненных полей структуры с указателем на пустую структуру", func(t *testing.T) {
t.Run("Получение количества заполненных полей структуры с указателем на пустую структуру", func(t *testing.T) { assert.Equal(t, 0, utils.GetFilledFieldsCount(&struct{}{}))
assert.Equal(t, 0, utils.GetFilledFieldsCount(&struct{}{})) })
})
t.Run("Получение количества заполненных полей структуры с заполненной структурой", func(t *testing.T) {
t.Run("Получение количества заполненных полей структуры с заполненной структурой", func(t *testing.T) { assert.Equal(t, 1, utils.GetFilledFieldsCount(struct {
assert.Equal(t, 1, utils.GetFilledFieldsCount(struct { name *string
name *string age *uint
age *uint }{
}{ name: &name,
name: &name, }))
})) })
})
t.Run("Получение количества заполненных полей структуры с указателем на заполненную структурой", func(t *testing.T) {
t.Run("Получение количества заполненных полей структуры с указателем на заполненную структурой", func(t *testing.T) { assert.Equal(t, 2, utils.GetFilledFieldsCount(&struct {
assert.Equal(t, 2, utils.GetFilledFieldsCount(&struct { name *string
name *string age *uint
age *uint }{
}{ name: &name,
name: &name, age: &age,
age: &age, }))
})) })
})
t.Run("Получение количества заполненных полей структуры с заполненную структурой не опциональных значений", func(t *testing.T) {
t.Run("Получение количества заполненных полей структуры с заполненную структурой не опциональных значений", func(t *testing.T) { assert.Equal(t, 2, utils.GetFilledFieldsCount(&struct {
assert.Equal(t, 2, utils.GetFilledFieldsCount(&struct { name *string
name *string age uint
age uint }{
}{ name: &name,
name: &name, age: age,
age: age, }))
})) })
})
t.Run("Получение количества заполненных полей структуры с не заполненную структурой не опциональных значений", func(t *testing.T) {
t.Run("Получение количества заполненных полей структуры с не заполненную структурой не опциональных значений", func(t *testing.T) { assert.Equal(t, 0, utils.GetFilledFieldsCount(&struct {
assert.Equal(t, 0, utils.GetFilledFieldsCount(&struct { name string
name string age uint
age uint }{
}{ name: "",
name: "", age: 0,
age: 0, }))
})) })
}) }
}

@ -1,50 +1,50 @@
package utils_test package utils_test
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils" "penahub.gitlab.yandexcloud.net/pena-services/accruals-service/pkg/utils"
) )
func TestMergeMaps(t *testing.T) { func TestMergeMaps(t *testing.T) {
t.Run("Соединение двух пустых map", func(t *testing.T) { t.Run("Соединение двух пустых map", func(t *testing.T) {
assert.Equal(t, map[string]interface{}{}, utils.MergeMaps(map[string]interface{}{}, map[string]interface{}{})) assert.Equal(t, map[string]interface{}{}, utils.MergeMaps(map[string]interface{}{}, map[string]interface{}{}))
}) })
t.Run("Соединение несколько пустых map", func(t *testing.T) { t.Run("Соединение несколько пустых map", func(t *testing.T) {
assert.Equal(t, map[string]interface{}{}, utils.MergeMaps(map[string]interface{}{}, map[string]interface{}{}, map[string]interface{}{})) assert.Equal(t, map[string]interface{}{}, utils.MergeMaps(map[string]interface{}{}, map[string]interface{}{}, map[string]interface{}{}))
}) })
t.Run("Соединение заполненных map", func(t *testing.T) { t.Run("Соединение заполненных map", func(t *testing.T) {
assert.Equal(t, assert.Equal(t,
map[string]interface{}{ map[string]interface{}{
"test1": "1", "test1": "1",
"test2": "1", "test2": "1",
"test3": "1", "test3": "1",
"test4": "1", "test4": "1",
}, },
utils.MergeMaps( utils.MergeMaps(
map[string]interface{}{"test1": "1"}, map[string]interface{}{"test1": "1"},
map[string]interface{}{"test2": "1"}, map[string]interface{}{"test2": "1"},
map[string]interface{}{"test3": "1", "test4": "1"}, map[string]interface{}{"test3": "1", "test4": "1"},
), ),
) )
}) })
t.Run("Соединение заполненных map с одинаковыми ключами", func(t *testing.T) { t.Run("Соединение заполненных map с одинаковыми ключами", func(t *testing.T) {
assert.Equal(t, assert.Equal(t,
map[string]interface{}{ map[string]interface{}{
"test1": "1", "test1": "1",
"test2": "1", "test2": "1",
"test3": "1", "test3": "1",
"test4": "1", "test4": "1",
}, },
utils.MergeMaps( utils.MergeMaps(
map[string]interface{}{"test1": "1"}, map[string]interface{}{"test1": "1"},
map[string]interface{}{"test2": "1", "test1": "1"}, map[string]interface{}{"test2": "1", "test1": "1"},
map[string]interface{}{"test3": "1", "test4": "1"}, map[string]interface{}{"test3": "1", "test4": "1"},
), ),
) )
}) })
} }

@ -1,17 +1,17 @@
syntax = "proto3"; syntax = "proto3";
package discount; package discount;
import "google/api/annotations.proto"; import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto"; import "google/protobuf/empty.proto";
option go_package = "./discount"; option go_package = "./discount";
message Audit { message Audit {
google.protobuf.Timestamp UpdatedAt = 1; google.protobuf.Timestamp UpdatedAt = 1;
google.protobuf.Timestamp CreatedAt = 2; google.protobuf.Timestamp CreatedAt = 2;
optional google.protobuf.Timestamp DeletedAt = 3; optional google.protobuf.Timestamp DeletedAt = 3;
bool Deleted = 4; bool Deleted = 4;
} }

Some files were not shown because too many files have changed in this diff Show More