mergeconflicts

This commit is contained in:
skeris 2023-12-01 20:46:53 +03:00
commit 4f500f5ea5
27 changed files with 908 additions and 254 deletions

@ -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
@ -13,8 +13,6 @@ RUN mkdir /bin/golang-migrate -p
ADD ./tools/migrate /bin/golang-migrate/
# Add main files to app
ADD . .
# Download go depences
RUN go mod download
# Build app
RUN GOOS=linux go build -o bin ./...
@ -24,9 +22,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 +40,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

@ -25,18 +25,21 @@ 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 ./tests/integration/...
docker-compose -p integration -f deployments/test/docker-compose.integration.yaml down
docker-compose -p integration -f deployments/test/docker-compose.integration.yaml up --exit-code-from integration --remove-orphans
docker-compose -p integration -f deployments/test/docker-compose.integration.yaml down
test.e2e.start: ## run integration test
go test ./tests/e2e/...
@ -48,4 +51,4 @@ dev.up: ## run dev environment
docker-compose -f deployments/dev/docker-compose.yaml up -d
dev.down: ## shutting down dev environment
docker-compose -f deployments/dev/docker-compose.yaml down --volumes --rmi local
docker-compose -f deployments/dev/docker-compose.yaml down --volumes --rmi local

@ -42,3 +42,4 @@ KAFKA_TOPIC_TARIFF - название топика для сообщений т
## Полезные ссылки:
- [**Диаграммы**](./docs/diagram/README.md)
- Для того чтобы создать новые endpoint, нужно прописать их в customer/api/openapi/v1/openapi.yaml, сделать его описание, с помощью инструкций в makefile сгенерировать файлы

@ -4,7 +4,7 @@ info:
description: |-
Область ответственности сервиса - предоставление пользователю функционала корзины и кошелька.
version: 1.0.0
tags:
- name: account
description: аккаунт
@ -16,7 +16,7 @@ tags:
description: кошелёк
- name: history
description: история
paths:
/account:
get:
@ -28,24 +28,24 @@ paths:
security:
- Bearer: []
responses:
'200':
"200":
description: успешное получение
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'401':
$ref: "#/components/schemas/Account"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: "#/components/schemas/Error"
"404":
description: Такого пользователя нет
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
post:
tags:
- account
@ -55,18 +55,18 @@ paths:
security:
- Bearer: []
responses:
'200':
"200":
description: успешное создание аккаунта
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'401':
$ref: "#/components/schemas/Account"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
patch:
tags:
- account
@ -76,24 +76,24 @@ paths:
security:
- Bearer: []
requestBody:
description: Модель имени
content:
application/json:
schema:
$ref: '#/components/schemas/Name'
description: Модель имени
content:
application/json:
schema:
$ref: "#/components/schemas/Name"
responses:
'200':
"200":
description: успешное создание аккаунта
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'401':
$ref: "#/components/schemas/Account"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
delete:
tags:
- account
@ -103,19 +103,19 @@ paths:
security:
- Bearer: []
responses:
'200':
"200":
description: успешное удаление аккаунта
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'401':
$ref: "#/components/schemas/Account"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
/accounts:
get:
tags:
@ -141,7 +141,7 @@ paths:
type: integer
default: 100
responses:
'200':
"200":
description: успешное получение страницы аккаунтов
content:
application/json:
@ -151,11 +151,11 @@ paths:
totalPages:
type: integer
example: 11
accounts:
accounts:
type: array
items:
$ref: "#/components/schemas/Account"
/account/{userId}:
get:
tags:
@ -173,24 +173,24 @@ paths:
schema:
type: string
responses:
'200':
"200":
description: Успешное получение аккаунта
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'401':
$ref: "#/components/schemas/Account"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: "#/components/schemas/Error"
"404":
description: Нет такого пользователя
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
patch:
tags:
- account
@ -213,20 +213,20 @@ paths:
type: object
properties:
status:
$ref: '#/components/schemas/AccountStatus'
$ref: "#/components/schemas/AccountStatus"
responses:
'200':
"200":
description: Успешное выставление статуса
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'404':
$ref: "#/components/schemas/Account"
"404":
description: Нет такого пользователя
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
delete:
tags:
- account
@ -243,25 +243,25 @@ paths:
schema:
type: string
responses:
'200':
description: Успешное удаление аккаунта
"200":
description: Успешное удаление аккаунта
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'401':
$ref: "#/components/schemas/Account"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: "#/components/schemas/Error"
"404":
description: Нет такого пользователя
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
/currencies:
get:
tags:
@ -269,17 +269,17 @@ paths:
summary: получить список одобренных валют
operationId: getCurrencies
responses:
'200':
"200":
description: успешное получение списка валют
content:
application/json:
schema:
type: array
example:
example:
- "RUB"
- "USD"
- "CAD"
items:
items:
type: string
put:
tags:
@ -293,32 +293,32 @@ paths:
application/json:
schema:
type: array
example:
example:
- "RUB"
- "USD"
- "CAD"
items:
items:
type: string
responses:
'200':
"200":
description: успешное обновление списка валют
content:
application/json:
schema:
type: array
example:
example:
- "RUB"
- "USD"
- "CAD"
items:
items:
type: string
'401':
"401":
description: Uanuthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
/cart:
patch:
tags:
@ -336,24 +336,24 @@ paths:
type: string
example: "807f1f77bcf81cd799439011"
responses:
'200':
"200":
description: Добавлено в корзину
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'401':
$ref: "#/components/schemas/Account"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: "#/components/schemas/Error"
"404":
description: Не найден такой тариф
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
delete:
tags:
- cart
@ -369,25 +369,25 @@ paths:
type: string
example: "807f1f77bcf81cd799439011"
responses:
'200':
"200":
description: Удалено из корзины
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'401':
$ref: "#/components/schemas/Account"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: "#/components/schemas/Error"
"404":
description: Не найден такой тариф
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
/cart/pay:
post:
tags:
@ -398,34 +398,34 @@ paths:
- Bearer: []
description: Запрос на проведение оплаты корзины
responses:
'200':
"200":
description: успешная оплата корзины
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
'401':
$ref: "#/components/schemas/Account"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
/wallet:
patch:
tags:
- wallet
summary: Изменить валюту кошелька
operationId: changeCurrency
description: >-
description: >-
При запросе необходимо:
- Отвалидировать, что такая валюта одобрена
- Получить данные из сервиса cbrf (выдам задачу на него чуть позднее)
- Перевести валюту кошелька в новую валюту. Кошелёк нарочно целочисленный, чтобы не было претензий к точности с плавающей точкой, поэтому, например долларовый счёт считается в центах
security:
- Bearer: []
requestBody:
@ -435,29 +435,29 @@ paths:
type: object
required: [currency]
properties:
currency:
currency:
type: string
example: "USD"
responses:
'200':
"200":
description: Успешная смена валюты кошелька
content:
application/json:
schema:
$ref: "#/components/schemas/Account"
'401':
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
'400':
"400":
description: Такая валюта не одобрена
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
'404':
"404":
description: Пользователь не найден
content:
application/json:
@ -472,7 +472,7 @@ paths:
- Bearer: []
description: >-
- Формируем запрос к сервису оплаты, с необходимыми постбэками
- Получаем ответ от сервиса оплаты с ссылкой на оплату, которую надо передать пользователю
requestBody:
content:
@ -482,7 +482,7 @@ paths:
required: [type, currency, amount]
properties:
type:
$ref: '#/components/schemas/PaymentType'
$ref: "#/components/schemas/PaymentType"
currency:
type: string
description: "ISO-4217 формат"
@ -491,7 +491,7 @@ paths:
type: integer
example: 15020
bankCard:
$ref: '#/components/schemas/BankCard'
$ref: "#/components/schemas/BankCard"
phoneNumber:
type: string
example: "79000000000"
@ -502,23 +502,23 @@ paths:
type: string
example: "https://site.ru/cart"
responses:
'200':
"200":
description: Успешный запрос денег
content:
application/json:
schema:
type: object
properties:
link:
link:
type: string
example: "https://google.ru"
'500':
"500":
description: Сервис оплаты вернул ошибку
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
/history:
get:
tags:
@ -552,46 +552,107 @@ paths:
schema:
type: string
responses:
'200':
"200":
description: Успешное получение событий
content:
application/json:
schema:
type: object
properties:
totalPages:
totalPages:
type: integer
example: 11
records:
type: array
items:
$ref: '#/components/schemas/History'
'401':
$ref: "#/components/schemas/History"
"401":
description: Неавторизован
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
$ref: "#/components/schemas/Error"
/recent:
get:
tags:
- history
summary: Получение недавних тарифов
description: Возвращает список уникальных тарифов из истории. Айди аккаунта получается из заголовка.
operationId: getRecentTariffs
security:
- Bearer: []
responses:
'200':
description: Успешный запрос
get:
tags:
- history
summary: Получение недавних тарифов
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"
components:
schemas:
Account:
type: object
required: [_id, userId, cart, wallet, name, status, deleted, createdAt, updatedAt]
required:
[_id, userId, cart, wallet, name, status, deleted, createdAt, updatedAt]
properties:
_id:
type: string
@ -600,18 +661,18 @@ components:
type: string
example: "807f1f77bcf81cd799439011"
name:
$ref: '#/components/schemas/Name'
$ref: "#/components/schemas/Name"
cart:
type: array
items:
type: string
example:
example:
- "807f1f77bcf81cd799439011"
- "807f1f77bcf81cd799439012"
wallet:
$ref: "#/components/schemas/Wallet"
status:
$ref: '#/components/schemas/AccountStatus'
$ref: "#/components/schemas/AccountStatus"
isDeleted:
type: boolean
example: false
@ -624,11 +685,11 @@ components:
deletedAt:
type: string
format: "date-time"
Name:
type: object
properties:
firstname:
firstname:
type: string
example: Иван
secondname:
@ -665,7 +726,7 @@ components:
type: string
description: Имя владельца карты
example: "IVAN IVANOV"
Wallet:
type: object
required: [currency, cash, money, purchasesAmount, spent]
@ -695,10 +756,11 @@ components:
возникало денег изниоткуда. фиксируемся к одной валюте,
она будет внутренней, никому её не покажем
example: 10701
History:
type: object
required: [id, userId, type, deleted, createdAt, updatedAt, comment, subject]
required:
[id, userId, type, deleted, createdAt, updatedAt, comment, subject]
properties:
id:
type: string
@ -709,7 +771,7 @@ components:
type:
type: string
example: "customer.tariffEnded"
isDeleted:
isDeleted:
type: boolean
example: false
createdAt:
@ -726,16 +788,37 @@ components:
example: "я это сделал потому что 42"
rawDetails:
type: string
description: >-
description: >-
Я пока не могу предположить, какие будут фильтры по
истории, поэтому предлагаю в это
поле просто класть строку с json.
Ибо для каждого типа записи она своя.
example: '{"tariffs":["807f1f77bcf81cd799439011","807f1f77bcf81cd799439011"]}'
PaymentType:
type: string
enum: ["bankCard", "tinkoffBank", "qiwi", "sberbank", "yoomoney", "mobile", "installments", "cash", "sbp", "b2bSberbank", "alfabank"]
enum:
[
"bankCard",
"tinkoffBank",
"qiwi",
"sberbank",
"yoomoney",
"mobile",
"installments",
"cash",
"sbp",
"b2bSberbank",
"alfabank",
]
TariffID:
type: object
properties:
ID:
type: string
example: "807f1f77bcf81cd799439011"
AccountStatus:
type: string
@ -752,9 +835,9 @@ components:
message:
type: string
example: user not found
securitySchemes:
Bearer: # arbitrary name for the security scheme
Bearer: # arbitrary name for the security scheme
type: http
scheme: bearer
bearerFormat: JWT

@ -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:
@ -39,24 +39,23 @@ services:
- 8082:8000
- 9092:9000
depends_on:
- customer-db
- customer-migration
- redpanda
customer-db:
condition: service_started
customer-migration:
condition: service_completed_successfully
redpanda:
condition: service_healthy
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',
]
image: alpine
command: migrate -source file://migrations -database "mongodb://test:test@customer-db:27017/admin" up
volumes:
- ../../migrations/test:/app/migrations:ro
- ../../tools/migrate:/usr/local/bin/migrate:ro
working_dir: /app
depends_on:
- customer-db
networks:
@ -105,17 +104,18 @@ services:
networks:
- test
healthcheck:
test: ["CMD-SHELL", "rpk cluster health | grep -E 'Healthy:.+true' || exit 1"]
interval: 15s
timeout: 3s
test: rpk cluster health | grep -q 'Healthy:.*true'
interval: 2s
timeout: 2s
retries: 5
start_period: 5s
console:
tty: true
container_name: customer-console
image: docker.redpanda.com/redpandadata/console:v2.2.4
entrypoint: /bin/sh
command: -c "echo \"$$CONSOLE_CONFIG_FILE\" > /tmp/config.yml; /app/console"
entrypoint: ''
command: sh -c 'echo "$$CONSOLE_CONFIG_FILE" > /tmp/config.yml && exec /app/console'
environment:
CONFIG_FILEPATH: /tmp/config.yml
CONSOLE_CONFIG_FILE: |
@ -138,18 +138,20 @@ 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:
@ -170,6 +172,8 @@ services:
CONNECT_GC_LOG_ENABLED: "false"
CONNECT_HEAP_OPTS: -Xms512M -Xmx512M
CONNECT_LOG_LEVEL: info
networks:
- test
networks:
test:
test:

9
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
@ -25,6 +25,7 @@ require (
require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/benbjohnson/clock v1.3.3 // indirect
github.com/daixiang0/gci v0.11.2 // 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
@ -34,6 +35,8 @@ require (
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/yaml v0.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
@ -48,6 +51,8 @@ require (
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/twmb/franz-go/pkg/kmsg v1.4.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
@ -59,7 +64,7 @@ require (
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

17
go.sum

@ -10,7 +10,10 @@ github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvF
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
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/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daixiang0/gci v0.11.2 h1:Oji+oPsp3bQ6bNNgX30NBAVT18P4uBH4sRZnlOlTj7Y=
github.com/daixiang0/gci v0.11.2/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -63,6 +66,10 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
@ -119,9 +126,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMfPQbKMDE=
github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
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/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
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=
@ -171,12 +183,15 @@ 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/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=
@ -211,6 +226,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.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=

@ -72,7 +72,9 @@ func Run(config *models.Config, logger *zap.Logger) (appErr error) {
HubadminURL: &config.Service.HubadminMicroservice.URL,
CurrencyURL: &config.Service.CurrencyMicroservice.URL,
DiscountServiceConfiguration: &config.Service.DiscountMicroservice,
VerificationURL: &config.Service.VerificationMicroservice.URL,
PaymentServiceConfiguration: &config.Service.PaymentMicroservice,
TemplategenURL: &config.Service.TemplategenMicroserviceURL.URL,
})
repositories := initialize.NewRepositories(initialize.RepositoriesDeps{

@ -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,
}),
}
}

@ -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
}

@ -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
}

@ -87,3 +87,16 @@ func (receiver *Controller) GetRecentTariffs(ctx echo.Context) error {
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)
}

@ -168,3 +168,93 @@ func (receiver *HistoryRepository) GetRecentTariffs(ctx context.Context, userID
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
}

@ -65,6 +65,9 @@ type ServerInterface interface {
// Получение недавних тарифов
// (GET /recent)
GetRecentTariffs(ctx echo.Context) error
// отправить акт проделанных работ на почту
// (POST /sendReport)
SendReport(ctx echo.Context) error
// Изменить валюту кошелька
// (PATCH /wallet)
ChangeCurrency(ctx echo.Context) error
@ -317,6 +320,17 @@ func (w *ServerInterfaceWrapper) GetRecentTariffs(ctx echo.Context) error {
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
@ -382,6 +396,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.PUT(baseURL+"/currencies", wrapper.UpdateCurrencies)
router.GET(baseURL+"/history", wrapper.GetHistory)
router.GET(baseURL+"/recent", wrapper.GetRecentTariffs)
router.POST(baseURL+"/sendReport", wrapper.SendReport)
router.PATCH(baseURL+"/wallet", wrapper.ChangeCurrency)
router.POST(baseURL+"/wallet", wrapper.RequestMoney)
@ -390,73 +405,74 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"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",
"SAAA",
"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",
}
// GetSwagger returns the content of the embedded swagger specification file

@ -36,6 +36,7 @@ type walletController interface {
type historyController interface {
GetHistoryList(ctx echo.Context, params GetHistoryParams) error
GetRecentTariffs(ctx echo.Context) error
SendReport(ctx echo.Context) error
}
type Deps struct {
@ -152,6 +153,10 @@ 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)
}
// Wallet
func (receiver *API) RequestMoney(ctx echo.Context) error {

@ -157,6 +157,11 @@ type GetHistoryParams struct {
Type *string `form:"type,omitempty" json:"type,omitempty"`
}
// SendReportJSONBody defines parameters for SendReport.
type SendReportJSONBody struct {
Id string `json:"id"`
}
// ChangeCurrencyJSONBody defines parameters for ChangeCurrency.
type ChangeCurrencyJSONBody struct {
Currency string `json:"currency"`
@ -184,6 +189,9 @@ type SetAccountVerificationStatusJSONRequestBody SetAccountVerificationStatusJSO
// UpdateCurrenciesJSONRequestBody defines body for UpdateCurrencies for application/json ContentType.
type UpdateCurrenciesJSONRequestBody = UpdateCurrenciesJSONBody
// 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"`
}

@ -18,6 +18,23 @@ 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()

@ -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"`
}

@ -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"`
}

@ -5,6 +5,8 @@ import (
"fmt"
"log"
"math"
"os"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.uber.org/zap"
@ -33,16 +35,36 @@ type historyRepository interface {
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)
}
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 {
@ -54,9 +76,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,
}
}
@ -130,3 +157,105 @@ func (receiver *Service) GetRecentTariffs(ctx context.Context, userID string) ([
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
}

BIN
report.docx Normal file

Binary file not shown.

@ -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")
// t.Log("TOKEN:", token)
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 string = 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",

0
tools/migrate Normal file → Executable file