generated from PenaSide/GolangTemplate
merge dev in ref intrf
This commit is contained in:
commit
d614710c80
@ -1,57 +1,16 @@
|
||||
include:
|
||||
- project: "devops/pena-continuous-integration"
|
||||
file: "/templates/docker/build-template.gitlab-ci.yml"
|
||||
- project: "devops/pena-continuous-integration"
|
||||
file: "/templates/docker/deploy-template.gitlab-ci.yml"
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- clean
|
||||
- build
|
||||
- deploy
|
||||
|
||||
lint:
|
||||
image: golangci/golangci-lint:v1.55-alpine
|
||||
stage: lint
|
||||
before_script:
|
||||
- go install github.com/vektra/mockery/v2@v2.26.0
|
||||
- go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4
|
||||
script:
|
||||
- go generate ./internal/...
|
||||
- golangci-lint version
|
||||
- golangci-lint run ./internal/...
|
||||
|
||||
test:
|
||||
image: golang:1.21-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:
|
||||
stage: clean
|
||||
image:
|
||||
name: docker/compose:1.28.0
|
||||
entrypoint: [""]
|
||||
allow_failure: true
|
||||
variables:
|
||||
PRODUCTION_BRANCH: main
|
||||
STAGING_BRANCH: "staging"
|
||||
DEPLOY_TO: "staging"
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $PRODUCTION_BRANCH || $CI_COMMIT_BRANCH == $STAGING_BRANCH
|
||||
when: on_success
|
||||
before_script:
|
||||
- echo DEPLOY_TO = $DEPLOY_TO
|
||||
script:
|
||||
- docker-compose -f deployments/$DEPLOY_TO/docker-compose.yaml down --volumes --rmi local
|
||||
|
||||
build-app:
|
||||
stage: build
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
entrypoint: [""]
|
||||
tags:
|
||||
- gobuild
|
||||
variables:
|
||||
DOCKER_BUILD_PATH: "./Dockerfile"
|
||||
STAGING_BRANCH: "staging"
|
||||
@ -69,32 +28,22 @@ build-app:
|
||||
- echo CI_COMMIT_REF_SLUG = $CI_COMMIT_REF_SLUG
|
||||
- echo DOCKER_BUILD_PATH = $DOCKER_BUILD_PATH
|
||||
- echo CI_PIPELINE_ID = $CI_PIPELINE_ID
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
script:
|
||||
- mkdir -p /kaniko/.docker
|
||||
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
|
||||
- |
|
||||
/kaniko/executor --context $CI_PROJECT_DIR \
|
||||
--cache=true --cache-repo=$CI_REGISTRY_IMAGE \
|
||||
--dockerfile $CI_PROJECT_DIR/$DOCKER_BUILD_PATH --target production \
|
||||
--destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID --build-arg GITLAB_TOKEN=$GITLAB_TOKEN $CI_PROJECT_DIR
|
||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
|
||||
|
||||
|
||||
|
||||
deploy-to-staging:
|
||||
stage: deploy
|
||||
image:
|
||||
name: docker/compose:1.28.0
|
||||
entrypoint: [""]
|
||||
extends: .deploy_template
|
||||
variables:
|
||||
DEPLOY_TO: "staging"
|
||||
BRANCH: "staging"
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $BRANCH
|
||||
before_script:
|
||||
- echo CI_PROJECT_NAME = $CI_PROJECT_NAME
|
||||
- echo CI_REGISTRY = $CI_REGISTRY
|
||||
- echo REGISTRY_USER = $REGISTRY_USER
|
||||
- echo REGISTRY_TOKEN = $REGISTRY_TOKEN
|
||||
- echo DEPLOY_TO = $DEPLOY_TO
|
||||
- echo BRANCH = $BRANCH
|
||||
script:
|
||||
- docker login -u $REGISTRY_USER -p $REGISTRY_TOKEN $CI_REGISTRY
|
||||
- docker-compose -f deployments/$DEPLOY_TO/docker-compose.yaml up -d
|
||||
- if: "$CI_COMMIT_BRANCH == $BRANCH"
|
||||
after_script:
|
||||
- ls
|
||||
|
||||
|
@ -44,7 +44,7 @@ linters:
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
# - stylecheck
|
||||
- thelper
|
||||
- typecheck
|
||||
- unconvert
|
||||
|
@ -2,7 +2,7 @@
|
||||
FROM golang:1.20.3-alpine AS build
|
||||
|
||||
# Update packages and clear cache
|
||||
RUN apk update && apk add --no-cache curl && rm -rf /var/cache/apk/*
|
||||
RUN apk add --no-cache curl
|
||||
# Set work directory
|
||||
WORKDIR /app
|
||||
# Create binary directory
|
||||
@ -24,9 +24,7 @@ RUN GOOS=linux go build -o bin ./...
|
||||
FROM alpine:3.18.3 AS test
|
||||
|
||||
# Install packages
|
||||
RUN apk --no-cache add ca-certificates && rm -rf /var/cache/apk/*
|
||||
# Set GO111MODULE env
|
||||
ENV GO111MODULE=off
|
||||
RUN apk --no-cache add ca-certificates
|
||||
# Create home directory
|
||||
WORKDIR /app
|
||||
# Copy build file
|
||||
@ -44,7 +42,7 @@ CMD [ "./app" ]
|
||||
FROM alpine:3.18.3 AS production
|
||||
|
||||
# Install packages
|
||||
RUN apk --no-cache add ca-certificates && rm -rf /var/cache/apk/*
|
||||
RUN apk --no-cache add ca-certificates
|
||||
# Create home directory
|
||||
WORKDIR /app
|
||||
# Copy build file
|
||||
|
5
Makefile
5
Makefile
@ -25,15 +25,16 @@ test.unit: ## run unit tests
|
||||
go test ./...
|
||||
|
||||
test.integration: ## run integration tests
|
||||
go mod vendor
|
||||
@make test.integration.up
|
||||
@make test.integration.start
|
||||
@make test.integration.down
|
||||
|
||||
test.integration.up: ## build integration test environment
|
||||
docker-compose -f deployments/test/docker-compose.yaml up -d
|
||||
docker-compose -f deployments/test/docker-compose.yaml up -d --remove-orphans
|
||||
|
||||
test.integration.down: ## shutting down integration environment
|
||||
docker-compose -f deployments/test/docker-compose.yaml down --volumes --rmi local
|
||||
docker-compose -f deployments/test/docker-compose.yaml down --volumes
|
||||
|
||||
test.integration.start: ## run integration test
|
||||
go test -count=1 ./tests/integration/...
|
||||
|
@ -42,3 +42,4 @@ KAFKA_TOPIC_TARIFF - название топика для сообщений т
|
||||
## Полезные ссылки:
|
||||
|
||||
- [**Диаграммы**](./docs/diagram/README.md)
|
||||
- Для того чтобы создать новые endpoint, нужно прописать их в customer/api/openapi/v1/openapi.yaml, сделать его описание, с помощью инструкций в makefile сгенерировать файлы
|
||||
|
@ -18,16 +18,6 @@ tags:
|
||||
description: история
|
||||
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
summary: Health Check
|
||||
description: Check the health status of the API
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
503:
|
||||
description: Service Unavailable
|
||||
|
||||
/account:
|
||||
get:
|
||||
tags:
|
||||
@ -536,7 +526,7 @@ paths:
|
||||
summary: Получение лога событий связанных с аккаунтом
|
||||
operationId: getHistory
|
||||
security:
|
||||
- Bearer: []
|
||||
- Bearer: [ ]
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
@ -561,6 +551,13 @@ paths:
|
||||
explode: false
|
||||
schema:
|
||||
type: string
|
||||
- name: accountID
|
||||
in: query
|
||||
description: Идентификатор аккаунта. Если не указан, будет использоваться идентификатор из токена.
|
||||
required: false
|
||||
explode: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Успешное получение событий
|
||||
@ -583,6 +580,140 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/recent:
|
||||
get:
|
||||
tags:
|
||||
- history
|
||||
summary: Получение недавних тарифов
|
||||
operationId: getRecentTariffs
|
||||
description: Возвращает список уникальных тарифов из истории. Айди аккаунта получается из заголовка.
|
||||
security:
|
||||
- Bearer: []
|
||||
responses:
|
||||
'200':
|
||||
description: Успешный запрос
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/TariffID"
|
||||
'400':
|
||||
description: Неверный запрос
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'404':
|
||||
description: Тарифы не найдены
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [id]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
example: "807f1f77bcf81cd799439011"
|
||||
/sendReport:
|
||||
post:
|
||||
tags:
|
||||
- history
|
||||
summary: отправить акт проделанных работ на почту
|
||||
operationId: sendReport
|
||||
security:
|
||||
- Bearer: []
|
||||
description: Запрос на отправку акта проделанных работ
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [id]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
example: "807f1f77bcf81cd799439011"
|
||||
responses:
|
||||
"200":
|
||||
description: успешная отправка
|
||||
"401":
|
||||
description: Неавторизован
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/history/ltv:
|
||||
post:
|
||||
tags:
|
||||
- history
|
||||
summary: Расчет среднего времени жизни платящего клиента (LTV)
|
||||
operationId: calculateLTV
|
||||
security:
|
||||
- Bearer: [ ]
|
||||
requestBody:
|
||||
description: Период для расчета LTV
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
from:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Начальная дата в формате Unix timestamp. Если 0, устанавливает начало истории.
|
||||
to:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Конечная дата в формате Unix timestamp. Если 0, устанавливает текущее время.
|
||||
required:
|
||||
- from
|
||||
- to
|
||||
responses:
|
||||
'200':
|
||||
description: Успешный расчет LTV
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ltv:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Среднее количество дней между первым и последним платежом
|
||||
'400':
|
||||
description: Неверный запрос, если from больше, чем to
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Неавторизован
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Account:
|
||||
@ -748,6 +879,14 @@ components:
|
||||
"alfabank",
|
||||
]
|
||||
|
||||
TariffID:
|
||||
type: object
|
||||
properties:
|
||||
ID:
|
||||
type: string
|
||||
example: "807f1f77bcf81cd799439011"
|
||||
|
||||
|
||||
AccountStatus:
|
||||
type: string
|
||||
enum: ["no", "nko", "org"]
|
||||
|
9
deployments/local/auth.env.test
Normal file
9
deployments/local/auth.env.test
Normal file
@ -0,0 +1,9 @@
|
||||
DB_HOST=test-pena-auth-db
|
||||
DB_PORT=27017
|
||||
ENVIRONMENT=staging
|
||||
HTTP_HOST=0.0.0.0
|
||||
HTTP_PORT=8000
|
||||
DB_USERNAME=test
|
||||
DB_PASSWORD=test
|
||||
DB_NAME=admin
|
||||
DB_AUTH=admin
|
191
deployments/local/docker-compose.yaml
Normal file
191
deployments/local/docker-compose.yaml
Normal file
@ -0,0 +1,191 @@
|
||||
version: "3"
|
||||
|
||||
volumes:
|
||||
redpanda: null
|
||||
test-mongodb: null
|
||||
test-mongoconfdb: null
|
||||
|
||||
services:
|
||||
customer-service:
|
||||
container_name: customer-service
|
||||
build:
|
||||
context: ../../.
|
||||
dockerfile: Dockerfile
|
||||
target: test
|
||||
env_file:
|
||||
- .env.test
|
||||
environment:
|
||||
- HTTP_HOST=0.0.0.0
|
||||
- HTTP_PORT=8000
|
||||
|
||||
- GRPC_HOST=0.0.0.0
|
||||
- GRPC_PORT=9000
|
||||
- GRPC_DOMEN=customer-service:9000
|
||||
|
||||
- MONGO_HOST=customer-db
|
||||
- MONGO_PORT=27017
|
||||
- MONGO_USER=test
|
||||
- MONGO_PASSWORD=test
|
||||
- MONGO_DB_NAME=admin
|
||||
- MONGO_AUTH=admin
|
||||
|
||||
- KAFKA_BROKERS=customer-redpanda:9092
|
||||
- KAFKA_TOPIC_TARIFF=tariffs
|
||||
|
||||
- AUTH_MICROSERVICE_USER_URL=http://pena-auth-service:8000/user
|
||||
- HUBADMIN_MICROSERVICE_TARIFF_URL=http://hub-admin-backend-service:8000/tariff
|
||||
- CURRENCY_MICROSERVICE_TRANSLATE_URL=http://cbrfworker-service:8000/change
|
||||
- DISCOUNT_MICROSERVICE_GRPC_HOST=discount-service:9000
|
||||
- PAYMENT_MICROSERVICE_GRPC_HOST=treasurer-service:9085
|
||||
ports:
|
||||
- 8082:8000
|
||||
- 9092:9000
|
||||
depends_on:
|
||||
- customer-db
|
||||
- customer-migration
|
||||
- redpanda
|
||||
networks:
|
||||
- test
|
||||
|
||||
customer-migration:
|
||||
container_name: customer-migration
|
||||
build:
|
||||
context: ../../.
|
||||
dockerfile: Dockerfile
|
||||
target: test
|
||||
command:
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
'migrate -source file://migrations -database "mongodb://test:test@customer-db:27017/admin?authSource=admin" up',
|
||||
]
|
||||
depends_on:
|
||||
- customer-db
|
||||
networks:
|
||||
- test
|
||||
|
||||
customer-db:
|
||||
container_name: customer-db
|
||||
image: "mongo:6.0.3"
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: test
|
||||
MONGO_INITDB_ROOT_PASSWORD: test
|
||||
ports:
|
||||
- "27024:27017"
|
||||
networks:
|
||||
- test
|
||||
|
||||
redpanda:
|
||||
container_name: customer-redpanda
|
||||
tty: true
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v23.1.13
|
||||
command:
|
||||
- redpanda start
|
||||
- --smp 1
|
||||
- --overprovisioned
|
||||
- --kafka-addr internal://0.0.0.0:9092,external://0.0.0.0:19092
|
||||
# Address the broker advertises to clients that connect to the Kafka API.
|
||||
# Use the internal addresses to connect to the Redpanda brokers
|
||||
# from inside the same Docker network.
|
||||
# Use the external addresses to connect to the Redpanda brokers
|
||||
# from outside the Docker network.
|
||||
- --advertise-kafka-addr internal://redpanda:9092,external://localhost:19092
|
||||
- --pandaproxy-addr internal://0.0.0.0:8082,external://0.0.0.0:18082
|
||||
# Address the broker advertises to clients that connect to the HTTP Proxy.
|
||||
- --advertise-pandaproxy-addr internal://redpanda:8082,external://localhost:18082
|
||||
- --schema-registry-addr internal://0.0.0.0:8081,external://0.0.0.0:18081
|
||||
# Redpanda brokers use the RPC API to communicate with each other internally.
|
||||
- --rpc-addr redpanda:33145
|
||||
- --advertise-rpc-addr redpanda:33145
|
||||
ports:
|
||||
- 18081:18081
|
||||
- 18082:18082
|
||||
- 19092:19092
|
||||
- 19644:9644
|
||||
volumes:
|
||||
- redpanda:/var/lib/redpanda/data
|
||||
networks:
|
||||
- test
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "rpk cluster health | grep -E 'Healthy:.+true' || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
|
||||
console:
|
||||
tty: true
|
||||
image: docker.redpanda.com/redpandadata/console:v2.2.4
|
||||
entrypoint: /bin/sh
|
||||
command: -c "echo \"$$CONSOLE_CONFIG_FILE\" > /tmp/config.yml; /app/console"
|
||||
environment:
|
||||
CONFIG_FILEPATH: /tmp/config.yml
|
||||
CONSOLE_CONFIG_FILE: |
|
||||
kafka:
|
||||
brokers: ["redpanda:9092"]
|
||||
schemaRegistry:
|
||||
enabled: true
|
||||
urls: ["http://redpanda:8081"]
|
||||
redpanda:
|
||||
adminApi:
|
||||
enabled: true
|
||||
urls: ["http://redpanda:9644"]
|
||||
connect:
|
||||
enabled: true
|
||||
clusters:
|
||||
- name: local-connect-cluster
|
||||
url: http://connect:8083
|
||||
ports:
|
||||
- 8080:8080
|
||||
networks:
|
||||
- test
|
||||
depends_on:
|
||||
- redpanda
|
||||
|
||||
connect:
|
||||
tty: true
|
||||
image: docker.redpanda.com/redpandadata/connectors:latest
|
||||
hostname: connect
|
||||
container_name: connect
|
||||
networks:
|
||||
- test
|
||||
# platform: 'linux/amd64'
|
||||
depends_on:
|
||||
- redpanda
|
||||
ports:
|
||||
- "8083:8083"
|
||||
environment:
|
||||
CONNECT_CONFIGURATION: |
|
||||
key.converter=org.apache.kafka.connect.converters.ByteArrayConverter
|
||||
value.converter=org.apache.kafka.connect.converters.ByteArrayConverter
|
||||
group.id=connectors-cluster
|
||||
offset.storage.topic=_internal_connectors_offsets
|
||||
config.storage.topic=_internal_connectors_configs
|
||||
status.storage.topic=_internal_connectors_status
|
||||
config.storage.replication.factor=-1
|
||||
offset.storage.replication.factor=-1
|
||||
status.storage.replication.factor=-1
|
||||
offset.flush.interval.ms=1000
|
||||
producer.linger.ms=50
|
||||
producer.batch.size=131072
|
||||
CONNECT_BOOTSTRAP_SERVERS: redpanda:9092
|
||||
CONNECT_GC_LOG_ENABLED: "false"
|
||||
CONNECT_HEAP_OPTS: -Xms512M -Xmx512M
|
||||
CONNECT_LOG_LEVEL: info
|
||||
|
||||
test-pena-auth-db:
|
||||
container_name: test-pena-auth-db
|
||||
init: true
|
||||
image: "mongo:6.0.3"
|
||||
command: mongod --quiet --logpath /dev/null
|
||||
volumes:
|
||||
- test-mongodb:/data/db
|
||||
- test-mongoconfdb:/data/configdb
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: test
|
||||
MONGO_INITDB_ROOT_PASSWORD: test
|
||||
networks:
|
||||
- test
|
||||
|
||||
networks:
|
||||
test:
|
@ -24,11 +24,13 @@ services:
|
||||
- KAFKA_BROKERS=10.6.0.11:9092
|
||||
- KAFKA_TOPIC_TARIFF=tariffs
|
||||
|
||||
- AUTH_MICROSERVICE_USER_URL=https://admin.pena.digital/user
|
||||
- HUBADMIN_MICROSERVICE_TARIFF_URL=https://admin.pena.digital/strator/tariff
|
||||
- AUTH_MICROSERVICE_USER_URL=http://10.6.0.11:59300/user
|
||||
- HUBADMIN_MICROSERVICE_TARIFF_URL=http://10.6.0.11:59303/tariff
|
||||
- CURRENCY_MICROSERVICE_TRANSLATE_URL=http://10.6.0.11:3131/change
|
||||
- DISCOUNT_MICROSERVICE_GRPC_HOST=10.6.0.11:9001
|
||||
- PAYMENT_MICROSERVICE_GRPC_HOST=10.6.0.11:9085
|
||||
- VERIFICATION_MICROSERVICE_USER_URL=http://10.6.0.17:7035/verification
|
||||
- TEMPLATEGEN_MICROSERVICE_URL=10.6.0.17
|
||||
|
||||
- JWT_PUBLIC_KEY=$JWT_PUBLIC_KEY
|
||||
- JWT_ISSUER=pena-auth-service
|
||||
@ -37,9 +39,4 @@ services:
|
||||
- 8065:8065
|
||||
- 9065:9065
|
||||
networks:
|
||||
- marketplace_penahub_frontend
|
||||
- default
|
||||
|
||||
networks:
|
||||
marketplace_penahub_frontend:
|
||||
external: true
|
18
deployments/test/docker-compose.integration.yaml
Normal file
18
deployments/test/docker-compose.integration.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
integration:
|
||||
container_name: customer-integration
|
||||
image: golang:1
|
||||
volumes:
|
||||
- ../..:/app:ro,z
|
||||
working_dir: /app
|
||||
command: go test ./tests/integration/...
|
||||
environment:
|
||||
- CUSTOMER_SERVICE=customer-service:8000
|
||||
networks:
|
||||
- test_test
|
||||
|
||||
networks:
|
||||
test_test:
|
||||
external: true
|
@ -7,7 +7,7 @@ services:
|
||||
customer-service:
|
||||
container_name: customer-service
|
||||
build:
|
||||
context: ../../.
|
||||
context: ../..
|
||||
dockerfile: Dockerfile
|
||||
target: test
|
||||
env_file:
|
||||
@ -40,17 +40,11 @@ services:
|
||||
- 9092:9000
|
||||
depends_on:
|
||||
customer-db:
|
||||
condition: service_healthy
|
||||
condition: service_started
|
||||
customer-migration:
|
||||
condition: service_completed_successfully
|
||||
redpanda:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: wget -q --spider http://localhost:8000/health
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
networks:
|
||||
- test
|
||||
|
||||
@ -75,12 +69,6 @@ services:
|
||||
MONGO_INITDB_ROOT_PASSWORD: test
|
||||
ports:
|
||||
- "27024:27017"
|
||||
healthcheck:
|
||||
test: mongosh --quiet --eval "db.adminCommand('ping')" >/dev/null
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
networks:
|
||||
- test
|
||||
|
||||
@ -150,38 +138,42 @@ services:
|
||||
networks:
|
||||
- test
|
||||
depends_on:
|
||||
- redpanda
|
||||
connect:
|
||||
condition: service_started
|
||||
redpanda:
|
||||
condition: service_healthy
|
||||
|
||||
connect:
|
||||
tty: true
|
||||
image: docker.redpanda.com/redpandadata/connectors:latest
|
||||
hostname: connect
|
||||
container_name: connect
|
||||
networks:
|
||||
- test
|
||||
# hostname: connect
|
||||
container_name: customer-connect
|
||||
# platform: 'linux/amd64'
|
||||
depends_on:
|
||||
- redpanda
|
||||
redpanda:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "8083:8083"
|
||||
environment:
|
||||
CONNECT_CONFIGURATION: |
|
||||
key.converter=org.apache.kafka.connect.converters.ByteArrayConverter
|
||||
value.converter=org.apache.kafka.connect.converters.ByteArrayConverter
|
||||
group.id=connectors-cluster
|
||||
offset.storage.topic=_internal_connectors_offsets
|
||||
config.storage.topic=_internal_connectors_configs
|
||||
status.storage.topic=_internal_connectors_status
|
||||
config.storage.replication.factor=-1
|
||||
offset.storage.replication.factor=-1
|
||||
status.storage.replication.factor=-1
|
||||
offset.flush.interval.ms=1000
|
||||
producer.linger.ms=50
|
||||
producer.batch.size=131072
|
||||
key.converter=org.apache.kafka.connect.converters.ByteArrayConverter
|
||||
value.converter=org.apache.kafka.connect.converters.ByteArrayConverter
|
||||
group.id=connectors-cluster
|
||||
offset.storage.topic=_internal_connectors_offsets
|
||||
config.storage.topic=_internal_connectors_configs
|
||||
status.storage.topic=_internal_connectors_status
|
||||
config.storage.replication.factor=-1
|
||||
offset.storage.replication.factor=-1
|
||||
status.storage.replication.factor=-1
|
||||
offset.flush.interval.ms=1000
|
||||
producer.linger.ms=50
|
||||
producer.batch.size=131072
|
||||
CONNECT_BOOTSTRAP_SERVERS: redpanda:9092
|
||||
CONNECT_GC_LOG_ENABLED: "false"
|
||||
CONNECT_HEAP_OPTS: -Xms512M -Xmx512M
|
||||
CONNECT_LOG_LEVEL: info
|
||||
networks:
|
||||
- test
|
||||
|
||||
networks:
|
||||
test:
|
||||
|
6
go.mod
6
go.mod
@ -16,7 +16,7 @@ require (
|
||||
github.com/twmb/franz-go v1.13.6
|
||||
github.com/twmb/franz-go/pkg/kadm v1.8.1
|
||||
go.mongodb.org/mongo-driver v1.11.4
|
||||
go.uber.org/zap v1.24.0
|
||||
go.uber.org/zap v1.26.0
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923
|
||||
google.golang.org/grpc v1.53.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
@ -24,7 +24,6 @@ require (
|
||||
|
||||
require (
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/benbjohnson/clock v1.3.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
@ -55,11 +54,10 @@ require (
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
|
16
go.sum
16
go.sum
@ -4,8 +4,6 @@ github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMz
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/benbjohnson/clock v1.3.3 h1:g+rSsSaAzhHJYcIQE78hJ3AhyjjtQvleKDjlhdBnIhc=
|
||||
github.com/benbjohnson/clock v1.3.3/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
@ -149,6 +147,7 @@ github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
@ -167,16 +166,15 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
go.mongodb.org/mongo-driver v1.11.4 h1:4ayjakA013OdpGyL2K3ZqylTac/rMjrJOMZ1EHizXas=
|
||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@ -209,8 +207,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -13,14 +13,18 @@ type ClientsDeps struct {
|
||||
CurrencyURL *models.CurrencyMicroserviceURL
|
||||
DiscountServiceConfiguration *models.DiscountMicroserviceConfiguration
|
||||
PaymentServiceConfiguration *models.PaymentMicroserviceConfiguration
|
||||
VerificationURL *models.VerificationMicroserviceURL
|
||||
TemplategenURL *models.TemplategenMicroserviceURL
|
||||
}
|
||||
|
||||
type Clients struct {
|
||||
AuthClient *client.AuthClient
|
||||
HubadminClient *client.HubadminClient
|
||||
CurrencyClient *client.CurrencyClient
|
||||
DiscountClient *client.DiscountClient
|
||||
PaymentClient *client.PaymentClient
|
||||
AuthClient *client.AuthClient
|
||||
HubadminClient *client.HubadminClient
|
||||
CurrencyClient *client.CurrencyClient
|
||||
DiscountClient *client.DiscountClient
|
||||
PaymentClient *client.PaymentClient
|
||||
VerificationClient *client.VerificationClient
|
||||
TemplateClient *client.TemplateClient
|
||||
}
|
||||
|
||||
func NewClients(deps ClientsDeps) *Clients {
|
||||
@ -45,5 +49,13 @@ func NewClients(deps ClientsDeps) *Clients {
|
||||
Logger: deps.Logger,
|
||||
PaymentServiceHost: deps.PaymentServiceConfiguration.HostGRPC,
|
||||
}),
|
||||
VerificationClient: client.NewVerificationClient(client.VerificationClientDeps{
|
||||
Logger: deps.Logger,
|
||||
URLs: deps.VerificationURL,
|
||||
}),
|
||||
TemplateClient: client.NewTemplateClient(client.TemplateClientDeps{
|
||||
Logger: deps.Logger,
|
||||
URLs: deps.TemplategenURL,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ func TestNewClients(t *testing.T) {
|
||||
CurrencyURL: &models.CurrencyMicroserviceURL{},
|
||||
DiscountServiceConfiguration: &models.DiscountMicroserviceConfiguration{HostGRPC: "host"},
|
||||
PaymentServiceConfiguration: &models.PaymentMicroserviceConfiguration{HostGRPC: "host"},
|
||||
VerificationURL: &models.VerificationMicroserviceURL{Verification: ""},
|
||||
TemplategenURL: &models.TemplategenMicroserviceURL{Templategen: ""},
|
||||
})
|
||||
|
||||
assert.NotNil(t, clients)
|
||||
|
@ -90,6 +90,16 @@ func setDefaultTestingENV(t *testing.T) *models.Config {
|
||||
PaymentMicroservice: models.PaymentMicroserviceConfiguration{
|
||||
HostGRPC: "domen",
|
||||
},
|
||||
VerificationMicroservice: models.VerificationMicroserviceConfiguration{
|
||||
URL: models.VerificationMicroserviceURL{
|
||||
Verification: "domen",
|
||||
},
|
||||
},
|
||||
TemplategenMicroserviceURL: models.TemplategenMicroserviceConfiguration{
|
||||
URL: models.TemplategenMicroserviceURL{
|
||||
Templategen: "domen",
|
||||
},
|
||||
},
|
||||
JWT: models.JWTConfiguration{
|
||||
PrivateKey: "jwt private key",
|
||||
PublicKey: "jwt public key",
|
||||
@ -126,6 +136,8 @@ func setDefaultTestingENV(t *testing.T) *models.Config {
|
||||
t.Setenv("CURRENCY_MICROSERVICE_TRANSLATE_URL", defaultConfiguration.Service.CurrencyMicroservice.URL.Translate)
|
||||
t.Setenv("DISCOUNT_MICROSERVICE_GRPC_HOST", defaultConfiguration.Service.DiscountMicroservice.HostGRPC)
|
||||
t.Setenv("PAYMENT_MICROSERVICE_GRPC_HOST", defaultConfiguration.Service.PaymentMicroservice.HostGRPC)
|
||||
t.Setenv("VERIFICATION_MICROSERVICE_USER_URL", defaultConfiguration.Service.VerificationMicroservice.URL.Verification)
|
||||
t.Setenv("TEMPLATEGEN_MICROSERVICE_URL", defaultConfiguration.Service.TemplategenMicroserviceURL.URL.Templategen)
|
||||
|
||||
t.Setenv("MONGO_HOST", defaultConfiguration.Database.Host)
|
||||
t.Setenv("MONGO_PORT", defaultConfiguration.Database.Port)
|
||||
|
@ -25,6 +25,8 @@ func TestNewControllers(t *testing.T) {
|
||||
CurrencyURL: &models.CurrencyMicroserviceURL{},
|
||||
DiscountServiceConfiguration: &models.DiscountMicroserviceConfiguration{HostGRPC: "host"},
|
||||
PaymentServiceConfiguration: &models.PaymentMicroserviceConfiguration{HostGRPC: "host"},
|
||||
VerificationURL: &models.VerificationMicroserviceURL{Verification: ""},
|
||||
TemplategenURL: &models.TemplategenMicroserviceURL{Templategen: ""},
|
||||
})
|
||||
|
||||
repositories := initialize.NewRepositories(initialize.RepositoriesDeps{
|
||||
|
@ -36,6 +36,7 @@ func NewServices(deps ServicesDeps) *Services {
|
||||
historyService := history.New(history.Deps{
|
||||
Logger: deps.Logger,
|
||||
Repository: deps.Repositories.HistoryRepository,
|
||||
AuthClient: deps.Clients.AuthClient,
|
||||
})
|
||||
|
||||
walletService := wallet.New(wallet.Deps{
|
||||
|
@ -25,6 +25,8 @@ func TestNewServices(t *testing.T) {
|
||||
CurrencyURL: &models.CurrencyMicroserviceURL{},
|
||||
DiscountServiceConfiguration: &models.DiscountMicroserviceConfiguration{HostGRPC: "host"},
|
||||
PaymentServiceConfiguration: &models.PaymentMicroserviceConfiguration{HostGRPC: "host"},
|
||||
VerificationURL: &models.VerificationMicroserviceURL{Verification: ""},
|
||||
TemplategenURL: &models.TemplategenMicroserviceURL{Templategen: ""},
|
||||
})
|
||||
|
||||
brokers := initialize.NewBrokers(initialize.BrokersDeps{
|
||||
|
88
internal/interface/client/templategen.go
Normal file
88
internal/interface/client/templategen.go
Normal file
@ -0,0 +1,88 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
|
||||
)
|
||||
|
||||
type TemplateClientDeps struct {
|
||||
Logger *zap.Logger
|
||||
URLs *models.TemplategenMicroserviceURL
|
||||
}
|
||||
|
||||
type TemplateClient struct {
|
||||
logger *zap.Logger
|
||||
urls *models.TemplategenMicroserviceURL
|
||||
}
|
||||
|
||||
func NewTemplateClient(deps TemplateClientDeps) *TemplateClient {
|
||||
if deps.Logger == nil {
|
||||
log.Panicln("logger is nil on <NewTemplateClient>")
|
||||
}
|
||||
|
||||
if deps.URLs == nil {
|
||||
log.Panicln("urls is nil on <NewTemplateClient>")
|
||||
}
|
||||
|
||||
return &TemplateClient{
|
||||
logger: deps.Logger,
|
||||
urls: deps.URLs,
|
||||
}
|
||||
}
|
||||
|
||||
func (receiver *TemplateClient) SendData(ctx context.Context, data models.RespGeneratorService, fileContents []byte, email string) errors.Error {
|
||||
tmplURL := receiver.urls.Templategen
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
err := writer.WriteField("email", email)
|
||||
if err != nil {
|
||||
return errors.New(err, errors.ErrInternalError)
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return errors.New(err, errors.ErrInternalError)
|
||||
}
|
||||
err = writer.WriteField("data", string(jsonData))
|
||||
if err != nil {
|
||||
return errors.New(err, errors.ErrInternalError)
|
||||
}
|
||||
|
||||
fileWriter, err := writer.CreateFormFile("file", "report.docx")
|
||||
if err != nil {
|
||||
return errors.New(err, errors.ErrInternalError)
|
||||
}
|
||||
_, err = fileWriter.Write(fileContents)
|
||||
if err != nil {
|
||||
return errors.New(err, errors.ErrInternalError)
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return errors.New(err, errors.ErrInternalError)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, tmplURL, body)
|
||||
if err != nil {
|
||||
return errors.New(err, errors.ErrInternalError)
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.New(err, errors.ErrInternalError)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
58
internal/interface/client/verification.go
Normal file
58
internal/interface/client/verification.go
Normal file
@ -0,0 +1,58 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/client"
|
||||
)
|
||||
|
||||
type VerificationClientDeps struct {
|
||||
Logger *zap.Logger
|
||||
URLs *models.VerificationMicroserviceURL
|
||||
}
|
||||
|
||||
type VerificationClient struct {
|
||||
logger *zap.Logger
|
||||
urls *models.VerificationMicroserviceURL
|
||||
}
|
||||
|
||||
func NewVerificationClient(deps VerificationClientDeps) *VerificationClient {
|
||||
if deps.Logger == nil {
|
||||
log.Panicln("logger is nil on <NewVerificationClient>")
|
||||
}
|
||||
|
||||
if deps.URLs == nil {
|
||||
log.Panicln("urls is nil on <NewVerificationClient>")
|
||||
}
|
||||
|
||||
return &VerificationClient{
|
||||
logger: deps.Logger,
|
||||
urls: deps.URLs,
|
||||
}
|
||||
}
|
||||
|
||||
func (receiver *VerificationClient) GetVerification(ctx context.Context, userID string) (*models.Verification, errors.Error) {
|
||||
verifURL, err := url.JoinPath(receiver.urls.Verification, userID)
|
||||
if err != nil {
|
||||
return nil, errors.New(
|
||||
fmt.Errorf("failed to join path on <GetVerification> of <VerificationClient>: %w", err),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
|
||||
response, err := client.Get[models.Verification, models.FastifyError](ctx, &client.RequestSettings{
|
||||
URL: verifURL,
|
||||
Headers: map[string]string{"Content-Type": "application/json"},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.New(err, errors.ErrInternalError)
|
||||
}
|
||||
|
||||
return response.Body, nil
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package history
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
@ -38,8 +39,17 @@ func New(deps Deps) *Controller {
|
||||
}
|
||||
|
||||
func (receiver *Controller) GetHistoryList(ctx echo.Context, params swagger.GetHistoryParams) error {
|
||||
var userID string
|
||||
|
||||
if params.AccountID != nil && *params.AccountID != "" {
|
||||
userID = *params.AccountID
|
||||
} else {
|
||||
userID, _ = ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
||||
}
|
||||
|
||||
histories, err := receiver.historyService.GetHistoryList(ctx.Request().Context(), &history.GetHistories{
|
||||
Type: params.Type,
|
||||
Type: params.Type,
|
||||
UserID: userID,
|
||||
Pagination: &models.Pagination{
|
||||
Page: int64(*params.Page),
|
||||
Limit: int64(*params.Limit),
|
||||
@ -52,3 +62,74 @@ func (receiver *Controller) GetHistoryList(ctx echo.Context, params swagger.GetH
|
||||
|
||||
return ctx.JSON(http.StatusOK, histories)
|
||||
}
|
||||
|
||||
// TODO:tests.
|
||||
func (receiver *Controller) GetRecentTariffs(ctx echo.Context) error {
|
||||
userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string)
|
||||
if !ok {
|
||||
receiver.logger.Error("failed to convert jwt payload to string on <GetRecentTariffs> of <HistoryController>")
|
||||
return errors.HTTP(ctx, errors.New(
|
||||
fmt.Errorf("failed to convert jwt payload to string: %s", userID),
|
||||
errors.ErrInvalidArgs,
|
||||
))
|
||||
}
|
||||
|
||||
tariffs, err := receiver.historyService.GetRecentTariffs(ctx.Request().Context(), userID)
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to get recent tariffs on <GetRecentTariffs> of <HistoryController>",
|
||||
zap.String("userId", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return errors.HTTP(ctx, err)
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusOK, tariffs)
|
||||
}
|
||||
|
||||
// TODO:tests.
|
||||
func (receiver *Controller) SendReport(ctx echo.Context) error {
|
||||
historyID := ctx.Param("id")
|
||||
|
||||
err := receiver.historyService.GetHistoryByID(ctx.Request().Context(), historyID)
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to send report on <SendReport> of <HistoryController>", zap.Error(err))
|
||||
return errors.HTTP(ctx, err)
|
||||
}
|
||||
|
||||
return ctx.NoContent(http.StatusOK)
|
||||
}
|
||||
|
||||
// TODO:tests.
|
||||
func (receiver *Controller) CalculateLTV(ctx echo.Context) error {
|
||||
var req swagger.CalculateLTVJSONBody
|
||||
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
receiver.logger.Error("failed to bind request", zap.Error(err))
|
||||
return errors.HTTP(ctx, errors.New(
|
||||
fmt.Errorf("failed to bind request: %s", err),
|
||||
errors.ErrInvalidArgs,
|
||||
))
|
||||
}
|
||||
|
||||
if req.From > req.To && req.To != 0 {
|
||||
receiver.logger.Error("From timestamp must be less than To timestamp unless To is 0")
|
||||
return errors.HTTP(ctx, errors.New(
|
||||
fmt.Errorf("From timestamp must be less than To timestamp unless To is 0"),
|
||||
errors.ErrInvalidArgs,
|
||||
))
|
||||
}
|
||||
|
||||
ltv, err := receiver.historyService.CalculateCustomerLTV(ctx.Request().Context(), req.From, req.To)
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to calculate LTV", zap.Error(err))
|
||||
return errors.HTTP(ctx, err)
|
||||
}
|
||||
|
||||
response := struct {
|
||||
LTV int64 `json:"LTV"`
|
||||
}{
|
||||
LTV: ltv,
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
@ -4,12 +4,15 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/fields"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/service/history"
|
||||
mongoWrapper "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/mongo"
|
||||
@ -80,6 +83,9 @@ func (receiver *HistoryRepository) FindMany(ctx context.Context, dto *history.Ge
|
||||
|
||||
findOptions.SetSkip((dto.Pagination.Page - 1) * dto.Pagination.Limit)
|
||||
findOptions.SetLimit(dto.Pagination.Limit)
|
||||
findOptions.SetSort(bson.D{{
|
||||
Key: "createdAt", Value: -1,
|
||||
}})
|
||||
|
||||
histories, err := mongoWrapper.Find[models.History](ctx, &mongoWrapper.RequestSettings{
|
||||
Driver: receiver.mongoDB,
|
||||
@ -118,3 +124,218 @@ func (receiver *HistoryRepository) CountAll(ctx context.Context, dto *history.Ge
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// TODO:tests
|
||||
// GetRecentTariffs method for processing a user request with data aggregation with a limit of 100 sorted in descending order.
|
||||
func (receiver *HistoryRepository) GetRecentTariffs(ctx context.Context, userID string) ([]models.TariffID, errors.Error) {
|
||||
matchStage := bson.D{
|
||||
{Key: "$match", Value: bson.D{
|
||||
{Key: fields.History.UserID, Value: userID},
|
||||
{Key: fields.History.IsDeleted, Value: false},
|
||||
{Key: fields.History.Type, Value: models.CustomerHistoryKeyPayCart},
|
||||
}},
|
||||
}
|
||||
|
||||
unwindStage := bson.D{
|
||||
{Key: "$unwind", Value: bson.D{
|
||||
{Key: "path", Value: "$rawDetails.tariffs"},
|
||||
}},
|
||||
}
|
||||
|
||||
groupStage := bson.D{
|
||||
{Key: "$group", Value: bson.D{
|
||||
{Key: "_id", Value: "$rawDetails.tariffs.id"},
|
||||
}},
|
||||
}
|
||||
|
||||
sortStage := bson.D{
|
||||
{Key: "$sort", Value: bson.D{
|
||||
{Key: "createdAt", Value: -1},
|
||||
}},
|
||||
}
|
||||
|
||||
limitStage := bson.D{
|
||||
{Key: "$limit", Value: 100},
|
||||
}
|
||||
|
||||
cursor, err := receiver.mongoDB.Aggregate(ctx, mongo.Pipeline{matchStage, unwindStage, sortStage, groupStage, limitStage})
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to get recent tariffs on <GetRecentTariffs> of <HistoryRepository>",
|
||||
zap.String("userId", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, errors.New(
|
||||
fmt.Errorf("failed to get recent tariffs on <GetRecentTariffs> of <HistoryRepository>: %w", err),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
|
||||
var result []models.TariffID
|
||||
if err := cursor.All(ctx, &result); err != nil {
|
||||
receiver.logger.Error("failed to decode recent tariffs on <GetRecentTariffs> of <HistoryRepository>",
|
||||
zap.String("userId", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, errors.New(
|
||||
fmt.Errorf("failed to decode recent tariffs on <GetRecentTariffs> of <HistoryRepository>: %w", err),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// TODO:tests.
|
||||
func (receiver *HistoryRepository) GetHistoryByID(ctx context.Context, historyID string) (*models.ReportHistory, errors.Error) {
|
||||
history := &models.ReportHistory{}
|
||||
err := receiver.mongoDB.FindOne(ctx, bson.M{"_id": historyID}).Decode(history)
|
||||
if err != nil {
|
||||
receiver.logger.Error(
|
||||
"failed to find by id in <GetHistoryById> of <HistoryRepository>",
|
||||
zap.String("historyID", historyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, errors.New(
|
||||
fmt.Errorf("history not found with ID: %s", historyID),
|
||||
errors.ErrNotFound,
|
||||
)
|
||||
}
|
||||
|
||||
return nil, errors.New(
|
||||
fmt.Errorf("failed to find by id: %w", err),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
|
||||
return history, nil
|
||||
}
|
||||
|
||||
// TODO:tests.
|
||||
func (receiver *HistoryRepository) GetDocNumber(ctx context.Context, userID string) (map[string]int, errors.Error) {
|
||||
findOptions := options.Find()
|
||||
findOptions.SetSort(bson.D{{Key: "createdAt", Value: 1}})
|
||||
|
||||
filter := bson.M{
|
||||
fields.History.UserID: userID,
|
||||
}
|
||||
|
||||
cursor, err := receiver.mongoDB.Find(ctx, filter, findOptions)
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to get DocNumber list on <GetDocNumber> of <HistoryRepository>",
|
||||
zap.String("userId", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, errors.New(
|
||||
fmt.Errorf("failed to get DocNumber list on <GetDocNumber> of <HistoryRepository>: %w", err),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := cursor.Close(ctx); err != nil {
|
||||
receiver.logger.Error("failed to close cursor on <GetDocNumber> of <HistoryRepository>",
|
||||
zap.String("userId", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}()
|
||||
|
||||
result := make(map[string]int)
|
||||
var count int
|
||||
for cursor.Next(ctx) {
|
||||
var history models.History
|
||||
if err := cursor.Decode(&history); err != nil {
|
||||
receiver.logger.Error("failed to decode history on <GetDocNumber> of <HistoryRepository>",
|
||||
zap.String("userId", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, errors.New(
|
||||
fmt.Errorf("failed to decode history on <GetDocNumber> of <HistoryRepository>: %w", err),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
|
||||
result[history.ID] = count
|
||||
count++
|
||||
}
|
||||
|
||||
if err := cursor.Err(); err != nil {
|
||||
receiver.logger.Error("cursor error on <GetDocNumber> of <HistoryRepository>",
|
||||
zap.String("userId", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, errors.New(
|
||||
fmt.Errorf("cursor error on <GetDocNumber> of <HistoryRepository>: %w", err),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (receiver *HistoryRepository) CalculateCustomerLTV(ctx context.Context, from, to int64) (int64, errors.Error) {
|
||||
timeFilter := bson.M{}
|
||||
if from != 0 || to != 0 {
|
||||
timeRange := bson.M{}
|
||||
if from != 0 {
|
||||
timeRange["$gte"] = time.Unix(from, 0).UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
if to != 0 {
|
||||
timeRange["$lte"] = time.Unix(to, 0).UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
timeFilter["createdAt"] = timeRange
|
||||
}
|
||||
pipeline := mongo.Pipeline{
|
||||
{{Key: "$match", Value: bson.M{"key": models.CustomerHistoryKeyPayCart, "isDeleted": false}}},
|
||||
{{Key: "$match", Value: timeFilter}},
|
||||
{{Key: "$group", Value: bson.M{
|
||||
"_id": "$userId",
|
||||
"firstPayment": bson.M{"$min": "$createdAt"},
|
||||
"lastPayment": bson.M{"$max": "$createdAt"},
|
||||
}}},
|
||||
{{Key: "$project", Value: bson.M{
|
||||
"lifeTimeInDays": bson.M{"$divide": []interface{}{
|
||||
bson.M{"$subtract": []interface{}{bson.M{"$toDate": "$lastPayment"}, bson.M{"$toDate": "$firstPayment"}}},
|
||||
86400000,
|
||||
}},
|
||||
}}},
|
||||
{{Key: "$group", Value: bson.M{
|
||||
"_id": nil,
|
||||
"averageLTV": bson.M{"$avg": "$lifeTimeInDays"},
|
||||
}}},
|
||||
}
|
||||
|
||||
cursor, err := receiver.mongoDB.Aggregate(ctx, pipeline)
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to calculate customer LTV <CalculateCustomerLTV> of <HistoryRepository>",
|
||||
zap.Error(err),
|
||||
)
|
||||
return 0, errors.New(
|
||||
fmt.Errorf("failed to calculate customer LTV <CalculateCustomerLTV> of <HistoryRepository>: %w", err),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
defer func() {
|
||||
if err := cursor.Close(ctx); err != nil {
|
||||
receiver.logger.Error("failed to close cursor", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
var results []struct{ AverageLTV float64 }
|
||||
if err := cursor.All(ctx, &results); err != nil {
|
||||
receiver.logger.Error("failed to getting result LTV <CalculateCustomerLTV> of <HistoryRepository>",
|
||||
zap.Error(err),
|
||||
)
|
||||
return 0, errors.New(
|
||||
fmt.Errorf("failed to getting result LTV <CalculateCustomerLTV> of <HistoryRepository>: %w", err),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
if len(results) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
averageLTV := int64(results[0].AverageLTV)
|
||||
return averageLTV, nil
|
||||
}
|
||||
|
@ -207,8 +207,9 @@ func (api *API2) PaginationAccounts(ctx echo.Context, params PaginationAccountsP
|
||||
return api.error(ctx, http.StatusInternalServerError, "default values missing for PaginationAccounts")
|
||||
}
|
||||
|
||||
page := int64(max(*params.Page, 1))
|
||||
limit := min(int64(max(*params.Limit, 1)), models.DefaultLimit)
|
||||
page := int64(math.Max(float64(*params.Page), 1))
|
||||
limit := int64(math.Max(float64(*params.Limit), 1))
|
||||
limit = int64(math.Min(float64(limit), float64(models.DefaultLimit)))
|
||||
|
||||
count, err := api.account.CountAll(ctx.Request().Context())
|
||||
if err != nil {
|
||||
@ -592,3 +593,18 @@ func (api *API2) ChangeCurrency(ctx echo.Context) error {
|
||||
|
||||
return ctx.JSON(http.StatusOK, updatedAccount)
|
||||
}
|
||||
|
||||
func (api *API2) CalculateLTV(ctx echo.Context) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (api *API2) GetRecentTariffs(ctx echo.Context) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (api *API2) SendReport(ctx echo.Context) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
@ -59,12 +59,18 @@ type ServerInterface interface {
|
||||
// обновляет список одобренных валют
|
||||
// (PUT /currencies)
|
||||
UpdateCurrencies(ctx echo.Context) error
|
||||
// Health Check
|
||||
// (GET /health)
|
||||
GetHealth(ctx echo.Context) error
|
||||
// Получение лога событий связанных с аккаунтом
|
||||
// (GET /history)
|
||||
GetHistory(ctx echo.Context, params GetHistoryParams) error
|
||||
// Расчет среднего времени жизни платящего клиента (LTV)
|
||||
// (POST /history/ltv)
|
||||
CalculateLTV(ctx echo.Context) error
|
||||
// Получение недавних тарифов
|
||||
// (GET /recent)
|
||||
GetRecentTariffs(ctx echo.Context) error
|
||||
// отправить акт проделанных работ на почту
|
||||
// (POST /sendReport)
|
||||
SendReport(ctx echo.Context) error
|
||||
// Изменить валюту кошелька
|
||||
// (PATCH /wallet)
|
||||
ChangeCurrency(ctx echo.Context) error
|
||||
@ -272,15 +278,6 @@ func (w *ServerInterfaceWrapper) UpdateCurrencies(ctx echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetHealth converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) GetHealth(ctx echo.Context) error {
|
||||
var err error
|
||||
|
||||
// Invoke the callback with all the unmarshaled arguments
|
||||
err = w.Handler.GetHealth(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetHistory converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) GetHistory(ctx echo.Context) error {
|
||||
var err error
|
||||
@ -310,11 +307,51 @@ func (w *ServerInterfaceWrapper) GetHistory(ctx echo.Context) error {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter type: %s", err))
|
||||
}
|
||||
|
||||
// ------------- Optional query parameter "accountID" -------------
|
||||
|
||||
err = runtime.BindQueryParameter("form", false, false, "accountID", ctx.QueryParams(), ¶ms.AccountID)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter accountID: %s", err))
|
||||
}
|
||||
|
||||
// Invoke the callback with all the unmarshaled arguments
|
||||
err = w.Handler.GetHistory(ctx, params)
|
||||
return err
|
||||
}
|
||||
|
||||
// CalculateLTV converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) CalculateLTV(ctx echo.Context) error {
|
||||
var err error
|
||||
|
||||
ctx.Set(BearerScopes, []string{})
|
||||
|
||||
// Invoke the callback with all the unmarshaled arguments
|
||||
err = w.Handler.CalculateLTV(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetRecentTariffs converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) GetRecentTariffs(ctx echo.Context) error {
|
||||
var err error
|
||||
|
||||
ctx.Set(BearerScopes, []string{})
|
||||
|
||||
// Invoke the callback with all the unmarshaled arguments
|
||||
err = w.Handler.GetRecentTariffs(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendReport converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) SendReport(ctx echo.Context) error {
|
||||
var err error
|
||||
|
||||
ctx.Set(BearerScopes, []string{})
|
||||
|
||||
// Invoke the callback with all the unmarshaled arguments
|
||||
err = w.Handler.SendReport(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ChangeCurrency converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) ChangeCurrency(ctx echo.Context) error {
|
||||
var err error
|
||||
@ -378,8 +415,10 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||
router.POST(baseURL+"/cart/pay", wrapper.PayCart)
|
||||
router.GET(baseURL+"/currencies", wrapper.GetCurrencies)
|
||||
router.PUT(baseURL+"/currencies", wrapper.UpdateCurrencies)
|
||||
router.GET(baseURL+"/health", wrapper.GetHealth)
|
||||
router.GET(baseURL+"/history", wrapper.GetHistory)
|
||||
router.POST(baseURL+"/history/ltv", wrapper.CalculateLTV)
|
||||
router.GET(baseURL+"/recent", wrapper.GetRecentTariffs)
|
||||
router.POST(baseURL+"/sendReport", wrapper.SendReport)
|
||||
router.PATCH(baseURL+"/wallet", wrapper.ChangeCurrency)
|
||||
router.POST(baseURL+"/wallet", wrapper.RequestMoney)
|
||||
|
||||
@ -388,72 +427,81 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"WKMkDmpVAAA=",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
173
internal/interface/swagger/api.go
Normal file
173
internal/interface/swagger/api.go
Normal file
@ -0,0 +1,173 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type accountController interface {
|
||||
RemoveAccount(ctx echo.Context) error
|
||||
GetAccount(ctx echo.Context) error
|
||||
CreateAccount(ctx echo.Context) error
|
||||
RemoveDirectAccount(ctx echo.Context, userID string) error
|
||||
GetDirectAccount(ctx echo.Context, userID string) error
|
||||
GetAccounts(echo.Context, PaginationAccountsParams) error
|
||||
SetVerificationStatus(ctx echo.Context, userID string) error
|
||||
UpdateAccountName(ctx echo.Context) error
|
||||
}
|
||||
|
||||
type currencyController interface {
|
||||
GetCurrencies(ctx echo.Context) error
|
||||
PutCurrencies(ctx echo.Context) error
|
||||
}
|
||||
|
||||
type cartController interface {
|
||||
Remove(echo.Context, RemoveFromCartParams) error
|
||||
Add(echo.Context, Add2cartParams) error
|
||||
Pay(echo.Context) error
|
||||
}
|
||||
|
||||
type walletController interface {
|
||||
ChangeCurrency(ctx echo.Context) error
|
||||
GetPaymentLink(ctx echo.Context) error
|
||||
}
|
||||
|
||||
type historyController interface {
|
||||
GetHistoryList(ctx echo.Context, params GetHistoryParams) error
|
||||
GetRecentTariffs(ctx echo.Context) error
|
||||
SendReport(ctx echo.Context) error
|
||||
CalculateLTV(ctx echo.Context) error
|
||||
}
|
||||
|
||||
type Deps struct {
|
||||
AccountController accountController
|
||||
CurrencyController currencyController
|
||||
CartController cartController
|
||||
WalletController walletController
|
||||
HistoryController historyController
|
||||
}
|
||||
|
||||
type API struct {
|
||||
accountController accountController
|
||||
currencyController currencyController
|
||||
cartController cartController
|
||||
walletController walletController
|
||||
historyController historyController
|
||||
}
|
||||
|
||||
func New(deps Deps) *API {
|
||||
if deps.AccountController == nil {
|
||||
log.Panicln("AccountController is nil on <New (API)>")
|
||||
}
|
||||
|
||||
if deps.CurrencyController == nil {
|
||||
log.Panicln("currencyController is nil on <New (API)>")
|
||||
}
|
||||
|
||||
if deps.CartController == nil {
|
||||
log.Panicln("cartController is nil on <New (API)>")
|
||||
}
|
||||
|
||||
if deps.WalletController == nil {
|
||||
log.Panicln("walletController is nil on <New (API)>")
|
||||
}
|
||||
|
||||
if deps.HistoryController == nil {
|
||||
log.Panicln("historyController is nil on <New (API)>")
|
||||
}
|
||||
|
||||
return &API{
|
||||
accountController: deps.AccountController,
|
||||
currencyController: deps.CurrencyController,
|
||||
cartController: deps.CartController,
|
||||
walletController: deps.WalletController,
|
||||
historyController: deps.HistoryController,
|
||||
}
|
||||
}
|
||||
|
||||
// Account
|
||||
|
||||
func (receiver *API) DeleteAccount(ctx echo.Context) error {
|
||||
return receiver.accountController.RemoveAccount(ctx)
|
||||
}
|
||||
|
||||
func (receiver *API) ChangeAccount(ctx echo.Context) error {
|
||||
return receiver.accountController.UpdateAccountName(ctx)
|
||||
}
|
||||
|
||||
func (receiver *API) SetAccountVerificationStatus(ctx echo.Context, userID string) error {
|
||||
return receiver.accountController.SetVerificationStatus(ctx, userID)
|
||||
}
|
||||
|
||||
func (receiver *API) GetAccount(ctx echo.Context) error {
|
||||
return receiver.accountController.GetAccount(ctx)
|
||||
}
|
||||
|
||||
func (receiver *API) AddAccount(ctx echo.Context) error {
|
||||
return receiver.accountController.CreateAccount(ctx)
|
||||
}
|
||||
|
||||
func (receiver *API) DeleteDirectAccount(ctx echo.Context, userID string) error {
|
||||
return receiver.accountController.RemoveDirectAccount(ctx, userID)
|
||||
}
|
||||
|
||||
func (receiver *API) GetDirectAccount(ctx echo.Context, userID string) error {
|
||||
return receiver.accountController.GetDirectAccount(ctx, userID)
|
||||
}
|
||||
|
||||
func (receiver *API) PaginationAccounts(ctx echo.Context, params PaginationAccountsParams) error {
|
||||
return receiver.accountController.GetAccounts(ctx, params)
|
||||
}
|
||||
|
||||
// Cart
|
||||
|
||||
func (receiver *API) RemoveFromCart(ctx echo.Context, params RemoveFromCartParams) error {
|
||||
return receiver.cartController.Remove(ctx, params)
|
||||
}
|
||||
|
||||
func (receiver *API) Add2cart(ctx echo.Context, params Add2cartParams) error {
|
||||
return receiver.cartController.Add(ctx, params)
|
||||
}
|
||||
|
||||
func (receiver *API) PayCart(ctx echo.Context) error {
|
||||
return receiver.cartController.Pay(ctx)
|
||||
}
|
||||
|
||||
// Currency
|
||||
|
||||
func (receiver *API) GetCurrencies(ctx echo.Context) error {
|
||||
return receiver.currencyController.GetCurrencies(ctx)
|
||||
}
|
||||
|
||||
func (receiver *API) UpdateCurrencies(ctx echo.Context) error {
|
||||
return receiver.currencyController.PutCurrencies(ctx)
|
||||
}
|
||||
|
||||
// History
|
||||
|
||||
func (receiver *API) GetHistory(ctx echo.Context, params GetHistoryParams) error {
|
||||
return receiver.historyController.GetHistoryList(ctx, params)
|
||||
}
|
||||
|
||||
func (receiver *API) GetRecentTariffs(ctx echo.Context) error {
|
||||
return receiver.historyController.GetRecentTariffs(ctx)
|
||||
}
|
||||
|
||||
func (receiver *API) SendReport(ctx echo.Context) error {
|
||||
return receiver.historyController.SendReport(ctx)
|
||||
}
|
||||
|
||||
func (receiver *API) CalculateLTV(ctx echo.Context) error {
|
||||
return receiver.historyController.CalculateLTV(ctx)
|
||||
}
|
||||
|
||||
// Wallet
|
||||
|
||||
func (receiver *API) RequestMoney(ctx echo.Context) error {
|
||||
return receiver.walletController.GetPaymentLink(ctx)
|
||||
}
|
||||
|
||||
func (receiver *API) ChangeCurrency(ctx echo.Context) error {
|
||||
return receiver.walletController.ChangeCurrency(ctx)
|
||||
}
|
@ -100,6 +100,11 @@ type Name struct {
|
||||
// PaymentType defines model for PaymentType.
|
||||
type PaymentType string
|
||||
|
||||
// TariffID defines model for TariffID.
|
||||
type TariffID struct {
|
||||
ID *string `json:"ID,omitempty"`
|
||||
}
|
||||
|
||||
// Wallet defines model for Wallet.
|
||||
type Wallet struct {
|
||||
// Cash Сумма money переведённая на текущий курс
|
||||
@ -155,6 +160,28 @@ type GetHistoryParams struct {
|
||||
|
||||
// Type Тип события
|
||||
Type *string `form:"type,omitempty" json:"type,omitempty"`
|
||||
|
||||
// AccountID Идентификатор аккаунта. Если не указан, будет использоваться идентификатор из токена.
|
||||
AccountID *string `form:"accountID,omitempty" json:"accountID,omitempty"`
|
||||
}
|
||||
|
||||
// CalculateLTVJSONBody defines parameters for CalculateLTV.
|
||||
type CalculateLTVJSONBody struct {
|
||||
// From Начальная дата в формате Unix timestamp. Если 0, устанавливает начало истории.
|
||||
From int64 `json:"from"`
|
||||
|
||||
// To Конечная дата в формате Unix timestamp. Если 0, устанавливает текущее время.
|
||||
To int64 `json:"to"`
|
||||
}
|
||||
|
||||
// GetRecentTariffsJSONBody defines parameters for GetRecentTariffs.
|
||||
type GetRecentTariffsJSONBody struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
// SendReportJSONBody defines parameters for SendReport.
|
||||
type SendReportJSONBody struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
// ChangeCurrencyJSONBody defines parameters for ChangeCurrency.
|
||||
@ -184,6 +211,15 @@ type SetAccountVerificationStatusJSONRequestBody SetAccountVerificationStatusJSO
|
||||
// UpdateCurrenciesJSONRequestBody defines body for UpdateCurrencies for application/json ContentType.
|
||||
type UpdateCurrenciesJSONRequestBody = UpdateCurrenciesJSONBody
|
||||
|
||||
// CalculateLTVJSONRequestBody defines body for CalculateLTV for application/json ContentType.
|
||||
type CalculateLTVJSONRequestBody CalculateLTVJSONBody
|
||||
|
||||
// GetRecentTariffsJSONRequestBody defines body for GetRecentTariffs for application/json ContentType.
|
||||
type GetRecentTariffsJSONRequestBody GetRecentTariffsJSONBody
|
||||
|
||||
// SendReportJSONRequestBody defines body for SendReport for application/json ContentType.
|
||||
type SendReportJSONRequestBody SendReportJSONBody
|
||||
|
||||
// ChangeCurrencyJSONRequestBody defines body for ChangeCurrency for application/json ContentType.
|
||||
type ChangeCurrencyJSONRequestBody ChangeCurrencyJSONBody
|
||||
|
||||
|
@ -26,13 +26,15 @@ type ConfigurationGRPC struct {
|
||||
}
|
||||
|
||||
type ServiceConfiguration struct {
|
||||
AuthMicroservice AuthMicroserviceConfiguration
|
||||
HubadminMicroservice HubadminMicroserviceConfiguration
|
||||
CurrencyMicroservice CurrencyMicroserviceConfiguration
|
||||
DiscountMicroservice DiscountMicroserviceConfiguration
|
||||
PaymentMicroservice PaymentMicroserviceConfiguration
|
||||
JWT JWTConfiguration
|
||||
Kafka KafkaConfiguration
|
||||
AuthMicroservice AuthMicroserviceConfiguration
|
||||
HubadminMicroservice HubadminMicroserviceConfiguration
|
||||
CurrencyMicroservice CurrencyMicroserviceConfiguration
|
||||
DiscountMicroservice DiscountMicroserviceConfiguration
|
||||
PaymentMicroservice PaymentMicroserviceConfiguration
|
||||
VerificationMicroservice VerificationMicroserviceConfiguration
|
||||
TemplategenMicroserviceURL TemplategenMicroserviceConfiguration
|
||||
JWT JWTConfiguration
|
||||
Kafka KafkaConfiguration
|
||||
}
|
||||
|
||||
type KafkaConfiguration struct {
|
||||
@ -65,6 +67,14 @@ type CurrencyMicroserviceConfiguration struct {
|
||||
URL CurrencyMicroserviceURL
|
||||
}
|
||||
|
||||
type VerificationMicroserviceConfiguration struct {
|
||||
URL VerificationMicroserviceURL
|
||||
}
|
||||
|
||||
type TemplategenMicroserviceConfiguration struct {
|
||||
URL TemplategenMicroserviceURL
|
||||
}
|
||||
|
||||
type PaymentMicroserviceConfiguration struct {
|
||||
HostGRPC string `env:"PAYMENT_MICROSERVICE_GRPC_HOST,required"`
|
||||
}
|
||||
@ -84,3 +94,11 @@ type HubadminMicroserviceURL struct {
|
||||
type CurrencyMicroserviceURL struct {
|
||||
Translate string `env:"CURRENCY_MICROSERVICE_TRANSLATE_URL,required"`
|
||||
}
|
||||
|
||||
type VerificationMicroserviceURL struct {
|
||||
Verification string `env:"VERIFICATION_MICROSERVICE_USER_URL,required"`
|
||||
}
|
||||
|
||||
type TemplategenMicroserviceURL struct {
|
||||
Templategen string `env:"TEMPLATEGEN_MICROSERVICE_URL,required"`
|
||||
}
|
||||
|
@ -14,6 +14,27 @@ type History struct {
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty" bson:"deletedAt,omitempty"`
|
||||
}
|
||||
|
||||
type TariffID struct {
|
||||
ID string `json:"id" bson:"_id"`
|
||||
}
|
||||
|
||||
type ReportHistory struct {
|
||||
ID string `json:"id" bson:"_id,omitempty"`
|
||||
UserID string `json:"userId" bson:"userId"`
|
||||
Comment string `json:"comment" bson:"comment"`
|
||||
Key string `json:"key" bson:"key"`
|
||||
RawDetails RawDetails `json:"rawDetails" bson:"rawDetails"`
|
||||
Deleted bool `json:"isDeleted" bson:"isDeleted"`
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty" bson:"deletedAt,omitempty"`
|
||||
}
|
||||
|
||||
type RawDetails struct {
|
||||
Tariffs []Tariff `json:"tariffs" bson:"tariffs"`
|
||||
Price int64 `json:"price" bson:"price"`
|
||||
}
|
||||
|
||||
func (receiver *History) Sanitize() *History {
|
||||
now := time.Now()
|
||||
|
||||
|
12
internal/models/templategen.go
Normal file
12
internal/models/templategen.go
Normal file
@ -0,0 +1,12 @@
|
||||
package models
|
||||
|
||||
type RespGeneratorService struct {
|
||||
DocNumber int `json:"docnumber"`
|
||||
Date string `json:"date"`
|
||||
OrgTaxNum string `json:"orgtaxnum"`
|
||||
OrgName Name `json:"orgname"`
|
||||
Name string `json:"name"`
|
||||
Amount uint64 `json:"amount"`
|
||||
Price int64 `json:"price"`
|
||||
Sum int64 `json:"sum"`
|
||||
}
|
19
internal/models/verification.go
Normal file
19
internal/models/verification.go
Normal file
@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Verification struct {
|
||||
ID string `json:"_id" bson:"_id,omitempty"`
|
||||
UserID string `json:"userID" bson:"user_id,omitempty"`
|
||||
Accepted bool `json:"accepted" bson:"accepted"`
|
||||
Status string `json:"status" bson:"status,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
|
||||
Comment string `json:"comment" bson:"comment,omitempty"`
|
||||
Files []VerificationFile `json:"files" bson:"files,omitempty"`
|
||||
TaxNumber string `json:"taxnumber" bson:"taxnumber,omitempty"`
|
||||
}
|
||||
|
||||
type VerificationFile struct {
|
||||
Name string `json:"name" bson:"name"`
|
||||
URL string `json:"url" bson:"url"`
|
||||
}
|
@ -165,7 +165,7 @@ func (receiver *Service) Pay(ctx context.Context, accessToken string, userID str
|
||||
UserInformation: &discount.UserInformation{
|
||||
ID: account.UserID,
|
||||
Type: string(account.Status),
|
||||
PurchasesAmount: uint64(account.Wallet.PurchasesAmount),
|
||||
PurchasesAmount: uint64(account.Wallet.Spent),
|
||||
CartPurchasesAmount: tariffsAmount,
|
||||
},
|
||||
Products: transfer.TariffsToProductInformations(tariffs),
|
||||
@ -180,7 +180,7 @@ func (receiver *Service) Pay(ctx context.Context, accessToken string, userID str
|
||||
UserInformation: &discount.UserInformation{
|
||||
ID: account.UserID,
|
||||
Type: string(account.Status),
|
||||
PurchasesAmount: uint64(account.Wallet.PurchasesAmount),
|
||||
PurchasesAmount: uint64(account.Wallet.Spent),
|
||||
CartPurchasesAmount: tariffsAmount,
|
||||
},
|
||||
Products: transfer.TariffsToProductInformations(tariffs),
|
||||
@ -203,10 +203,13 @@ func (receiver *Service) Pay(ctx context.Context, accessToken string, userID str
|
||||
|
||||
go func(tariffs []models.Tariff) {
|
||||
if _, historyErr := receiver.historyService.CreateHistory(ctx, &models.History{
|
||||
Key: models.CustomerHistoryKeyPayCart,
|
||||
UserID: account.UserID,
|
||||
Comment: "Успешная оплата корзины",
|
||||
RawDetails: tariffs,
|
||||
Key: models.CustomerHistoryKeyPayCart,
|
||||
UserID: account.UserID,
|
||||
Comment: "Успешная оплата корзины",
|
||||
RawDetails: map[string]any{
|
||||
"tariffs": tariffs,
|
||||
"price": discountResponse.Price,
|
||||
},
|
||||
}); historyErr != nil {
|
||||
receiver.logger.Error("failed to insert history on <Pay> of <CartService>", zap.Error(historyErr))
|
||||
}
|
||||
@ -214,6 +217,8 @@ func (receiver *Service) Pay(ctx context.Context, accessToken string, userID str
|
||||
|
||||
// TODO: обработать ошибки при отправке сообщений
|
||||
|
||||
receiver.logger.Error("send to redpanda", zap.String("userID", account.UserID), zap.Any("bought tariffs", tariffs))
|
||||
|
||||
if sendErrors := receiver.tariffBrokerService.SendMany(ctx, account.UserID, tariffs); len(sendErrors) > 0 {
|
||||
for _, err := range sendErrors {
|
||||
receiver.logger.Error("failed to send tariffs to broker on <Pay> of <CartService>", zap.Error(err))
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.uber.org/zap"
|
||||
@ -16,12 +18,14 @@ import (
|
||||
type GetHistories struct {
|
||||
Pagination *models.Pagination
|
||||
Type *string
|
||||
UserID string
|
||||
}
|
||||
|
||||
func (receiver *GetHistories) BSON() bson.M {
|
||||
query := bson.M{
|
||||
fields.History.IsDeleted: false,
|
||||
fields.History.Type: *receiver.Type,
|
||||
fields.History.UserID: receiver.UserID,
|
||||
}
|
||||
|
||||
return query
|
||||
@ -31,16 +35,38 @@ type historyRepository interface {
|
||||
CountAll(context.Context, *GetHistories) (int64, errors.Error)
|
||||
FindMany(context.Context, *GetHistories) ([]models.History, errors.Error)
|
||||
Insert(context.Context, *models.History) (*models.History, errors.Error)
|
||||
GetRecentTariffs(context.Context, string) ([]models.TariffID, errors.Error) // new
|
||||
GetHistoryByID(context.Context, string) (*models.ReportHistory, errors.Error)
|
||||
GetDocNumber(context.Context, string) (map[string]int, errors.Error)
|
||||
CalculateCustomerLTV(ctx context.Context, from, to int64) (int64, errors.Error)
|
||||
}
|
||||
|
||||
type authClient interface {
|
||||
GetUser(ctx context.Context, userID string) (*models.User, errors.Error)
|
||||
}
|
||||
|
||||
type verificationClient interface {
|
||||
GetUser(ctx context.Context, userID string) (*models.Verification, errors.Error)
|
||||
}
|
||||
|
||||
type temlategenClient interface {
|
||||
SendData(ctx context.Context, data models.RespGeneratorService, fileContents []byte, email string) errors.Error
|
||||
}
|
||||
|
||||
type Deps struct {
|
||||
Logger *zap.Logger
|
||||
Repository historyRepository
|
||||
Logger *zap.Logger
|
||||
Repository historyRepository
|
||||
AuthClient authClient
|
||||
VerificationClient verificationClient
|
||||
TemlategenClient temlategenClient
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
logger *zap.Logger
|
||||
repository historyRepository
|
||||
logger *zap.Logger
|
||||
repository historyRepository
|
||||
AuthClient authClient
|
||||
VerificationClient verificationClient
|
||||
TemlategenClient temlategenClient
|
||||
}
|
||||
|
||||
func New(deps Deps) *Service {
|
||||
@ -52,9 +78,14 @@ func New(deps Deps) *Service {
|
||||
log.Panicln("repository is nil on <New (history service)>")
|
||||
}
|
||||
|
||||
if deps.AuthClient == nil {
|
||||
log.Panicln("auth client is nil on <New (account service)>")
|
||||
}
|
||||
|
||||
return &Service{
|
||||
logger: deps.Logger,
|
||||
repository: deps.Repository,
|
||||
AuthClient: deps.AuthClient,
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,3 +136,138 @@ func (receiver *Service) CreateHistory(ctx context.Context, history *models.Hist
|
||||
|
||||
return createdHistory, nil
|
||||
}
|
||||
|
||||
// TODO:tests.
|
||||
func (receiver *Service) GetRecentTariffs(ctx context.Context, userID string) ([]models.TariffID, errors.Error) {
|
||||
if userID == "" {
|
||||
receiver.logger.Error("user id is missing in <GetRecentTariffs> of <HistoryService>")
|
||||
return nil, errors.New(
|
||||
fmt.Errorf("user id is missing: %w", errors.ErrInvalidArgs),
|
||||
errors.ErrInvalidArgs,
|
||||
)
|
||||
}
|
||||
|
||||
tariffs, err := receiver.repository.GetRecentTariffs(ctx, userID)
|
||||
if err != nil {
|
||||
receiver.logger.Error(
|
||||
"failed to get recent tariffs in <GetRecentTariffs> of <HistoryService>",
|
||||
zap.String("userId", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tariffs, nil
|
||||
}
|
||||
|
||||
func (receiver *Service) GetHistoryByID(ctx context.Context, historyID string) errors.Error {
|
||||
if historyID == "" {
|
||||
receiver.logger.Error("history id is missing in <GetHistoryById> of <HistoryService>")
|
||||
return errors.New(
|
||||
fmt.Errorf("history id is missing: %w", errors.ErrInvalidArgs),
|
||||
errors.ErrInvalidArgs,
|
||||
)
|
||||
}
|
||||
|
||||
tariffs, err := receiver.repository.GetHistoryByID(ctx, historyID)
|
||||
if err != nil {
|
||||
receiver.logger.Error(
|
||||
"failed to get history by id in <GetHistoryById> of <HistoryService>",
|
||||
zap.String("historyID", historyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
if tariffs.Key != models.CustomerHistoryKeyPayCart {
|
||||
receiver.logger.Error(
|
||||
"invalid history record key",
|
||||
zap.String("historyID", historyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
historyMap, err := receiver.repository.GetDocNumber(ctx, tariffs.UserID)
|
||||
if err != nil {
|
||||
receiver.logger.Error(
|
||||
"failed to get history of sorting by date created in <GetDocNumber> of <HistoryService>",
|
||||
zap.String("historyID", historyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
verifuser, err := receiver.VerificationClient.GetUser(ctx, tariffs.UserID)
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to get user verification on <GetHistoryById> of <HistoryService>",
|
||||
zap.Error(err),
|
||||
zap.String("userID", tariffs.UserID),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
if !verifuser.Accepted {
|
||||
receiver.logger.Error(
|
||||
"verification not accepted",
|
||||
zap.String("userID", tariffs.UserID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
authuser, err := receiver.AuthClient.GetUser(ctx, tariffs.UserID)
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to get user on <GetHistoryById> of <HistoryService>",
|
||||
zap.Error(err),
|
||||
zap.String("userID", tariffs.UserID),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
fileContents, readerr := os.ReadFile("./report.docx")
|
||||
if readerr != nil {
|
||||
return errors.New(
|
||||
fmt.Errorf("failed to read file: %w", errors.ErrInternalError),
|
||||
errors.ErrInternalError,
|
||||
)
|
||||
}
|
||||
|
||||
for _, tariff := range tariffs.RawDetails.Tariffs {
|
||||
totalAmount := uint64(0)
|
||||
for _, privilege := range tariff.Privileges {
|
||||
totalAmount += privilege.Amount
|
||||
}
|
||||
data := models.RespGeneratorService{
|
||||
DocNumber: historyMap[historyID] + 1,
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
OrgTaxNum: verifuser.TaxNumber,
|
||||
OrgName: models.Name{Orgname: "Orgname"},
|
||||
Name: tariff.Name,
|
||||
Amount: totalAmount,
|
||||
Price: tariffs.RawDetails.Price,
|
||||
Sum: tariffs.RawDetails.Price,
|
||||
}
|
||||
err = receiver.TemlategenClient.SendData(ctx, data, fileContents, authuser.Email)
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to send report to user on <GetHistoryById> of <HistoryService>",
|
||||
zap.Error(err),
|
||||
zap.String("userID", tariffs.UserID),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (receiver *Service) CalculateCustomerLTV(ctx context.Context, from, to int64) (int64, errors.Error) {
|
||||
ltv, err := receiver.repository.CalculateCustomerLTV(ctx, from, to)
|
||||
if err != nil {
|
||||
receiver.logger.Error("failed to calculate LTV", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ltv, nil
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ func CalculateCartPurchasesAmount(tariffs []models.Tariff) uint64 {
|
||||
privilegesSum := uint64(0)
|
||||
|
||||
for _, privilege := range tariff.Privileges {
|
||||
privilegesSum += privilege.Price
|
||||
privilegesSum += privilege.Price*privilege.Amount
|
||||
}
|
||||
|
||||
sum += privilegesSum
|
||||
|
@ -13,6 +13,12 @@ func TariffsToProductInformations(tarrifs []models.Tariff) []*discount.ProductIn
|
||||
for _, privilege := range tariff.Privileges {
|
||||
productInformations = append(productInformations, PrivilegeToProductInformation(privilege))
|
||||
}
|
||||
|
||||
if tariff.Price != 0 {
|
||||
for i := range productInformations {
|
||||
productInformations[i].Price = tariff.Price
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return productInformations
|
||||
|
BIN
main
Executable file
BIN
main
Executable file
Binary file not shown.
BIN
report.docx
Normal file
BIN
report.docx
Normal file
Binary file not shown.
54
tests/integration/calculate_LTV_test.go
Normal file
54
tests/integration/calculate_LTV_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/swagger"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/client"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/tests/helpers"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCalculateLTV(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
jwtUtil := helpers.InitializeJWT()
|
||||
token, err := jwtUtil.Create("807f1f77bcf81cd799439077")
|
||||
|
||||
if ok := assert.NoError(t, err); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
layout := "2006-01-02T15:04:05.000Z"
|
||||
fromString := "2023-11-08T22:29:48.719Z"
|
||||
toString := "2023-12-27T15:00:00.000Z"
|
||||
fromTime, err := time.Parse(layout, fromString)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
toTime, err := time.Parse(layout, toString)
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
}
|
||||
from := fromTime.Unix()
|
||||
to := toTime.Unix()
|
||||
fmt.Println(from, to)
|
||||
|
||||
response, err := client.Post[struct{}, models.ResponseErrorHTTP](ctx, &client.RequestSettings{
|
||||
URL: "http://" + "localhost:8000" + "/history/ltv",
|
||||
Body: swagger.CalculateLTVJSONBody{From: from, To: to},
|
||||
Headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)},
|
||||
})
|
||||
if ok := assert.NoError(t, err); !ok {
|
||||
return
|
||||
}
|
||||
if ok := assert.Nil(t, response.Error); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, 200, response.StatusCode)
|
||||
|
||||
fmt.Println(response.Body)
|
||||
}
|
37
tests/integration/history_report_test.go
Normal file
37
tests/integration/history_report_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/interface/swagger"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/client"
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/tests/helpers"
|
||||
)
|
||||
|
||||
func TestHistoryReport(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
jwtUtil := helpers.InitializeJWT()
|
||||
token, err := jwtUtil.Create("807f1f77bcf81cd799439077")
|
||||
|
||||
if ok := assert.NoError(t, err); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
response, err := client.Post[struct{}, models.ResponseErrorHTTP](ctx, &client.RequestSettings{
|
||||
URL: "http://" + customerServiceBase + "/sendReport",
|
||||
Body: swagger.SendReportJSONBody{Id: "10002"},
|
||||
Headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)},
|
||||
})
|
||||
if ok := assert.NoError(t, err); !ok {
|
||||
return
|
||||
}
|
||||
if ok := assert.Nil(t, response.Error); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, 200, response.StatusCode)
|
||||
}
|
@ -3,6 +3,7 @@ package integration_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -11,6 +12,8 @@ import (
|
||||
"penahub.gitlab.yandexcloud.net/pena-services/customer/tests/helpers"
|
||||
)
|
||||
|
||||
var customerServiceBase = os.Getenv("CUSTOMER_SERVICE")
|
||||
|
||||
func TestSetAccountVerificationStatusNO(t *testing.T) {
|
||||
jwtUtil := helpers.InitializeJWT()
|
||||
|
||||
@ -25,7 +28,7 @@ func TestSetAccountVerificationStatusNO(t *testing.T) {
|
||||
}
|
||||
|
||||
response, getAccountErr := client.Patch[models.Account, models.ResponseErrorHTTP](ctx, &client.RequestSettings{
|
||||
URL: "http://localhost:8082/account/807f1f77bcf81cd799439077",
|
||||
URL: "http://" + customerServiceBase + "/account/807f1f77bcf81cd799439077",
|
||||
Body: models.SetAccountStatus{Status: models.AccountStatusNo},
|
||||
Headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)},
|
||||
})
|
||||
@ -56,7 +59,7 @@ func TestSetAccountVerificationStatusORG(t *testing.T) {
|
||||
}
|
||||
|
||||
response, getAccountErr := client.Patch[models.Account, models.ResponseErrorHTTP](ctx, &client.RequestSettings{
|
||||
URL: "http://localhost:8082/account/807f1f77bcf81cd799439077",
|
||||
URL: "http://" + customerServiceBase + "/account/807f1f77bcf81cd799439077",
|
||||
Body: models.SetAccountStatus{Status: models.AccountStatusOrg},
|
||||
Headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)},
|
||||
})
|
||||
@ -87,7 +90,7 @@ func TestSetAccountVerificationStatusNKO(t *testing.T) {
|
||||
}
|
||||
|
||||
response, getAccountErr := client.Patch[models.Account, models.ResponseErrorHTTP](ctx, &client.RequestSettings{
|
||||
URL: "http://localhost:8082/account/807f1f77bcf81cd799439077",
|
||||
URL: "http://" + customerServiceBase + "/account/807f1f77bcf81cd799439077",
|
||||
Body: models.SetAccountStatus{Status: models.AccountStatusNko},
|
||||
Headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)},
|
||||
})
|
||||
@ -118,7 +121,7 @@ func TestSetAccountVerificationStatusFailure(t *testing.T) {
|
||||
}
|
||||
|
||||
response, getAccountErr := client.Patch[models.Account, models.ResponseErrorHTTP](ctx, &client.RequestSettings{
|
||||
URL: "http://localhost:8082/account/807f1f77bcf81cd799439077",
|
||||
URL: "http://" + customerServiceBase + "/account/807f1f77bcf81cd799439077",
|
||||
Body: models.SetAccountStatus{Status: "radnom-status"},
|
||||
Headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)},
|
||||
})
|
||||
|
@ -25,7 +25,7 @@ func TestUpdateAccountName(t *testing.T) {
|
||||
}
|
||||
|
||||
response, getAccountErr := client.Patch[models.Account, models.ResponseErrorHTTP](ctx, &client.RequestSettings{
|
||||
URL: "http://localhost:8082/account",
|
||||
URL: "http://" + customerServiceBase + "/account",
|
||||
Body: models.Name{
|
||||
FirstName: "Ivan",
|
||||
Secondname: "Ivanov",
|
||||
|
Loading…
Reference in New Issue
Block a user