Merge branch 'fastlinks' into 'dev'
Fastlinks See merge request pena-services/codeword!13
This commit is contained in:
commit
d85c096bd4
28
.env
28
.env
@ -21,6 +21,26 @@ PUBLIC_CURVE_KEY="-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAEbnIvjIMle4rqVol6K
|
||||
|
||||
PRIVATE_CURVE_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZvODgWAnUIwQhd8de5oZhY48gc23EWfrfs\n-----END PRIVATE KEY-----"
|
||||
|
||||
JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2B
|
||||
iw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikH
|
||||
oKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAEC
|
||||
gYAOphnVPXbk6lpYzdkLC1Xn5EOEuNfOLLURLxBnPWozZo26r/Mtahu/9mYhrYlv
|
||||
PP8r6mxta3VIil8iOdZyOLa/4d1LPd+UehgEXIJEiYXLtn7RS5eUnoPuQxssfs1k
|
||||
OWjdN8p6SzppleegFTvGRX4KM3cDLfSphOk8JuBCrpSSYQJBAOdqizTSrdKMTuVe
|
||||
c7Jk1JOJkyFuFs+N5zeryyeFGH7IpRdWy0rkWMxIUAi8Ap1vYVBPHv4tDOo3sy5X
|
||||
VLc/knkCQQCE62pg+0TmsrhO/2Pgog6MLBkzlzXYMRp/01HbmznwYF+ejfPnzLkz
|
||||
hnUlxRUNK3lhXM/7H6oAjvqF2R72u/OPAkEAterkmdbQfEZ+MwNoEiH/lie9OLdx
|
||||
SSI1VGdBYcTYN7qFRW6eizYstBJYkDU0HQ0Uw+we4hMKJwk4W0KdvxxDiQJAeqlB
|
||||
V1QqBneBbK10PzVuFV8QtrJhJyxRVwrtbKq38iMNuqUnI4+ijXEUpJFWVvv6nKXo
|
||||
7McQvEk12dU/JNTX8wJAOlAtSNjp9tVwpMpC0w2St1eKc1L2SknjeohA5ldoBz8sGeZsPhTU3eHSD1neAZXLKN5K68z3zFBr20ubY9nyLw==
|
||||
-----END RSA PRIVATE KEY-----"
|
||||
|
||||
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAE=\n-----END PUBLIC KEY-----"
|
||||
|
||||
JWT_ISSUER="pena-auth-service"
|
||||
|
||||
JWT_AUDIENCE="pena"
|
||||
# SIGN_SECRET="group"
|
||||
|
||||
SIGN_SECRET="secret"
|
||||
@ -36,4 +56,10 @@ SMTP_SENDER="noreply@mailing.pena.digital"
|
||||
|
||||
# URL settings
|
||||
DEFAULT_REDIRECTION_URL = "def.url"
|
||||
AUTH_EXCHANGE_URL = "http://localhost:8000/auth/exchange"
|
||||
AUTH_EXCHANGE_URL = "http://localhost:8000/auth/exchange"
|
||||
DISCOUNT_ADDRESS = "http://CHANGEME:1234"
|
||||
RECOVERY_URL = "http://127.0.0.1:8080/recover/"
|
||||
|
||||
# Kafka settings
|
||||
KAFKA_BROKERS="localhost:9092"
|
||||
KAFKA_TOPIC_TARIFF="tariffs"
|
@ -6,8 +6,9 @@ services:
|
||||
ports:
|
||||
- "27020:27017"
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
|
||||
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
|
||||
- MONGO_INITDB_ROOT_USERNAME=test
|
||||
- MONGO_INITDB_ROOT_PASSWORD=test
|
||||
- MONGO_INITDB_AUTH_MECHANISM=SCRAM-SHA-1
|
||||
volumes:
|
||||
- mongo_data:/data/db
|
||||
|
||||
|
@ -2,66 +2,64 @@ openapi: 3.0.0
|
||||
info:
|
||||
title: Codeword Recovery Service API
|
||||
version: 1.0.0
|
||||
description: API for handling password recovery for the Codeword service.
|
||||
description: API для обработки восстановления паролей для сервиса Codeword.
|
||||
|
||||
tags:
|
||||
- name: recover
|
||||
description: Операции связанные с восстановлением пароля
|
||||
- name: promocode
|
||||
description: Операции связанные с промокодами
|
||||
- name: stats
|
||||
description: Операции связанные со статистикой
|
||||
|
||||
paths:
|
||||
/liveness:
|
||||
get:
|
||||
operationId: Liveness
|
||||
summary: Роут проверки активности
|
||||
tags:
|
||||
- recover
|
||||
responses:
|
||||
'200':
|
||||
description: Успех – сервис запущен
|
||||
|
||||
/readiness:
|
||||
get:
|
||||
summary: Роут проверки базы данных
|
||||
operationId: Readiness
|
||||
summary: Роут проверки баз данных
|
||||
tags:
|
||||
- recover
|
||||
responses:
|
||||
'200':
|
||||
description: Успех — сервис готов и соединение с БД живо
|
||||
'503':
|
||||
description: Служба недоступна — не удалось выполнить проверку связи с БД
|
||||
|
||||
/recover:
|
||||
post:
|
||||
summary: Запустите процесс восстановления пароля
|
||||
operationId: Recovery
|
||||
summary: Восстановления пароля
|
||||
tags:
|
||||
- recover
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
description: Электронная почта, на которую нужно отправить инструкции по восстановлению
|
||||
Referrer:
|
||||
type: string
|
||||
description: URL-адрес referral, если он доступен
|
||||
RedirectionURL:
|
||||
type: string
|
||||
description: URL-адрес, на который перенаправляется пользователь после отправки электронного письма
|
||||
|
||||
$ref: '#/components/schemas/RecoveryReq'
|
||||
responses:
|
||||
'200':
|
||||
description: Запрос на восстановление принят, и возвращен идентификатор записи восстановления
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Идентификатор запроса на восстановление
|
||||
description: Запрос на восстановление принят
|
||||
'404':
|
||||
description: Пользователь не найден по электронной почте
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера – разные причины
|
||||
description: Внутренняя ошибка сервера
|
||||
|
||||
/recover/{sign}:
|
||||
get:
|
||||
summary: Обработать ссылку восстановления, в которой содержится подпись и обменять ее на токены
|
||||
operationId: RecoveryLink
|
||||
summary: Обработать ссылку восстановления и обменять ее на токены
|
||||
tags:
|
||||
- recover
|
||||
parameters:
|
||||
- in: path
|
||||
name: sign
|
||||
@ -71,103 +69,67 @@ paths:
|
||||
description: Подпись восстановления как часть URL-адреса восстановления
|
||||
responses:
|
||||
'200':
|
||||
description: Восстановление успешно, информация для обмена токенов возвращена
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
accessToken:
|
||||
type: string
|
||||
refreshToken:
|
||||
type: string
|
||||
description: Восстановление успешно, информация для обмена токенов возвращена в cookie
|
||||
'406':
|
||||
description: NotAcceptable - срок действия ссылки для восстановления истек или она недействительна
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера – разные причины
|
||||
description: Внутренняя ошибка сервера
|
||||
|
||||
/promocode/create:
|
||||
post:
|
||||
operationId: CreatePromoCode
|
||||
summary: Создать новый промокод
|
||||
tags:
|
||||
- promocode
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PromoCodeRequest'
|
||||
$ref: '#/components/schemas/PromoCodeReq'
|
||||
responses:
|
||||
'201':
|
||||
'200':
|
||||
description: Новый промокод успешно создан
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PromoCodeResponse'
|
||||
$ref: '#/components/schemas/PromoCode'
|
||||
'400':
|
||||
description: Invalid request payload / Duplicate Codeword
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: Неверный формат запроса или дублирующийся codeword
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
|
||||
/promocode/edit:
|
||||
put:
|
||||
operationId: EditPromoCode
|
||||
summary: Обновить существующий промокод
|
||||
tags:
|
||||
- promocode
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReqEditPromoCode'
|
||||
$ref: '#/components/schemas/EditPromoCodeReq'
|
||||
responses:
|
||||
'200':
|
||||
description: Промокод успешно обновлен
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PromoCodeResponse'
|
||||
$ref: '#/components/schemas/PromoCode'
|
||||
'400':
|
||||
description: Неверный формат запроса
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
'404':
|
||||
description: Промокод не найден
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
|
||||
/promocode/getList:
|
||||
post:
|
||||
summary: Получить список промокодов
|
||||
operationId: GetList
|
||||
summary: Получить список промокодов с пагинацией
|
||||
tags:
|
||||
- promocode
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@ -183,19 +145,15 @@ paths:
|
||||
$ref: '#/components/schemas/GetPromoCodesListResp'
|
||||
'400':
|
||||
description: Неверный запрос из-за невалидных данных
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера
|
||||
|
||||
/promocode/activate:
|
||||
post:
|
||||
operationId: Activate
|
||||
summary: Активировать промокод
|
||||
tags:
|
||||
- promocode
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@ -210,7 +168,7 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ActivateResp'
|
||||
'400':
|
||||
description: Невалидный запрос или отсутствует обязательное поле codeword
|
||||
description: Невалидный запрос или отсутствует обязательное поле codeword или fastLink
|
||||
'404':
|
||||
description: Промокод не найден
|
||||
'500':
|
||||
@ -218,7 +176,10 @@ paths:
|
||||
|
||||
/promocode/{promocodeID}:
|
||||
delete:
|
||||
operationId: Delete
|
||||
summary: Мягко удалить промокод по его id
|
||||
tags:
|
||||
- promocode
|
||||
parameters:
|
||||
- in: path
|
||||
name: promocodeID
|
||||
@ -231,39 +192,81 @@ paths:
|
||||
description: Промокод успешно помечен как удаленный
|
||||
'400':
|
||||
description: Неверный запрос, отсутствует идентификатор промокода
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
'404':
|
||||
description: Промокод не найден
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера
|
||||
|
||||
/promocode/fastlink:
|
||||
post:
|
||||
operationId: CreateFastLink
|
||||
summary: Создать быструю ссылку для промокода
|
||||
tags:
|
||||
- promocode
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateFastLinkReq'
|
||||
responses:
|
||||
'200':
|
||||
description: Быстрая ссылка для промокода успешно создана
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
$ref: '#/components/schemas/CreateFastLinkResp'
|
||||
'400':
|
||||
description: Неверный запрос, отсутствует идентификатор промокода
|
||||
'404':
|
||||
description: Промокод не найден
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера
|
||||
|
||||
/promocode/stats:
|
||||
get:
|
||||
operationId: GetStats
|
||||
summary: Получить статистику промокода
|
||||
tags:
|
||||
- stats
|
||||
description: Идентификатор промокода
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PromoCodeStatsReq'
|
||||
responses:
|
||||
'200':
|
||||
description: Статистика промокода успешно получена
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PromoCodeStatsResp'
|
||||
'400':
|
||||
description: Неверный запрос
|
||||
'500':
|
||||
description: Внутренняя ошибка сервера
|
||||
|
||||
|
||||
components:
|
||||
schemas:
|
||||
PromoCodeRequest:
|
||||
RecoveryReq:
|
||||
type: object
|
||||
required:
|
||||
- email
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
description: Электронная почта, на которую нужно отправить инструкции по восстановлению
|
||||
redirectionURL:
|
||||
type: string
|
||||
description: URL-адрес, на который перенаправляется пользователь
|
||||
PromoCodeStatsReq:
|
||||
type: object
|
||||
properties:
|
||||
codeword:
|
||||
promoCodeID:
|
||||
type: string
|
||||
description: Кодовое слово, которое должен ввести пользователь
|
||||
description:
|
||||
@ -311,61 +314,30 @@ components:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: ID созданного промокода
|
||||
codeword:
|
||||
type: string
|
||||
description: Кодовое слово промокода
|
||||
description:
|
||||
type: string
|
||||
description: Описание промокода
|
||||
greetings:
|
||||
type: string
|
||||
description: Текст, который будет отправлен пользователю в ответ на активацию кода
|
||||
dueTo:
|
||||
description: Идентификатор промокода
|
||||
usageCount:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Временная метка окончания активации кода
|
||||
activationCount:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Лимит активации кода
|
||||
bonus:
|
||||
description: Количество использований промокода
|
||||
usageMap:
|
||||
type: object
|
||||
properties:
|
||||
privilege:
|
||||
type: object
|
||||
properties:
|
||||
privilegeID:
|
||||
type: string
|
||||
description: Идентификатор привилегии, которую необходимо предоставить
|
||||
amount:
|
||||
type: integer
|
||||
format: uint64
|
||||
description: Размер привилегии
|
||||
discount:
|
||||
type: object
|
||||
properties:
|
||||
layer:
|
||||
type: integer
|
||||
factor:
|
||||
type: number
|
||||
target:
|
||||
type: string
|
||||
threshold:
|
||||
type: integer
|
||||
description: Информация о бонусах
|
||||
outdated:
|
||||
type: boolean
|
||||
offLimit:
|
||||
type: boolean
|
||||
delete:
|
||||
type: boolean
|
||||
createdAt:
|
||||
description: Карта использования промокода
|
||||
additionalProperties:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Usage'
|
||||
|
||||
Usage:
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
description: fastlink или codeword в зависимости от того что применялось
|
||||
time:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Время создания промокода
|
||||
description: Время использования промокода
|
||||
|
||||
ReqEditPromoCode:
|
||||
CreateFastLinkReq:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
@ -430,27 +402,12 @@ components:
|
||||
filter:
|
||||
$ref: '#/components/schemas/GetPromoCodesListReqFilter'
|
||||
|
||||
GetPromoCodesListReqFilter:
|
||||
CreateFastLinkResp:
|
||||
type: object
|
||||
properties:
|
||||
text:
|
||||
fastlink:
|
||||
type: string
|
||||
description: Полнотекстовый поиск по полям Codeword, Description, Greetings
|
||||
active:
|
||||
type: boolean
|
||||
description: Если true, выбираются записи, где delete, outdated и offLimit равны false
|
||||
|
||||
GetPromoCodesListResp:
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Общее количество промокодов в выборке
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PromoCodeResponse'
|
||||
description: Быстрая ссылка для активации промокода
|
||||
|
||||
ActivateReq:
|
||||
type: object
|
||||
@ -459,11 +416,188 @@ components:
|
||||
properties:
|
||||
codeword:
|
||||
type: string
|
||||
description: Кодовое слово промокода, которое требуется активировать
|
||||
description: Кодовое слово для активации промокода
|
||||
fastLink:
|
||||
type: string
|
||||
description: Быстрая ссылка для активации промокода
|
||||
|
||||
ActivateResp:
|
||||
type: object
|
||||
properties:
|
||||
greetings:
|
||||
type: string
|
||||
description: Поле из активированного промокода
|
||||
description: Слово успешной активации промокода
|
||||
|
||||
GetPromoCodesListReq:
|
||||
type: object
|
||||
required:
|
||||
- page
|
||||
- limit
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
description: Номер страницы
|
||||
limit:
|
||||
type: integer
|
||||
description: Максимальное количество элементов на странице
|
||||
filter:
|
||||
$ref: '#/components/schemas/Filter'
|
||||
|
||||
Filter:
|
||||
type: object
|
||||
properties:
|
||||
text:
|
||||
type: string
|
||||
description: Текстовый фильтр для поиска промокодов
|
||||
active:
|
||||
type: boolean
|
||||
description: Флаг для фильтрации активных промокодов
|
||||
|
||||
GetPromoCodesListResp:
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
description: Общее количество промокодов
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PromoCode'
|
||||
|
||||
PromoCode:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Идентификатор промокода
|
||||
codeword:
|
||||
type: string
|
||||
description: Кодовое слово для активации промокода
|
||||
description:
|
||||
type: string
|
||||
description: Описание промокода
|
||||
greetings:
|
||||
type: string
|
||||
description: Приветственное сообщение после активации промокода
|
||||
dueTo:
|
||||
type: integer
|
||||
description: Дата истечения действия промокода в формате Unix time
|
||||
activationCount:
|
||||
type: integer
|
||||
description: Количество активаций промокода
|
||||
bonus:
|
||||
type: object
|
||||
description: Бонус, предоставляемый с промокодом
|
||||
items:
|
||||
$ref: '#/components/schemas/Bonus'
|
||||
outdated:
|
||||
type: boolean
|
||||
description: Флаг
|
||||
offLimit:
|
||||
type: boolean
|
||||
description: Флаг
|
||||
delete:
|
||||
type: boolean
|
||||
description: Флаг
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Дата и время создания промокода
|
||||
fastLinks:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Список быстрых ссылок для активации промокода
|
||||
|
||||
EditPromoCodeReq:
|
||||
type: object
|
||||
properties:
|
||||
ID:
|
||||
type: string
|
||||
description: Идентификатор промокода, который требуется обновить
|
||||
Description:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Описание промокода
|
||||
Greetings:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Приветственное сообщение после активации промокода
|
||||
DueTo:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: Дата окончания промокода в формате Unix time
|
||||
ActivationCount:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: Количество активаций промокода
|
||||
Delete:
|
||||
type: boolean
|
||||
nullable: true
|
||||
description: Флаг удаления промокода
|
||||
PromoCodeReq:
|
||||
type: object
|
||||
properties:
|
||||
codeword:
|
||||
type: string
|
||||
description: Кодовое слово для активации промокода
|
||||
description:
|
||||
type: string
|
||||
description: Описание промокода
|
||||
greetings:
|
||||
type: string
|
||||
description: Приветственное сообщение после активации промокода
|
||||
dueTo:
|
||||
type: integer
|
||||
description: Дата истечения действия промокода в формате Unix time
|
||||
activationCount:
|
||||
type: integer
|
||||
description: Количество активаций промокода
|
||||
bonus:
|
||||
type: object
|
||||
description: Бонус
|
||||
items:
|
||||
$ref: '#/components/schemas/Bonus'
|
||||
fastLinks:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Список быстрых ссылок для активации промокода
|
||||
|
||||
Bonus:
|
||||
type: object
|
||||
description: Бонус
|
||||
properties:
|
||||
privilege:
|
||||
$ref: '#/components/schemas/Privilege'
|
||||
discount:
|
||||
$ref: '#/components/schemas/Discount'
|
||||
|
||||
Privilege:
|
||||
type: object
|
||||
description: Привилегия
|
||||
properties:
|
||||
privilegeID:
|
||||
type: string
|
||||
description: Идентификатор привилегии
|
||||
amount:
|
||||
type: integer
|
||||
description: Количество привилегии
|
||||
|
||||
Discount:
|
||||
type: object
|
||||
description: Скидка
|
||||
properties:
|
||||
layer:
|
||||
type: integer
|
||||
description: Уровень скидки
|
||||
factor:
|
||||
type: integer
|
||||
description: Множитель скидки
|
||||
target:
|
||||
type: string
|
||||
description: Цель скидки
|
||||
threshold:
|
||||
type: integer
|
||||
description: Порог скидки
|
||||
|
||||
|
27
go.mod
27
go.mod
@ -6,27 +6,38 @@ require (
|
||||
github.com/caarlos0/env/v8 v8.0.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/gofiber/fiber/v2 v2.51.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/pioz/faker v1.7.3
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/twmb/franz-go v1.15.4
|
||||
go.mongodb.org/mongo-driver v1.13.1
|
||||
go.uber.org/zap v1.26.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac
|
||||
google.golang.org/grpc v1.60.1
|
||||
google.golang.org/protobuf v1.32.0
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.19 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.7.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.50.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
@ -35,9 +46,13 @@ require (
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sync v0.4.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
72
go.sum
72
go.sum
@ -2,8 +2,8 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0=
|
||||
github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/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=
|
||||
@ -15,10 +15,17 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
|
||||
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
@ -26,6 +33,12 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
@ -41,20 +54,24 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4=
|
||||
github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pioz/faker v1.7.3 h1:Tez8Emuq0UN+/d6mo3a9m/9ZZ/zdfJk0c5RtRatrceM=
|
||||
github.com/pioz/faker v1.7.3/go.mod h1:xSpay5w/oz1a6+ww0M3vfpe40pSIykeUPeWEc3TvVlc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/twmb/franz-go v1.15.4 h1:qBCkHaiutetnrXjAUWA99D9FEcZVMt2AYwkH3vWEQTw=
|
||||
github.com/twmb/franz-go v1.15.4/go.mod h1:rC18hqNmfo8TMc1kz7CQmHL74PLNF8KVvhflxiiJZCU=
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.7.0 h1:a457IbvezYfA5UkiBvyV3zj0Is3y1i8EJgqjJYoij2E=
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.7.0/go.mod h1:se9Mjdt0Nwzc9lnjJ0HyDtLyBnaBDAd7pCje47OhSyw=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
|
||||
@ -81,18 +98,19 @@ 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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -101,8 +119,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -111,16 +129,28 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg=
|
||||
google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac h1:OZkkudMUu9LVQMCoRUbI/1p5VCo9BOrlvkqMvWtqa6s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
||||
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
||||
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
@ -128,3 +158,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d h1:gbaDt35HMDqOK84WYmDIlXMI7rstUcRqNttaT6Kx1do=
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d/go.mod h1:lTmpjry+8evVkXWbEC+WMOELcFkRD1lFMc7J09mOndM=
|
||||
|
@ -9,17 +9,16 @@ import (
|
||||
)
|
||||
|
||||
type RecoveryEmailSenderDeps struct {
|
||||
SmtpApiUrl string
|
||||
SmtpHost string
|
||||
SmtpPort string
|
||||
SmtpSender string
|
||||
Username string
|
||||
Password string
|
||||
ApiKey string
|
||||
FiberClient *fiber.Client
|
||||
Logger *zap.Logger
|
||||
CodewordHost string
|
||||
CodewordPort string
|
||||
SmtpApiUrl string
|
||||
SmtpHost string
|
||||
SmtpPort string
|
||||
SmtpSender string
|
||||
Username string
|
||||
Password string
|
||||
ApiKey string
|
||||
FiberClient *fiber.Client
|
||||
Logger *zap.Logger
|
||||
RecoveryUrl string
|
||||
}
|
||||
|
||||
type RecoveryEmailSender struct {
|
||||
@ -40,7 +39,7 @@ func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature string)
|
||||
|
||||
fmt.Println(email, signature)
|
||||
|
||||
message := fmt.Sprintf("http://"+r.deps.CodewordHost+":"+r.deps.CodewordPort+"/recover/%s", signature)
|
||||
message := r.deps.RecoveryUrl + signature
|
||||
|
||||
form := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(form)
|
||||
@ -48,7 +47,7 @@ func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature string)
|
||||
|
||||
fields := map[string]string{
|
||||
"from": r.deps.SmtpSender,
|
||||
"to": "pashamullin202@gmail.com",
|
||||
"to": email,
|
||||
"subject": "Восстановление доступа",
|
||||
"html": message,
|
||||
}
|
||||
|
@ -9,13 +9,28 @@ import (
|
||||
"codeword/internal/services"
|
||||
"codeword/internal/worker/purge_worker"
|
||||
"codeword/internal/worker/recovery_worker"
|
||||
"codeword/pkg/closer"
|
||||
"codeword/utils"
|
||||
"context"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"errors"
|
||||
"github.com/twmb/franz-go/pkg/kgo"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||
logger.Info("Запуск приложения", zap.String("AppName", cfg.AppName))
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("Recovered from a panic", zap.Any("error", r))
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Info("Starting application", zap.String("AppName", cfg.AppName))
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
shutdownGroup := closer.NewCloserGroup()
|
||||
|
||||
mdb, err := initialize.MongoDB(ctx, cfg)
|
||||
if err != nil {
|
||||
@ -23,10 +38,47 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = initialize.InitDatabaseIndexes(ctx, mdb, logger); err != nil {
|
||||
logger.Error("Failed to initialize db indexes", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
kafkaTariffClient, err := kgo.NewClient(
|
||||
kgo.SeedBrokers(cfg.KafkaBrokers),
|
||||
kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()),
|
||||
kgo.DefaultProduceTopic(cfg.KafkaTopic),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = kafkaTariffClient.Ping(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
discountRpcClient, err := initialize.DiscountGRPCClient(cfg.DiscountServiceAddress)
|
||||
if err != nil {
|
||||
logger.Error("failed to connect to discount service", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
brokers := initialize.NewBrokers(initialize.BrokersDeps{
|
||||
Logger: logger,
|
||||
TariffClient: kafkaTariffClient,
|
||||
Topic: cfg.KafkaTopic,
|
||||
})
|
||||
|
||||
rdb, err := initialize.Redis(ctx, cfg)
|
||||
if err != nil {
|
||||
logger.Error("failed to connect to redis db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
encrypt := initialize.Encrypt(cfg)
|
||||
|
||||
promoCodeRepo := repository.NewPromoCodeRepository(mdb.Collection("promoCodes"))
|
||||
statsRepo := repository.NewStatsRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("promoStats")})
|
||||
codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")})
|
||||
userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")})
|
||||
|
||||
@ -42,12 +94,18 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||
})
|
||||
|
||||
promoService := services.NewPromoCodeService(services.PromoDeps{
|
||||
Logger: logger,
|
||||
PromoCodeRepo: promoCodeRepo,
|
||||
Logger: logger,
|
||||
PromoCodeRepo: promoCodeRepo,
|
||||
StatsRepo: statsRepo,
|
||||
Kafka: brokers.TariffProducer,
|
||||
DiscountClient: discountRpcClient,
|
||||
})
|
||||
|
||||
jwtUtil := utils.NewJWT(&cfg)
|
||||
authMiddleware := utils.NewAuthenticator(jwtUtil)
|
||||
|
||||
recoveryController := recovery.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL)
|
||||
promoCodeController := promocode.NewPromoCodeController(logger, promoService)
|
||||
promoCodeController := promocode.NewPromoCodeController(promocode.Deps{Logger: logger, PromoCodeService: promoService, AuthMiddleware: authMiddleware})
|
||||
|
||||
recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{
|
||||
Logger: logger,
|
||||
@ -56,7 +114,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||
Mongo: mdb.Collection("codeword"),
|
||||
})
|
||||
|
||||
purgeWC := purge_worker.NewRecoveryWC(purge_worker.Deps{
|
||||
purgeWC := purge_worker.NewPurgeWC(purge_worker.Deps{
|
||||
Logger: logger,
|
||||
Mongo: mdb.Collection("codeword"),
|
||||
})
|
||||
@ -65,52 +123,37 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||
go purgeWC.Start(ctx)
|
||||
|
||||
server := httpserver.NewServer(httpserver.ServerConfig{
|
||||
Logger: logger,
|
||||
RecoveryController: recoveryController,
|
||||
PromoCodeController: promoCodeController,
|
||||
Logger: logger,
|
||||
Controllers: []httpserver.Controller{recoveryController, promoCodeController},
|
||||
})
|
||||
|
||||
go func() {
|
||||
if err := server.Start(cfg.HTTPHost + ":" + cfg.HTTPPort); err != nil {
|
||||
logger.Error("Server startup error", zap.Error(err))
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
server.ListRoutes()
|
||||
|
||||
shutdownGroup.Add(closer.CloserFunc(server.Shutdown))
|
||||
shutdownGroup.Add(closer.CloserFunc(mdb.Client().Disconnect))
|
||||
shutdownGroup.Add(closer.CloserFunc(recoveryWC.Stop))
|
||||
shutdownGroup.Add(closer.CloserFunc(purgeWC.Stop))
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
if err := shutdownApp(ctx, server, mdb, logger); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("The application has stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO возможно стоит вынести в отдельные файлы или отказаться от разделения на отдельные методы
|
||||
|
||||
func shutdownApp(ctx context.Context, server *httpserver.Server, mdb *mongo.Database, logger *zap.Logger) error {
|
||||
if err := shutdownHTTPServer(ctx, server, logger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := shutdownMongoDB(ctx, mdb, logger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func shutdownHTTPServer(ctx context.Context, server *httpserver.Server, logger *zap.Logger) error {
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
logger.Error("Error stopping HTTP server", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func shutdownMongoDB(ctx context.Context, mdb *mongo.Database, logger *zap.Logger) error {
|
||||
if err := mdb.Client().Disconnect(ctx); err != nil {
|
||||
logger.Error("Error when closing MongoDB connection", zap.Error(err))
|
||||
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer timeoutCancel()
|
||||
if err := shutdownGroup.Call(timeoutCtx); err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
logger.Error("Shutdown timed out", zap.Error(err))
|
||||
} else {
|
||||
logger.Error("Failed to shutdown services gracefully", zap.Error(err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("Application has stopped")
|
||||
return nil
|
||||
}
|
||||
|
@ -9,15 +9,23 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Deps struct {
|
||||
Logger *zap.Logger
|
||||
PromoCodeService *services.PromoCodeService
|
||||
AuthMiddleware func(*fiber.Ctx) error
|
||||
}
|
||||
|
||||
type PromoCodeController struct {
|
||||
logger *zap.Logger
|
||||
promoCodeService *services.PromoCodeService
|
||||
authMiddleware func(*fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewPromoCodeController(logger *zap.Logger, promoCodeService *services.PromoCodeService) *PromoCodeController {
|
||||
func NewPromoCodeController(deps Deps) *PromoCodeController {
|
||||
return &PromoCodeController{
|
||||
logger: logger,
|
||||
promoCodeService: promoCodeService,
|
||||
logger: deps.Logger,
|
||||
promoCodeService: deps.PromoCodeService,
|
||||
authMiddleware: deps.AuthMiddleware,
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +35,10 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||
}
|
||||
|
||||
if req.Codeword == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword is required"})
|
||||
}
|
||||
|
||||
createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &req)
|
||||
if err != nil {
|
||||
p.logger.Error("Failed to create promocode", zap.Error(err))
|
||||
@ -38,7 +50,7 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(createdPromoCode)
|
||||
return c.Status(fiber.StatusOK).JSON(createdPromoCode)
|
||||
}
|
||||
|
||||
func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error {
|
||||
@ -84,30 +96,44 @@ func (p *PromoCodeController) GetList(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func (p *PromoCodeController) Activate(c *fiber.Ctx) error {
|
||||
err := p.authMiddleware(c)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err})
|
||||
}
|
||||
|
||||
userID := c.Locals(models.AuthJWTDecodedUserIDKey).(string)
|
||||
if userID == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "failed to get jwt payload"})
|
||||
}
|
||||
|
||||
var req models.ActivateReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||
}
|
||||
|
||||
if req.Codeword == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword is required"})
|
||||
if req.Codeword == "" && req.FastLink == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword or fastlink is required"})
|
||||
}
|
||||
|
||||
greetings, err := p.promoCodeService.ActivatePromo(c.Context(), &req)
|
||||
greetings, err := p.promoCodeService.ActivatePromo(c.Context(), &req, userID)
|
||||
if err != nil {
|
||||
p.logger.Error("Failed to activate promocode", zap.Error(err))
|
||||
|
||||
if errors.Is(err, repository.ErrPromoCodeNotFound) {
|
||||
switch {
|
||||
case errors.Is(err, repository.ErrPromoCodeNotFound):
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "PromoCode not found"})
|
||||
case errors.Is(err, repository.ErrPromoCodeAlreadyActivated):
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "PromoCode already activated"})
|
||||
case errors.Is(err, repository.ErrPromoCodeExpired):
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
||||
case errors.Is(err, repository.ErrPromoCodeExhausted):
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PromoCode exhausted"})
|
||||
default:
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
|
||||
resp := models.ActivateResp{
|
||||
Greetings: greetings,
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(resp)
|
||||
return c.Status(fiber.StatusOK).JSON(models.ActivateResp{Greetings: greetings})
|
||||
}
|
||||
|
||||
func (p *PromoCodeController) Delete(c *fiber.Ctx) error {
|
||||
@ -130,3 +156,50 @@ func (p *PromoCodeController) Delete(c *fiber.Ctx) error {
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func (p *PromoCodeController) CreateFastLink(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
PromoCodeID string `json:"id"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||
}
|
||||
|
||||
if req.PromoCodeID == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PromoCode ID is required"})
|
||||
}
|
||||
|
||||
fastLink, err := p.promoCodeService.CreateFastLink(c.Context(), req.PromoCodeID)
|
||||
if err != nil {
|
||||
p.logger.Error("Failed to create fastlink", zap.Error(err))
|
||||
|
||||
if errors.Is(err, repository.ErrPromoCodeNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "PromoCode not found"})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"fastlink": fastLink})
|
||||
}
|
||||
|
||||
func (p *PromoCodeController) GetStats(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
PromoCodeID string `json:"id"`
|
||||
}
|
||||
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||
}
|
||||
|
||||
if req.PromoCodeID == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PromoCode ID is required"})
|
||||
}
|
||||
|
||||
promoStats, err := p.promoCodeService.GetStats(c.Context(), req.PromoCodeID)
|
||||
if err != nil {
|
||||
p.logger.Error("Failed getting promo stats", zap.Error(err))
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(promoStats)
|
||||
}
|
||||
|
18
internal/controller/promocode/route.go
Normal file
18
internal/controller/promocode/route.go
Normal file
@ -0,0 +1,18 @@
|
||||
package promocode
|
||||
|
||||
import "github.com/gofiber/fiber/v2"
|
||||
|
||||
func (p *PromoCodeController) Register(router fiber.Router) {
|
||||
router.Post("/create", p.CreatePromoCode)
|
||||
router.Put("/edit", p.EditPromoCode)
|
||||
router.Post("/getList", p.GetList)
|
||||
router.Post("/activate", p.Activate)
|
||||
router.Delete("/:promocodeID", p.Delete)
|
||||
router.Post("/fastlink", p.CreateFastLink)
|
||||
router.Get("/stats", p.GetStats)
|
||||
|
||||
}
|
||||
|
||||
func (p *PromoCodeController) Name() string {
|
||||
return "promocode"
|
||||
}
|
@ -2,8 +2,11 @@ package recovery
|
||||
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"codeword/internal/repository"
|
||||
"codeword/internal/services"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
@ -23,23 +26,44 @@ func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error {
|
||||
return r.service.Ping(c.Context())
|
||||
func (r *RecoveryController) HandleLiveness(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
// HandleRecoveryRequest обрабатывает запрос на восстановление пароля
|
||||
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
||||
email := c.FormValue("email")
|
||||
referralURL := c.Get("Referrer")
|
||||
redirectionURL := c.FormValue("RedirectionURL")
|
||||
func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error {
|
||||
startTime := time.Now()
|
||||
if err := r.service.Ping(c.Context()); err != nil {
|
||||
r.logger.Error("Failed to ping the database", zap.Error(err))
|
||||
return c.Status(fiber.StatusServiceUnavailable).SendString("DB ping failed")
|
||||
}
|
||||
duration := time.Since(startTime)
|
||||
|
||||
if redirectionURL == "" && referralURL != "" {
|
||||
redirectionURL = referralURL
|
||||
} else if redirectionURL == "" {
|
||||
redirectionURL = r.defaultURL
|
||||
durationMillis := duration.Milliseconds()
|
||||
responseMessage := fmt.Sprintf("DB ping success - Time taken: %d ms", durationMillis)
|
||||
|
||||
return c.Status(fiber.StatusOK).SendString(responseMessage)
|
||||
}
|
||||
|
||||
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
||||
var req models.RecoveryRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
r.logger.Error("Failed to parse recovery request", zap.Error(err))
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Bad Request"})
|
||||
}
|
||||
|
||||
user, err := r.service.FindUserByEmail(c.Context(), email)
|
||||
if req.Email == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "email is required"})
|
||||
}
|
||||
|
||||
referralURL := c.Get("Referrer")
|
||||
|
||||
if req.RedirectionURL == "" && referralURL != "" {
|
||||
req.RedirectionURL = referralURL
|
||||
} else if req.RedirectionURL == "" {
|
||||
req.RedirectionURL = r.defaultURL
|
||||
}
|
||||
|
||||
user, err := r.service.FindUserByEmail(c.Context(), req.Email)
|
||||
if err != nil || user == nil {
|
||||
r.logger.Error("Failed to find user by email", zap.Error(err))
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
|
||||
@ -51,7 +75,7 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
|
||||
signUrl := redirectionURL + base64.URLEncoding.EncodeToString(key)
|
||||
signUrl := req.RedirectionURL
|
||||
sign := base64.URLEncoding.EncodeToString(key)
|
||||
|
||||
id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{UserID: user.ID.Hex(), Email: user.Email, Key: sign, Url: signUrl})
|
||||
@ -62,31 +86,32 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
||||
|
||||
signWithID := sign + id // подпись с id записи
|
||||
|
||||
err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: email, SignWithID: signWithID, ID: id})
|
||||
err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: req.Email, SignWithID: signWithID, ID: id})
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to send recovery email", zap.Error(err))
|
||||
|
||||
if errors.Is(err, repository.ErrAlreadyReported) {
|
||||
return c.Status(fiber.StatusAlreadyReported).JSON(fiber.Map{"error": "already reported"})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
||||
"id": id,
|
||||
})
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Recovery email sent successfully"})
|
||||
|
||||
}
|
||||
|
||||
// todo тут скорее всего помимо подписи будет передаваться еще что-то, например email пользователя от фронта для поиска в бд
|
||||
|
||||
// HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены
|
||||
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
||||
key := c.Params("sign")
|
||||
sign := c.Params("sign")
|
||||
|
||||
record, err := r.service.GetRecoveryRecord(c.Context(), key)
|
||||
record, err := r.service.GetRecoveryRecord(c.Context(), sign)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get recovery record", zap.Error(err))
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
|
||||
if time.Since(record.CreatedAt) > 15*time.Minute {
|
||||
r.logger.Error("Recovery link expired", zap.String("signature", key))
|
||||
r.logger.Error("Recovery link expired", zap.String("signature", sign))
|
||||
return c.Status(fiber.StatusNotAcceptable).JSON(fiber.Map{"error": "Recovery link expired"})
|
||||
}
|
||||
|
||||
@ -96,5 +121,14 @@ func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(tokens)
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "refreshToken",
|
||||
Value: tokens["refreshToken"],
|
||||
Domain: ".pena.digital",
|
||||
Expires: time.Now().Add(30 * 24 * time.Hour),
|
||||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
})
|
||||
|
||||
return c.Redirect(record.SignUrl + "?auth=" + tokens["accessToken"])
|
||||
}
|
||||
|
14
internal/controller/recovery/route.go
Normal file
14
internal/controller/recovery/route.go
Normal file
@ -0,0 +1,14 @@
|
||||
package recovery
|
||||
|
||||
import "github.com/gofiber/fiber/v2"
|
||||
|
||||
func (r *RecoveryController) Register(router fiber.Router) {
|
||||
router.Get("/liveness", r.HandleLiveness)
|
||||
router.Get("/readiness", r.HandlePingDB)
|
||||
router.Post("/recover", r.HandleRecoveryRequest)
|
||||
router.Get("/recover/:sign", r.HandleRecoveryLink)
|
||||
}
|
||||
|
||||
func (r *RecoveryController) Name() string {
|
||||
return ""
|
||||
}
|
@ -8,17 +8,16 @@ import (
|
||||
|
||||
func RecoveryEmailSender(cfg Config, logger *zap.Logger) *client.RecoveryEmailSender {
|
||||
return client.NewRecoveryEmailSender(client.RecoveryEmailSenderDeps{
|
||||
SmtpApiUrl: cfg.SmtpApiUrl,
|
||||
SmtpHost: cfg.SmtpHost,
|
||||
SmtpPort: cfg.SmtpPort,
|
||||
SmtpSender: cfg.SmtpSender,
|
||||
Username: cfg.SmtpUsername,
|
||||
Password: cfg.SmtpPassword,
|
||||
ApiKey: cfg.SmtpApiKey,
|
||||
FiberClient: &fiber.Client{},
|
||||
Logger: logger,
|
||||
CodewordHost: cfg.HTTPHost,
|
||||
CodewordPort: cfg.HTTPPort,
|
||||
SmtpApiUrl: cfg.SmtpApiUrl,
|
||||
SmtpHost: cfg.SmtpHost,
|
||||
SmtpPort: cfg.SmtpPort,
|
||||
SmtpSender: cfg.SmtpSender,
|
||||
Username: cfg.SmtpUsername,
|
||||
Password: cfg.SmtpPassword,
|
||||
ApiKey: cfg.SmtpApiKey,
|
||||
FiberClient: &fiber.Client{},
|
||||
Logger: logger,
|
||||
RecoveryUrl: cfg.RecoveryUrl,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -7,30 +7,38 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
AppName string `env:"APP_NAME" envDefault:"codeword"`
|
||||
HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"`
|
||||
HTTPPort string `env:"HTTP_PORT" envDefault:"3000"`
|
||||
MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"`
|
||||
MongoPort string `env:"MONGO_PORT" envDefault:"27020"`
|
||||
MongoUser string `env:"MONGO_USER" envDefault:"test"`
|
||||
MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"`
|
||||
MongoDatabase string `env:"MONGO_DB" envDefault:"admin"`
|
||||
MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"`
|
||||
PublicCurveKey string `env:"PUBLIC_CURVE_KEY"`
|
||||
PrivateCurveKey string `env:"PRIVATE_CURVE_KEY"`
|
||||
SignSecret string `env:"SIGN_SECRET"`
|
||||
RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"`
|
||||
RedisPassword string `env:"REDIS_PASS" envDefault:"admin"`
|
||||
RedisDB int `env:"REDIS_DB" envDefault:"2"`
|
||||
SmtpApiUrl string `env:"SMTP_API_URL"`
|
||||
SmtpHost string `env:"SMTP_HOST"`
|
||||
SmtpPort string `env:"SMTP_PORT"`
|
||||
SmtpUsername string `env:"SMTP_UNAME"`
|
||||
SmtpPassword string `env:"SMTP_PASS"`
|
||||
SmtpApiKey string `env:"SMTP_API_KEY"`
|
||||
SmtpSender string `env:"SMTP_SENDER"`
|
||||
DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"`
|
||||
AuthURL string `env:"AUTH_EXCHANGE_URL"`
|
||||
AppName string `env:"APP_NAME" envDefault:"codeword"`
|
||||
HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"`
|
||||
HTTPPort string `env:"HTTP_PORT" envDefault:"3000"`
|
||||
MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"`
|
||||
MongoPort string `env:"MONGO_PORT" envDefault:"27020"`
|
||||
MongoUser string `env:"MONGO_USER" envDefault:"test"`
|
||||
MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"`
|
||||
MongoDatabase string `env:"MONGO_DB" envDefault:"admin"`
|
||||
MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"`
|
||||
PublicCurveKey string `env:"PUBLIC_CURVE_KEY"`
|
||||
PrivateCurveKey string `env:"PRIVATE_CURVE_KEY"`
|
||||
SignSecret string `env:"SIGN_SECRET"`
|
||||
RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"`
|
||||
RedisPassword string `env:"REDIS_PASS" envDefault:"admin"`
|
||||
RedisDB int `env:"REDIS_DB" envDefault:"2"`
|
||||
SmtpApiUrl string `env:"SMTP_API_URL"`
|
||||
SmtpHost string `env:"SMTP_HOST"`
|
||||
SmtpPort string `env:"SMTP_PORT"`
|
||||
SmtpUsername string `env:"SMTP_UNAME"`
|
||||
SmtpPassword string `env:"SMTP_PASS"`
|
||||
SmtpApiKey string `env:"SMTP_API_KEY"`
|
||||
SmtpSender string `env:"SMTP_SENDER"`
|
||||
DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"`
|
||||
AuthURL string `env:"AUTH_EXCHANGE_URL"`
|
||||
KafkaBrokers string `env:"KAFKA_BROKERS"`
|
||||
KafkaTopic string `env:"KAFKA_TOPIC_TARIFF"`
|
||||
DiscountServiceAddress string `env:"DISCOUNT_ADDRESS"`
|
||||
RecoveryUrl string `env:"RECOVERY_URL"`
|
||||
PrivateKey string `env:"JWT_PRIVATE_KEY"`
|
||||
PublicKey string `env:"JWT_PUBLIC_KEY,required"`
|
||||
Issuer string `env:"JWT_ISSUER,required"`
|
||||
Audience string `env:"JWT_AUDIENCE,required"`
|
||||
}
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
|
26
internal/initialize/grpc.go
Normal file
26
internal/initialize/grpc.go
Normal file
@ -0,0 +1,26 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"codeword/internal/proto/discount"
|
||||
"context"
|
||||
"google.golang.org/grpc"
|
||||
"time"
|
||||
)
|
||||
|
||||
func DiscountGRPCClient(address string) (discount.DiscountServiceClient, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
options := []grpc.DialOption{
|
||||
grpc.WithInsecure(),
|
||||
//grpc.WithBlock(),
|
||||
}
|
||||
|
||||
conn, err := grpc.DialContext(ctx, address, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
discountClient := discount.NewDiscountServiceClient(conn)
|
||||
return discountClient, nil
|
||||
}
|
27
internal/initialize/kafka.go
Normal file
27
internal/initialize/kafka.go
Normal file
@ -0,0 +1,27 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"codeword/internal/kafka/tariff"
|
||||
"github.com/twmb/franz-go/pkg/kgo"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type BrokersDeps struct {
|
||||
Logger *zap.Logger
|
||||
TariffClient *kgo.Client
|
||||
Topic string
|
||||
}
|
||||
|
||||
type Brokers struct {
|
||||
TariffProducer *tariff.Producer
|
||||
}
|
||||
|
||||
func NewBrokers(deps BrokersDeps) *Brokers {
|
||||
return &Brokers{
|
||||
TariffProducer: tariff.NewProducer(tariff.ProducerDeps{
|
||||
Logger: deps.Logger,
|
||||
Client: deps.TariffClient,
|
||||
Topic: deps.Topic,
|
||||
}),
|
||||
}
|
||||
}
|
@ -1,36 +1,50 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
mdb "codeword/pkg/mongo"
|
||||
"codeword/internal/repository"
|
||||
"context"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.uber.org/zap"
|
||||
mdb "penahub.gitlab.yandexcloud.net/backend/penahub_common/mongo"
|
||||
"time"
|
||||
)
|
||||
|
||||
func MongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) {
|
||||
dbConfig := &mdb.Configuration{
|
||||
MongoHost: cfg.MongoHost,
|
||||
MongoPort: cfg.MongoPort,
|
||||
MongoUser: cfg.MongoUser,
|
||||
MongoPassword: cfg.MongoPassword,
|
||||
MongoDatabase: cfg.MongoDatabase,
|
||||
MongoAuth: cfg.MongoAuth,
|
||||
Host: cfg.MongoHost,
|
||||
Port: cfg.MongoPort,
|
||||
User: cfg.MongoUser,
|
||||
Password: cfg.MongoPassword,
|
||||
DatabaseName: cfg.MongoDatabase,
|
||||
Auth: cfg.MongoAuth,
|
||||
}
|
||||
|
||||
newCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoDeps := &mdb.ConnectDeps{
|
||||
Configuration: dbConfig,
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
db, err := mdb.Connect(ctx, mongoDeps)
|
||||
db, err := mdb.Connect(newCtx, mongoDeps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.Client().Ping(ctx, nil)
|
||||
err = db.Client().Ping(newCtx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func InitDatabaseIndexes(ctx context.Context, mdb *mongo.Database, logger *zap.Logger) error {
|
||||
if err := repository.InitPromoCodeIndexes(ctx, mdb.Collection("promoCodes")); err != nil {
|
||||
logger.Error("Failed to initialize promoCodes indexes", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
56
internal/kafka/tariff/producer.go
Normal file
56
internal/kafka/tariff/producer.go
Normal file
@ -0,0 +1,56 @@
|
||||
package tariff
|
||||
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"codeword/internal/utils/transfer"
|
||||
"context"
|
||||
"github.com/twmb/franz-go/pkg/kgo"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"log"
|
||||
)
|
||||
|
||||
type ProducerDeps struct {
|
||||
Logger *zap.Logger
|
||||
Client *kgo.Client
|
||||
Topic string
|
||||
}
|
||||
|
||||
type Producer struct {
|
||||
logger *zap.Logger
|
||||
client *kgo.Client
|
||||
topic string
|
||||
}
|
||||
|
||||
func NewProducer(deps ProducerDeps) *Producer {
|
||||
if deps.Logger == nil {
|
||||
log.Panicln("logger is nil on <NewTariffProducer>")
|
||||
}
|
||||
|
||||
if deps.Client == nil {
|
||||
log.Panicln("Kafka client is nil on <NewTariffProducer>")
|
||||
}
|
||||
|
||||
return &Producer{
|
||||
logger: deps.Logger,
|
||||
client: deps.Client,
|
||||
topic: deps.Topic,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Producer) Send(ctx context.Context, userID string, tariff *models.Tariff) error {
|
||||
bytes, err := proto.Marshal(transfer.TariffModelToProtoMessage(userID, tariff))
|
||||
if err != nil {
|
||||
p.logger.Error("failed to marshal tariff model", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// упростил, возможно зря, но теперь возвращаем одну ошибку, просто прерываем цикл при первой встретившейся ошибке
|
||||
err = p.client.ProduceSync(ctx, &kgo.Record{Topic: p.topic, Value: bytes}).FirstErr()
|
||||
if err != nil {
|
||||
p.logger.Error("failed to send tariff to Kafka", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -9,3 +9,6 @@ type RefreshResponse struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
}
|
||||
|
||||
const AuthJWTDecodedUserIDKey = "userID"
|
||||
const AuthJWTDecodedAccessTokenKey = "access-token"
|
||||
|
@ -18,7 +18,7 @@ type PromoCode struct {
|
||||
Amount uint64 `json:"amount" bson:"amount"` // количество
|
||||
} `json:"privilege" bson:"privilege"`
|
||||
Discount struct {
|
||||
Layer int `json:"layer" bson:"layer"` // 1|2
|
||||
Layer uint32 `json:"layer" bson:"layer"` // 1|2
|
||||
Factor float64 `json:"factor" bson:"factor"` // процент скидки, вернее множитель, при котором достигается этот процент скидки
|
||||
Target string `json:"target" bson:"target"` // PrivilegeID или ServiceKey в зависимости от слоя
|
||||
Threshold int64 `json:"threshold" bson:"threshold"` // граничное значение, при пересечении которого применяется эта скидка
|
||||
@ -28,6 +28,7 @@ type PromoCode struct {
|
||||
OffLimit bool `json:"offLimit" bson:"offLimit"`
|
||||
Delete bool `json:"delete" bson:"delete"`
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
||||
FastLinks []string `json:"fastLinks" bson:"fastLinks"`
|
||||
}
|
||||
|
||||
type ReqEditPromoCode struct {
|
||||
@ -73,8 +74,20 @@ type GetPromoCodesListResp struct {
|
||||
|
||||
type ActivateReq struct {
|
||||
Codeword string `json:"codeword"`
|
||||
FastLink string `json:"fastLink"`
|
||||
}
|
||||
|
||||
type ActivateResp struct {
|
||||
Greetings string `json:"greetings"` // поле из активированного промокода
|
||||
}
|
||||
|
||||
type PromoCodeStats struct {
|
||||
ID string `bson:"_id,omitempty" json:"id,omitempty"`
|
||||
UsageCount int `bson:"usageCount" json:"usageCount"`
|
||||
UsageMap map[string][]Usage `bson:"usageMap" json:"usageMap"`
|
||||
}
|
||||
|
||||
type Usage struct {
|
||||
Key string `bson:"key" json:"key"`
|
||||
Time time.Time `bson:"time" json:"time"`
|
||||
}
|
||||
|
46
internal/models/tariff.go
Normal file
46
internal/models/tariff.go
Normal file
@ -0,0 +1,46 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"codeword/internal/proto/broker"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Tariff struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
Price uint64 `json:"price,omitempty"`
|
||||
IsCustom bool `json:"isCustom"`
|
||||
Privileges []Privilege `json:"privileges"`
|
||||
Deleted bool `json:"isDeleted"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty"`
|
||||
}
|
||||
|
||||
type Privilege struct {
|
||||
ID string `json:"_id"`
|
||||
Name string `json:"name"`
|
||||
PrivilegeID string `json:"privilegeId"`
|
||||
ServiceKey string `json:"serviceKey"`
|
||||
Description string `json:"description"`
|
||||
Amount uint64 `json:"amount"`
|
||||
Type PrivilegeType `json:"type"`
|
||||
Value string `json:"value"`
|
||||
Price uint64 `json:"price"`
|
||||
}
|
||||
|
||||
type PrivilegeType string
|
||||
|
||||
const (
|
||||
PrivilegeTypeCount = "count"
|
||||
PrivilegeTypeDay = "day"
|
||||
PrivilegeTypeFull = "full"
|
||||
)
|
||||
|
||||
var (
|
||||
PrivilegeBrokerTypeMap = map[PrivilegeType]broker.PrivilegeType{
|
||||
PrivilegeTypeFull: broker.PrivilegeType_Full,
|
||||
PrivilegeTypeDay: broker.PrivilegeType_Day,
|
||||
PrivilegeTypeCount: broker.PrivilegeType_Count,
|
||||
}
|
||||
)
|
@ -35,3 +35,12 @@ type RecoveryRecord struct {
|
||||
Email string
|
||||
Key string
|
||||
}
|
||||
|
||||
type RecoveryRequest struct {
|
||||
Email string `json:"email"`
|
||||
RedirectionURL string `json:"redirectionURL"`
|
||||
}
|
||||
|
||||
type RecoveryLinkRequest struct {
|
||||
Sign string `json:"sign"`
|
||||
}
|
||||
|
314
internal/proto/broker/models.pb.go
Normal file
314
internal/proto/broker/models.pb.go
Normal file
@ -0,0 +1,314 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc (unknown)
|
||||
// source: kafka/models.proto
|
||||
|
||||
package broker
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type PrivilegeType int32
|
||||
|
||||
const (
|
||||
PrivilegeType_Full PrivilegeType = 0
|
||||
PrivilegeType_Day PrivilegeType = 1
|
||||
PrivilegeType_Count PrivilegeType = 2
|
||||
)
|
||||
|
||||
// Enum value maps for PrivilegeType.
|
||||
var (
|
||||
PrivilegeType_name = map[int32]string{
|
||||
0: "Full",
|
||||
1: "Day",
|
||||
2: "Count",
|
||||
}
|
||||
PrivilegeType_value = map[string]int32{
|
||||
"Full": 0,
|
||||
"Day": 1,
|
||||
"Count": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x PrivilegeType) Enum() *PrivilegeType {
|
||||
p := new(PrivilegeType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x PrivilegeType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (PrivilegeType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_broker_models_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (PrivilegeType) Type() protoreflect.EnumType {
|
||||
return &file_broker_models_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x PrivilegeType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PrivilegeType.Descriptor instead.
|
||||
func (PrivilegeType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_broker_models_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type PrivilegeMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
PrivilegeID string `protobuf:"bytes,1,opt,name=PrivilegeID,proto3" json:"PrivilegeID,omitempty"`
|
||||
ServiceKey string `protobuf:"bytes,2,opt,name=ServiceKey,proto3" json:"ServiceKey,omitempty"`
|
||||
Type PrivilegeType `protobuf:"varint,3,opt,name=Type,proto3,enum=kafka.PrivilegeType" json:"Type,omitempty"`
|
||||
Value string `protobuf:"bytes,4,opt,name=Value,proto3" json:"Value,omitempty"`
|
||||
Amount uint64 `protobuf:"varint,5,opt,name=Amount,proto3" json:"Amount,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PrivilegeMessage) Reset() {
|
||||
*x = PrivilegeMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_broker_models_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *PrivilegeMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PrivilegeMessage) ProtoMessage() {}
|
||||
|
||||
func (x *PrivilegeMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_broker_models_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PrivilegeMessage.ProtoReflect.Descriptor instead.
|
||||
func (*PrivilegeMessage) Descriptor() ([]byte, []int) {
|
||||
return file_broker_models_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *PrivilegeMessage) GetPrivilegeID() string {
|
||||
if x != nil {
|
||||
return x.PrivilegeID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PrivilegeMessage) GetServiceKey() string {
|
||||
if x != nil {
|
||||
return x.ServiceKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PrivilegeMessage) GetType() PrivilegeType {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return PrivilegeType_Full
|
||||
}
|
||||
|
||||
func (x *PrivilegeMessage) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PrivilegeMessage) GetAmount() uint64 {
|
||||
if x != nil {
|
||||
return x.Amount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type TariffMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Privileges []*PrivilegeMessage `protobuf:"bytes,1,rep,name=Privileges,proto3" json:"Privileges,omitempty"`
|
||||
UserID string `protobuf:"bytes,2,opt,name=UserID,proto3" json:"UserID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *TariffMessage) Reset() {
|
||||
*x = TariffMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_broker_models_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *TariffMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*TariffMessage) ProtoMessage() {}
|
||||
|
||||
func (x *TariffMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_broker_models_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use TariffMessage.ProtoReflect.Descriptor instead.
|
||||
func (*TariffMessage) Descriptor() ([]byte, []int) {
|
||||
return file_broker_models_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *TariffMessage) GetPrivileges() []*PrivilegeMessage {
|
||||
if x != nil {
|
||||
return x.Privileges
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *TariffMessage) GetUserID() string {
|
||||
if x != nil {
|
||||
return x.UserID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_broker_models_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_broker_models_proto_rawDesc = []byte{
|
||||
0x0a, 0x13, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x22, 0xad, 0x01,
|
||||
0x0a, 0x10, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x49,
|
||||
0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65,
|
||||
0x67, 0x65, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4b,
|
||||
0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x0e, 0x32, 0x15, 0x2e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76,
|
||||
0x69, 0x6c, 0x65, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
|
||||
0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x61, 0x0a,
|
||||
0x0d, 0x54, 0x61, 0x72, 0x69, 0x66, 0x66, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38,
|
||||
0x0a, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76,
|
||||
0x69, 0x6c, 0x65, 0x67, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x50, 0x72,
|
||||
0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x73, 0x65, 0x72,
|
||||
0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44,
|
||||
0x2a, 0x2d, 0x0a, 0x0d, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x54, 0x79, 0x70,
|
||||
0x65, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x44,
|
||||
0x61, 0x79, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x10, 0x02, 0x42,
|
||||
0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_broker_models_proto_rawDescOnce sync.Once
|
||||
file_broker_models_proto_rawDescData = file_broker_models_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_broker_models_proto_rawDescGZIP() []byte {
|
||||
file_broker_models_proto_rawDescOnce.Do(func() {
|
||||
file_broker_models_proto_rawDescData = protoimpl.X.CompressGZIP(file_broker_models_proto_rawDescData)
|
||||
})
|
||||
return file_broker_models_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_broker_models_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_broker_models_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_broker_models_proto_goTypes = []interface{}{
|
||||
(PrivilegeType)(0), // 0: kafka.PrivilegeType
|
||||
(*PrivilegeMessage)(nil), // 1: kafka.PrivilegeMessage
|
||||
(*TariffMessage)(nil), // 2: kafka.TariffMessage
|
||||
}
|
||||
var file_broker_models_proto_depIdxs = []int32{
|
||||
0, // 0: kafka.PrivilegeMessage.Type:type_name -> kafka.PrivilegeType
|
||||
1, // 1: kafka.TariffMessage.Privileges:type_name -> kafka.PrivilegeMessage
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_broker_models_proto_init() }
|
||||
func file_broker_models_proto_init() {
|
||||
if File_broker_models_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_broker_models_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*PrivilegeMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_broker_models_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*TariffMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_broker_models_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_broker_models_proto_goTypes,
|
||||
DependencyIndexes: file_broker_models_proto_depIdxs,
|
||||
EnumInfos: file_broker_models_proto_enumTypes,
|
||||
MessageInfos: file_broker_models_proto_msgTypes,
|
||||
}.Build()
|
||||
File_broker_models_proto = out.File
|
||||
file_broker_models_proto_rawDesc = nil
|
||||
file_broker_models_proto_goTypes = nil
|
||||
file_broker_models_proto_depIdxs = nil
|
||||
}
|
192
internal/proto/discount/audit.model.pb.go
Normal file
192
internal/proto/discount/audit.model.pb.go
Normal file
@ -0,0 +1,192 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc (unknown)
|
||||
// source: discount/audit.model.proto
|
||||
|
||||
package discount
|
||||
|
||||
import (
|
||||
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
_ "google.golang.org/protobuf/types/known/emptypb"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Audit struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"`
|
||||
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"`
|
||||
DeletedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=DeletedAt,proto3,oneof" json:"DeletedAt,omitempty"`
|
||||
Deleted bool `protobuf:"varint,4,opt,name=Deleted,proto3" json:"Deleted,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Audit) Reset() {
|
||||
*x = Audit{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_discount_audit_model_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Audit) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Audit) ProtoMessage() {}
|
||||
|
||||
func (x *Audit) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_discount_audit_model_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Audit.ProtoReflect.Descriptor instead.
|
||||
func (*Audit) Descriptor() ([]byte, []int) {
|
||||
return file_discount_audit_model_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Audit) GetUpdatedAt() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.UpdatedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Audit) GetCreatedAt() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreatedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Audit) GetDeletedAt() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.DeletedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Audit) GetDeleted() bool {
|
||||
if x != nil {
|
||||
return x.Deleted
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var File_discount_audit_model_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_discount_audit_model_proto_rawDesc = []byte{
|
||||
0x0a, 0x1a, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x61, 0x75, 0x64, 0x69, 0x74,
|
||||
0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x64, 0x69,
|
||||
0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61,
|
||||
0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x22, 0xe2, 0x01, 0x0a, 0x05, 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x38, 0x0a, 0x09,
|
||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x64, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74,
|
||||
0x12, 0x3d, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48,
|
||||
0x00, 0x52, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x88, 0x01, 0x01, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x07, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x44, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x64, 0x69, 0x73,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_discount_audit_model_proto_rawDescOnce sync.Once
|
||||
file_discount_audit_model_proto_rawDescData = file_discount_audit_model_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_discount_audit_model_proto_rawDescGZIP() []byte {
|
||||
file_discount_audit_model_proto_rawDescOnce.Do(func() {
|
||||
file_discount_audit_model_proto_rawDescData = protoimpl.X.CompressGZIP(file_discount_audit_model_proto_rawDescData)
|
||||
})
|
||||
return file_discount_audit_model_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_discount_audit_model_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_discount_audit_model_proto_goTypes = []interface{}{
|
||||
(*Audit)(nil), // 0: discount.Audit
|
||||
(*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp
|
||||
}
|
||||
var file_discount_audit_model_proto_depIdxs = []int32{
|
||||
1, // 0: discount.Audit.UpdatedAt:type_name -> google.protobuf.Timestamp
|
||||
1, // 1: discount.Audit.CreatedAt:type_name -> google.protobuf.Timestamp
|
||||
1, // 2: discount.Audit.DeletedAt:type_name -> google.protobuf.Timestamp
|
||||
3, // [3:3] is the sub-list for method output_type
|
||||
3, // [3:3] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_discount_audit_model_proto_init() }
|
||||
func file_discount_audit_model_proto_init() {
|
||||
if File_discount_audit_model_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_discount_audit_model_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Audit); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_discount_audit_model_proto_msgTypes[0].OneofWrappers = []interface{}{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_discount_audit_model_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_discount_audit_model_proto_goTypes,
|
||||
DependencyIndexes: file_discount_audit_model_proto_depIdxs,
|
||||
MessageInfos: file_discount_audit_model_proto_msgTypes,
|
||||
}.Build()
|
||||
File_discount_audit_model_proto = out.File
|
||||
file_discount_audit_model_proto_rawDesc = nil
|
||||
file_discount_audit_model_proto_goTypes = nil
|
||||
file_discount_audit_model_proto_depIdxs = nil
|
||||
}
|
1132
internal/proto/discount/discount.model.pb.go
Normal file
1132
internal/proto/discount/discount.model.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
524
internal/proto/discount/service.pb.go
Normal file
524
internal/proto/discount/service.pb.go
Normal file
@ -0,0 +1,524 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc (unknown)
|
||||
// source: discount/service.proto
|
||||
|
||||
package discount
|
||||
|
||||
import (
|
||||
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type GetDiscountByIDRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetDiscountByIDRequest) Reset() {
|
||||
*x = GetDiscountByIDRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_discount_service_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetDiscountByIDRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetDiscountByIDRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetDiscountByIDRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_discount_service_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetDiscountByIDRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetDiscountByIDRequest) Descriptor() ([]byte, []int) {
|
||||
return file_discount_service_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *GetDiscountByIDRequest) GetID() string {
|
||||
if x != nil {
|
||||
return x.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ApplyDiscountRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
UserInformation *UserInformation `protobuf:"bytes,1,opt,name=UserInformation,proto3" json:"UserInformation,omitempty"`
|
||||
Products []*ProductInformation `protobuf:"bytes,2,rep,name=Products,proto3" json:"Products,omitempty"`
|
||||
Date *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=Date,proto3" json:"Date,omitempty"`
|
||||
Coupon *string `protobuf:"bytes,4,opt,name=Coupon,proto3,oneof" json:"Coupon,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountRequest) Reset() {
|
||||
*x = ApplyDiscountRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_discount_service_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ApplyDiscountRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ApplyDiscountRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_discount_service_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ApplyDiscountRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ApplyDiscountRequest) Descriptor() ([]byte, []int) {
|
||||
return file_discount_service_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountRequest) GetUserInformation() *UserInformation {
|
||||
if x != nil {
|
||||
return x.UserInformation
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountRequest) GetProducts() []*ProductInformation {
|
||||
if x != nil {
|
||||
return x.Products
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountRequest) GetDate() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.Date
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountRequest) GetCoupon() string {
|
||||
if x != nil && x.Coupon != nil {
|
||||
return *x.Coupon
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ApplyDiscountResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Price uint64 `protobuf:"varint,1,opt,name=Price,proto3" json:"Price,omitempty"`
|
||||
AppliedDiscounts []*Discount `protobuf:"bytes,2,rep,name=AppliedDiscounts,proto3" json:"AppliedDiscounts,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountResponse) Reset() {
|
||||
*x = ApplyDiscountResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_discount_service_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ApplyDiscountResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ApplyDiscountResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_discount_service_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ApplyDiscountResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ApplyDiscountResponse) Descriptor() ([]byte, []int) {
|
||||
return file_discount_service_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountResponse) GetPrice() uint64 {
|
||||
if x != nil {
|
||||
return x.Price
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ApplyDiscountResponse) GetAppliedDiscounts() []*Discount {
|
||||
if x != nil {
|
||||
return x.AppliedDiscounts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateDiscountRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
Layer uint32 `protobuf:"varint,2,opt,name=Layer,proto3" json:"Layer,omitempty"`
|
||||
Description string `protobuf:"bytes,3,opt,name=Description,proto3" json:"Description,omitempty"`
|
||||
Condition *DiscountCondition `protobuf:"bytes,4,opt,name=Condition,proto3" json:"Condition,omitempty"`
|
||||
Target *DiscountCalculationTarget `protobuf:"bytes,5,opt,name=Target,proto3" json:"Target,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CreateDiscountRequest) Reset() {
|
||||
*x = CreateDiscountRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_discount_service_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CreateDiscountRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CreateDiscountRequest) ProtoMessage() {}
|
||||
|
||||
func (x *CreateDiscountRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_discount_service_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CreateDiscountRequest.ProtoReflect.Descriptor instead.
|
||||
func (*CreateDiscountRequest) Descriptor() ([]byte, []int) {
|
||||
return file_discount_service_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *CreateDiscountRequest) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateDiscountRequest) GetLayer() uint32 {
|
||||
if x != nil {
|
||||
return x.Layer
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *CreateDiscountRequest) GetDescription() string {
|
||||
if x != nil {
|
||||
return x.Description
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateDiscountRequest) GetCondition() *DiscountCondition {
|
||||
if x != nil {
|
||||
return x.Condition
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CreateDiscountRequest) GetTarget() *DiscountCalculationTarget {
|
||||
if x != nil {
|
||||
return x.Target
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_discount_service_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_discount_service_proto_rawDesc = []byte{
|
||||
0x0a, 0x16, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61,
|
||||
0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d,
|
||||
0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x28, 0x0a,
|
||||
0x16, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x22, 0xed, 0x01, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x6c,
|
||||
0x79, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x43, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x69, 0x73, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74,
|
||||
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12,
|
||||
0x2e, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x44, 0x61, 0x74, 0x65, 0x12,
|
||||
0x1b, 0x0a, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48,
|
||||
0x00, 0x52, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07,
|
||||
0x5f, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x15, 0x41, 0x70, 0x70, 0x6c, 0x79,
|
||||
0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||
0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65,
|
||||
0x64, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x52, 0x10, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x69, 0x73,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0xdb, 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x09,
|
||||
0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1b, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x43, 0x6f,
|
||||
0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65,
|
||||
0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x61, 0x6c, 0x63, 0x75,
|
||||
0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x06, 0x54, 0x61,
|
||||
0x72, 0x67, 0x65, 0x74, 0x32, 0x80, 0x07, 0x0a, 0x0f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x41,
|
||||
0x6c, 0x6c, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44,
|
||||
0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c,
|
||||
0x12, 0x0a, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x66, 0x0a, 0x10,
|
||||
0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
|
||||
0x12, 0x20, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x44,
|
||||
0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x13, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69,
|
||||
0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12,
|
||||
0x13, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f,
|
||||
0x7b, 0x49, 0x44, 0x7d, 0x12, 0x69, 0x0a, 0x12, 0x44, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e,
|
||||
0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x64, 0x69, 0x73,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x44, 0x69, 0x73, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x64, 0x69, 0x73,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22,
|
||||
0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x64, 0x69, 0x73,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x65, 0x12,
|
||||
0x6d, 0x0a, 0x0e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 0x12, 0x1e, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x70, 0x70,
|
||||
0x6c, 0x79, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x1f, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x70, 0x70,
|
||||
0x6c, 0x79, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f,
|
||||
0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x5f,
|
||||
0x0a, 0x0f, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49,
|
||||
0x44, 0x12, 0x20, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74,
|
||||
0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44,
|
||||
0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12,
|
||||
0x0e, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x12,
|
||||
0x5b, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x12, 0x1f, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69,
|
||||
0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x3a, 0x01,
|
||||
0x2a, 0x22, 0x09, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x5c, 0x0a, 0x0f,
|
||||
0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12,
|
||||
0x1a, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x1a, 0x12, 0x2e, 0x64, 0x69,
|
||||
0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22,
|
||||
0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x1a, 0x0e, 0x2f, 0x64, 0x69, 0x73,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x12, 0x5b, 0x0a, 0x0e, 0x55, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x64,
|
||||
0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x1a, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x19, 0x82, 0xd3,
|
||||
0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x32, 0x0e, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x12, 0x5e, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74,
|
||||
0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x64, 0x69, 0x73, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x69,
|
||||
0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22,
|
||||
0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a, 0x0e, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x64, 0x69, 0x73,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_discount_service_proto_rawDescOnce sync.Once
|
||||
file_discount_service_proto_rawDescData = file_discount_service_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_discount_service_proto_rawDescGZIP() []byte {
|
||||
file_discount_service_proto_rawDescOnce.Do(func() {
|
||||
file_discount_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_discount_service_proto_rawDescData)
|
||||
})
|
||||
return file_discount_service_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_discount_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_discount_service_proto_goTypes = []interface{}{
|
||||
(*GetDiscountByIDRequest)(nil), // 0: discount.GetDiscountByIDRequest
|
||||
(*ApplyDiscountRequest)(nil), // 1: discount.ApplyDiscountRequest
|
||||
(*ApplyDiscountResponse)(nil), // 2: discount.ApplyDiscountResponse
|
||||
(*CreateDiscountRequest)(nil), // 3: discount.CreateDiscountRequest
|
||||
(*UserInformation)(nil), // 4: discount.UserInformation
|
||||
(*ProductInformation)(nil), // 5: discount.ProductInformation
|
||||
(*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp
|
||||
(*Discount)(nil), // 7: discount.Discount
|
||||
(*DiscountCondition)(nil), // 8: discount.DiscountCondition
|
||||
(*DiscountCalculationTarget)(nil), // 9: discount.DiscountCalculationTarget
|
||||
(*emptypb.Empty)(nil), // 10: google.protobuf.Empty
|
||||
(*DiscountOptional)(nil), // 11: discount.DiscountOptional
|
||||
(*Discounts)(nil), // 12: discount.Discounts
|
||||
}
|
||||
var file_discount_service_proto_depIdxs = []int32{
|
||||
4, // 0: discount.ApplyDiscountRequest.UserInformation:type_name -> discount.UserInformation
|
||||
5, // 1: discount.ApplyDiscountRequest.Products:type_name -> discount.ProductInformation
|
||||
6, // 2: discount.ApplyDiscountRequest.Date:type_name -> google.protobuf.Timestamp
|
||||
7, // 3: discount.ApplyDiscountResponse.AppliedDiscounts:type_name -> discount.Discount
|
||||
8, // 4: discount.CreateDiscountRequest.Condition:type_name -> discount.DiscountCondition
|
||||
9, // 5: discount.CreateDiscountRequest.Target:type_name -> discount.DiscountCalculationTarget
|
||||
10, // 6: discount.DiscountService.GetAllDiscounts:input_type -> google.protobuf.Empty
|
||||
0, // 7: discount.DiscountService.GetUserDiscounts:input_type -> discount.GetDiscountByIDRequest
|
||||
1, // 8: discount.DiscountService.DetermineDiscounts:input_type -> discount.ApplyDiscountRequest
|
||||
1, // 9: discount.DiscountService.ApplyDiscounts:input_type -> discount.ApplyDiscountRequest
|
||||
0, // 10: discount.DiscountService.GetDiscountByID:input_type -> discount.GetDiscountByIDRequest
|
||||
3, // 11: discount.DiscountService.CreateDiscount:input_type -> discount.CreateDiscountRequest
|
||||
11, // 12: discount.DiscountService.ReplaceDiscount:input_type -> discount.DiscountOptional
|
||||
11, // 13: discount.DiscountService.UpdateDiscount:input_type -> discount.DiscountOptional
|
||||
0, // 14: discount.DiscountService.DeleteDiscount:input_type -> discount.GetDiscountByIDRequest
|
||||
12, // 15: discount.DiscountService.GetAllDiscounts:output_type -> discount.Discounts
|
||||
12, // 16: discount.DiscountService.GetUserDiscounts:output_type -> discount.Discounts
|
||||
12, // 17: discount.DiscountService.DetermineDiscounts:output_type -> discount.Discounts
|
||||
2, // 18: discount.DiscountService.ApplyDiscounts:output_type -> discount.ApplyDiscountResponse
|
||||
7, // 19: discount.DiscountService.GetDiscountByID:output_type -> discount.Discount
|
||||
7, // 20: discount.DiscountService.CreateDiscount:output_type -> discount.Discount
|
||||
7, // 21: discount.DiscountService.ReplaceDiscount:output_type -> discount.Discount
|
||||
7, // 22: discount.DiscountService.UpdateDiscount:output_type -> discount.Discount
|
||||
7, // 23: discount.DiscountService.DeleteDiscount:output_type -> discount.Discount
|
||||
15, // [15:24] is the sub-list for method output_type
|
||||
6, // [6:15] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_discount_service_proto_init() }
|
||||
func file_discount_service_proto_init() {
|
||||
if File_discount_service_proto != nil {
|
||||
return
|
||||
}
|
||||
file_discount_discount_model_proto_init()
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_discount_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetDiscountByIDRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_discount_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ApplyDiscountRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_discount_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ApplyDiscountResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_discount_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CreateDiscountRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_discount_service_proto_msgTypes[1].OneofWrappers = []interface{}{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_discount_service_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_discount_service_proto_goTypes,
|
||||
DependencyIndexes: file_discount_service_proto_depIdxs,
|
||||
MessageInfos: file_discount_service_proto_msgTypes,
|
||||
}.Build()
|
||||
File_discount_service_proto = out.File
|
||||
file_discount_service_proto_rawDesc = nil
|
||||
file_discount_service_proto_goTypes = nil
|
||||
file_discount_service_proto_depIdxs = nil
|
||||
}
|
404
internal/proto/discount/service_grpc.pb.go
Normal file
404
internal/proto/discount/service_grpc.pb.go
Normal file
@ -0,0 +1,404 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc (unknown)
|
||||
// source: discount/service.proto
|
||||
|
||||
package discount
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
const (
|
||||
DiscountService_GetAllDiscounts_FullMethodName = "/discount.DiscountService/GetAllDiscounts"
|
||||
DiscountService_GetUserDiscounts_FullMethodName = "/discount.DiscountService/GetUserDiscounts"
|
||||
DiscountService_DetermineDiscounts_FullMethodName = "/discount.DiscountService/DetermineDiscounts"
|
||||
DiscountService_ApplyDiscounts_FullMethodName = "/discount.DiscountService/ApplyDiscounts"
|
||||
DiscountService_GetDiscountByID_FullMethodName = "/discount.DiscountService/GetDiscountByID"
|
||||
DiscountService_CreateDiscount_FullMethodName = "/discount.DiscountService/CreateDiscount"
|
||||
DiscountService_ReplaceDiscount_FullMethodName = "/discount.DiscountService/ReplaceDiscount"
|
||||
DiscountService_UpdateDiscount_FullMethodName = "/discount.DiscountService/UpdateDiscount"
|
||||
DiscountService_DeleteDiscount_FullMethodName = "/discount.DiscountService/DeleteDiscount"
|
||||
)
|
||||
|
||||
// DiscountServiceClient is the client API for DiscountService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type DiscountServiceClient interface {
|
||||
GetAllDiscounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Discounts, error)
|
||||
GetUserDiscounts(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discounts, error)
|
||||
DetermineDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*Discounts, error)
|
||||
ApplyDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*ApplyDiscountResponse, error)
|
||||
GetDiscountByID(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error)
|
||||
CreateDiscount(ctx context.Context, in *CreateDiscountRequest, opts ...grpc.CallOption) (*Discount, error)
|
||||
ReplaceDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error)
|
||||
UpdateDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error)
|
||||
DeleteDiscount(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error)
|
||||
}
|
||||
|
||||
type discountServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewDiscountServiceClient(cc grpc.ClientConnInterface) DiscountServiceClient {
|
||||
return &discountServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *discountServiceClient) GetAllDiscounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Discounts, error) {
|
||||
out := new(Discounts)
|
||||
err := c.cc.Invoke(ctx, DiscountService_GetAllDiscounts_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *discountServiceClient) GetUserDiscounts(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discounts, error) {
|
||||
out := new(Discounts)
|
||||
err := c.cc.Invoke(ctx, DiscountService_GetUserDiscounts_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *discountServiceClient) DetermineDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*Discounts, error) {
|
||||
out := new(Discounts)
|
||||
err := c.cc.Invoke(ctx, DiscountService_DetermineDiscounts_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *discountServiceClient) ApplyDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*ApplyDiscountResponse, error) {
|
||||
out := new(ApplyDiscountResponse)
|
||||
err := c.cc.Invoke(ctx, DiscountService_ApplyDiscounts_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *discountServiceClient) GetDiscountByID(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) {
|
||||
out := new(Discount)
|
||||
err := c.cc.Invoke(ctx, DiscountService_GetDiscountByID_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *discountServiceClient) CreateDiscount(ctx context.Context, in *CreateDiscountRequest, opts ...grpc.CallOption) (*Discount, error) {
|
||||
out := new(Discount)
|
||||
err := c.cc.Invoke(ctx, DiscountService_CreateDiscount_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *discountServiceClient) ReplaceDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) {
|
||||
out := new(Discount)
|
||||
err := c.cc.Invoke(ctx, DiscountService_ReplaceDiscount_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *discountServiceClient) UpdateDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) {
|
||||
out := new(Discount)
|
||||
err := c.cc.Invoke(ctx, DiscountService_UpdateDiscount_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *discountServiceClient) DeleteDiscount(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) {
|
||||
out := new(Discount)
|
||||
err := c.cc.Invoke(ctx, DiscountService_DeleteDiscount_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DiscountServiceServer is the server API for DiscountService service.
|
||||
// All implementations should embed UnimplementedDiscountServiceServer
|
||||
// for forward compatibility
|
||||
type DiscountServiceServer interface {
|
||||
GetAllDiscounts(context.Context, *emptypb.Empty) (*Discounts, error)
|
||||
GetUserDiscounts(context.Context, *GetDiscountByIDRequest) (*Discounts, error)
|
||||
DetermineDiscounts(context.Context, *ApplyDiscountRequest) (*Discounts, error)
|
||||
ApplyDiscounts(context.Context, *ApplyDiscountRequest) (*ApplyDiscountResponse, error)
|
||||
GetDiscountByID(context.Context, *GetDiscountByIDRequest) (*Discount, error)
|
||||
CreateDiscount(context.Context, *CreateDiscountRequest) (*Discount, error)
|
||||
ReplaceDiscount(context.Context, *DiscountOptional) (*Discount, error)
|
||||
UpdateDiscount(context.Context, *DiscountOptional) (*Discount, error)
|
||||
DeleteDiscount(context.Context, *GetDiscountByIDRequest) (*Discount, error)
|
||||
}
|
||||
|
||||
// UnimplementedDiscountServiceServer should be embedded to have forward compatible implementations.
|
||||
type UnimplementedDiscountServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedDiscountServiceServer) GetAllDiscounts(context.Context, *emptypb.Empty) (*Discounts, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAllDiscounts not implemented")
|
||||
}
|
||||
func (UnimplementedDiscountServiceServer) GetUserDiscounts(context.Context, *GetDiscountByIDRequest) (*Discounts, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetUserDiscounts not implemented")
|
||||
}
|
||||
func (UnimplementedDiscountServiceServer) DetermineDiscounts(context.Context, *ApplyDiscountRequest) (*Discounts, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DetermineDiscounts not implemented")
|
||||
}
|
||||
func (UnimplementedDiscountServiceServer) ApplyDiscounts(context.Context, *ApplyDiscountRequest) (*ApplyDiscountResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ApplyDiscounts not implemented")
|
||||
}
|
||||
func (UnimplementedDiscountServiceServer) GetDiscountByID(context.Context, *GetDiscountByIDRequest) (*Discount, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetDiscountByID not implemented")
|
||||
}
|
||||
func (UnimplementedDiscountServiceServer) CreateDiscount(context.Context, *CreateDiscountRequest) (*Discount, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateDiscount not implemented")
|
||||
}
|
||||
func (UnimplementedDiscountServiceServer) ReplaceDiscount(context.Context, *DiscountOptional) (*Discount, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ReplaceDiscount not implemented")
|
||||
}
|
||||
func (UnimplementedDiscountServiceServer) UpdateDiscount(context.Context, *DiscountOptional) (*Discount, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateDiscount not implemented")
|
||||
}
|
||||
func (UnimplementedDiscountServiceServer) DeleteDiscount(context.Context, *GetDiscountByIDRequest) (*Discount, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteDiscount not implemented")
|
||||
}
|
||||
|
||||
// UnsafeDiscountServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to DiscountServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeDiscountServiceServer interface {
|
||||
mustEmbedUnimplementedDiscountServiceServer()
|
||||
}
|
||||
|
||||
func RegisterDiscountServiceServer(s grpc.ServiceRegistrar, srv DiscountServiceServer) {
|
||||
s.RegisterService(&DiscountService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _DiscountService_GetAllDiscounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiscountServiceServer).GetAllDiscounts(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DiscountService_GetAllDiscounts_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiscountServiceServer).GetAllDiscounts(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DiscountService_GetUserDiscounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetDiscountByIDRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiscountServiceServer).GetUserDiscounts(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DiscountService_GetUserDiscounts_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiscountServiceServer).GetUserDiscounts(ctx, req.(*GetDiscountByIDRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DiscountService_DetermineDiscounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ApplyDiscountRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiscountServiceServer).DetermineDiscounts(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DiscountService_DetermineDiscounts_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiscountServiceServer).DetermineDiscounts(ctx, req.(*ApplyDiscountRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DiscountService_ApplyDiscounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ApplyDiscountRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiscountServiceServer).ApplyDiscounts(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DiscountService_ApplyDiscounts_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiscountServiceServer).ApplyDiscounts(ctx, req.(*ApplyDiscountRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DiscountService_GetDiscountByID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetDiscountByIDRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiscountServiceServer).GetDiscountByID(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DiscountService_GetDiscountByID_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiscountServiceServer).GetDiscountByID(ctx, req.(*GetDiscountByIDRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DiscountService_CreateDiscount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CreateDiscountRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiscountServiceServer).CreateDiscount(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DiscountService_CreateDiscount_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiscountServiceServer).CreateDiscount(ctx, req.(*CreateDiscountRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DiscountService_ReplaceDiscount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DiscountOptional)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiscountServiceServer).ReplaceDiscount(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DiscountService_ReplaceDiscount_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiscountServiceServer).ReplaceDiscount(ctx, req.(*DiscountOptional))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DiscountService_UpdateDiscount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DiscountOptional)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiscountServiceServer).UpdateDiscount(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DiscountService_UpdateDiscount_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiscountServiceServer).UpdateDiscount(ctx, req.(*DiscountOptional))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DiscountService_DeleteDiscount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetDiscountByIDRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiscountServiceServer).DeleteDiscount(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DiscountService_DeleteDiscount_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiscountServiceServer).DeleteDiscount(ctx, req.(*GetDiscountByIDRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// DiscountService_ServiceDesc is the grpc.ServiceDesc for DiscountService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var DiscountService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "discount.DiscountService",
|
||||
HandlerType: (*DiscountServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetAllDiscounts",
|
||||
Handler: _DiscountService_GetAllDiscounts_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetUserDiscounts",
|
||||
Handler: _DiscountService_GetUserDiscounts_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DetermineDiscounts",
|
||||
Handler: _DiscountService_DetermineDiscounts_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ApplyDiscounts",
|
||||
Handler: _DiscountService_ApplyDiscounts_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetDiscountByID",
|
||||
Handler: _DiscountService_GetDiscountByID_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CreateDiscount",
|
||||
Handler: _DiscountService_CreateDiscount_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ReplaceDiscount",
|
||||
Handler: _DiscountService_ReplaceDiscount_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateDiscount",
|
||||
Handler: _DiscountService_UpdateDiscount_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteDiscount",
|
||||
Handler: _DiscountService_DeleteDiscount_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "discount/service.proto",
|
||||
}
|
@ -46,6 +46,17 @@ func (r *CodewordRepository) StoreRecoveryRecord(ctx context.Context, deps model
|
||||
|
||||
// добавляем в очередь данные для отправки на почту в редис
|
||||
func (r *CodewordRepository) InsertToQueue(ctx context.Context, deps models.RecEmailDeps) error {
|
||||
sendLockKey := "email:sendLock:" + deps.Email
|
||||
ttl := 5 * time.Minute
|
||||
|
||||
lockSuccess, err := r.rdb.SetNX(ctx, sendLockKey, "1", ttl).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !lockSuccess {
|
||||
return ErrAlreadyReported
|
||||
}
|
||||
|
||||
task := models.RecoveryRecord{
|
||||
ID: deps.ID,
|
||||
UserID: deps.UserID,
|
||||
@ -58,11 +69,7 @@ func (r *CodewordRepository) InsertToQueue(ctx context.Context, deps models.RecE
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.rdb.LPush(ctx, "recoveryQueue", taskBytes).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return r.rdb.Set(ctx, "email:task:"+deps.Email, taskBytes, ttl).Err()
|
||||
}
|
||||
|
||||
// получаем данные юзера по подписи
|
||||
@ -81,8 +88,13 @@ func (r *CodewordRepository) GetRecoveryRecord(ctx context.Context, key string)
|
||||
|
||||
// пингует в монгу чтобы проверить подключение
|
||||
func (r *CodewordRepository) Ping(ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
if err := r.mdb.Database().Client().Ping(ctx, readpref.Primary()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.rdb.Ping(ctx).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
13
internal/repository/errors.go
Normal file
13
internal/repository/errors.go
Normal file
@ -0,0 +1,13 @@
|
||||
package repository
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrPromoUserNotFound = errors.New("user not found")
|
||||
ErrAlreadyReported = errors.New("already reported")
|
||||
ErrDuplicateCodeword = errors.New("duplicate codeword")
|
||||
ErrPromoCodeNotFound = errors.New("promo code not found")
|
||||
ErrPromoCodeExpired = errors.New("promo code is expired")
|
||||
ErrPromoCodeExhausted = errors.New("promo code is exhausted")
|
||||
ErrPromoCodeAlreadyActivated = errors.New("promo code is already activated")
|
||||
)
|
@ -3,7 +3,6 @@ package repository
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"context"
|
||||
"errors"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
@ -11,11 +10,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDuplicateCodeword = errors.New("duplicate codeword")
|
||||
ErrPromoCodeNotFound = errors.New("promo code not found")
|
||||
)
|
||||
|
||||
// структура для горутины чтобы ошибки не пропускать
|
||||
type countResult struct {
|
||||
count int64
|
||||
@ -27,7 +21,10 @@ type PromoCodeRepository struct {
|
||||
}
|
||||
|
||||
func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository {
|
||||
// todo заменить паники вроде как в роде не круто их юзать
|
||||
return &PromoCodeRepository{mdb: mdb}
|
||||
}
|
||||
|
||||
func InitPromoCodeIndexes(ctx context.Context, mdb *mongo.Collection) error {
|
||||
uniqueIndexModel := mongo.IndexModel{
|
||||
Keys: bson.D{
|
||||
{Key: "codeword", Value: 1},
|
||||
@ -35,9 +32,9 @@ func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository {
|
||||
},
|
||||
Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"delete": false}),
|
||||
}
|
||||
_, err := mdb.Indexes().CreateOne(context.Background(), uniqueIndexModel)
|
||||
_, err := mdb.Indexes().CreateOne(ctx, uniqueIndexModel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
textIndexModel := mongo.IndexModel{
|
||||
@ -48,17 +45,20 @@ func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository {
|
||||
},
|
||||
Options: options.Index().SetName("TextIndex"),
|
||||
}
|
||||
_, err = mdb.Indexes().CreateOne(context.Background(), textIndexModel)
|
||||
_, err = mdb.Indexes().CreateOne(ctx, textIndexModel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return &PromoCodeRepository{mdb: mdb}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) {
|
||||
req.CreatedAt = time.Now()
|
||||
req.ID = primitive.NewObjectID()
|
||||
if req.FastLinks == nil {
|
||||
req.FastLinks = []string{}
|
||||
}
|
||||
|
||||
_, err := r.mdb.InsertOne(ctx, req)
|
||||
if err != nil {
|
||||
@ -214,48 +214,51 @@ func (r *PromoCodeRepository) GetPromoCodesList(ctx context.Context, req *models
|
||||
return promoCodes, count, nil
|
||||
}
|
||||
|
||||
func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) {
|
||||
session, err := r.mdb.Database().Client().StartSession()
|
||||
func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.ActivateReq) (*models.PromoCode, error) {
|
||||
var promoCode models.PromoCode
|
||||
|
||||
var filter bson.M
|
||||
if req.Codeword != "" {
|
||||
filter = bson.M{
|
||||
"codeword": req.Codeword,
|
||||
}
|
||||
} else if req.FastLink != "" {
|
||||
filter = bson.M{
|
||||
"fastLinks": req.FastLink,
|
||||
}
|
||||
}
|
||||
|
||||
opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
|
||||
|
||||
err := r.mdb.FindOneAndUpdate(ctx, filter, bson.M{"$inc": bson.M{"activationCount": -1}}, opts).Decode(&promoCode)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, ErrPromoCodeNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer session.EndSession(ctx)
|
||||
|
||||
var greetings string
|
||||
|
||||
transactionErr := mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error {
|
||||
filter := bson.M{
|
||||
"codeword": req.Codeword,
|
||||
"delete": false,
|
||||
"outdated": false,
|
||||
"offLimit": false,
|
||||
"activationCount": bson.M{"$gt": 0},
|
||||
"dueTo": bson.M{"$gt": time.Now().Unix()},
|
||||
}
|
||||
update := bson.M{
|
||||
"$inc": bson.M{"activationCount": -1},
|
||||
}
|
||||
opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
|
||||
|
||||
var updatedPromoCode models.PromoCode
|
||||
err := r.mdb.FindOneAndUpdate(sc, filter, update, opts).Decode(&updatedPromoCode)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return ErrPromoCodeNotFound
|
||||
if promoCode.ActivationCount <= 0 && promoCode.DueTo > time.Now().Unix() {
|
||||
if !promoCode.OffLimit {
|
||||
update := bson.M{"$set": bson.M{"offLimit": true}}
|
||||
_, err := r.mdb.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return &promoCode, nil
|
||||
}
|
||||
|
||||
greetings = updatedPromoCode.Greetings
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if transactionErr != nil {
|
||||
return "", transactionErr
|
||||
func (r *PromoCodeRepository) IncreaseActivationCount(ctx context.Context, promoCodeID primitive.ObjectID) error {
|
||||
filter := bson.M{"_id": promoCodeID}
|
||||
update := bson.M{"$inc": bson.M{"activationCount": 1}}
|
||||
_, err := r.mdb.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return greetings, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PromoCodeRepository) DeletePromoCode(ctx context.Context, promoCodeID string) error {
|
||||
@ -279,3 +282,19 @@ func (r *PromoCodeRepository) DeletePromoCode(ctx context.Context, promoCodeID s
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PromoCodeRepository) AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error {
|
||||
filter := bson.M{"_id": promoCodeID, "delete": false}
|
||||
update := bson.M{"$push": bson.M{"fastLinks": xid}}
|
||||
|
||||
result, err := r.mdb.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return ErrPromoCodeNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
72
internal/repository/promocode_stats.go
Normal file
72
internal/repository/promocode_stats.go
Normal file
@ -0,0 +1,72 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"context"
|
||||
"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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StatsRepository struct {
|
||||
mdb *mongo.Collection
|
||||
}
|
||||
|
||||
func NewStatsRepository(deps Deps) *StatsRepository {
|
||||
|
||||
return &StatsRepository{mdb: deps.Mdb}
|
||||
}
|
||||
|
||||
func (r *StatsRepository) UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error {
|
||||
filter := bson.M{"_id": promoCode.ID, "usageMap." + userID: bson.M{"$exists": true}}
|
||||
count, err := r.mdb.CountDocuments(ctx, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count >= 1 {
|
||||
return ErrPromoCodeAlreadyActivated
|
||||
}
|
||||
|
||||
var key string
|
||||
if req.FastLink != "" {
|
||||
key = req.FastLink
|
||||
} else {
|
||||
key = req.Codeword
|
||||
}
|
||||
|
||||
usage := models.Usage{
|
||||
Key: key,
|
||||
Time: time.Now(),
|
||||
}
|
||||
|
||||
update := bson.M{
|
||||
"$inc": bson.M{"usageCount": 1},
|
||||
"$push": bson.M{
|
||||
"usageMap." + userID: usage,
|
||||
},
|
||||
}
|
||||
|
||||
opts := options.Update().SetUpsert(true)
|
||||
_, err = r.mdb.UpdateOne(ctx, bson.M{"_id": promoCode.ID}, update, opts)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *StatsRepository) GetStatistics(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) {
|
||||
objID, err := primitive.ObjectIDFromHex(promoCodeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := bson.M{"_id": objID}
|
||||
|
||||
var promoCodeStats models.PromoCodeStats
|
||||
err = r.mdb.FindOne(ctx, filter).Decode(&promoCodeStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &promoCodeStats, nil
|
||||
}
|
@ -26,12 +26,12 @@ func NewUserRepository(deps Deps) *UserRepository {
|
||||
func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) {
|
||||
var user models.User
|
||||
|
||||
err := r.mdb.FindOne(ctx, bson.M{"email": email}).Decode(&user)
|
||||
err := r.mdb.FindOne(ctx, bson.M{"login": email}).Decode(&user)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
return nil, ErrPromoUserNotFound
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
@ -1,36 +1,30 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"codeword/internal/controller/promocode"
|
||||
"codeword/internal/controller/recovery"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
Logger *zap.Logger
|
||||
RecoveryController *recovery.RecoveryController
|
||||
PromoCodeController *promocode.PromoCodeController
|
||||
Logger *zap.Logger
|
||||
Controllers []Controller
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Logger *zap.Logger
|
||||
RecoveryController *recovery.RecoveryController
|
||||
PromoCodeController *promocode.PromoCodeController
|
||||
app *fiber.App
|
||||
Logger *zap.Logger
|
||||
Controllers []Controller
|
||||
app *fiber.App
|
||||
}
|
||||
|
||||
func NewServer(config ServerConfig) *Server {
|
||||
app := fiber.New()
|
||||
|
||||
s := &Server{
|
||||
Logger: config.Logger,
|
||||
RecoveryController: config.RecoveryController,
|
||||
PromoCodeController: config.PromoCodeController,
|
||||
app: app,
|
||||
Logger: config.Logger,
|
||||
Controllers: config.Controllers,
|
||||
app: app,
|
||||
}
|
||||
|
||||
s.registerRoutes()
|
||||
@ -51,34 +45,22 @@ func (s *Server) Shutdown(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (s *Server) registerRoutes() {
|
||||
s.app.Get("/liveness", s.handleLiveness)
|
||||
s.app.Get("/readiness", s.handleReadiness)
|
||||
|
||||
s.app.Post("/recover", s.RecoveryController.HandleRecoveryRequest)
|
||||
s.app.Get("/recover/:sign", s.RecoveryController.HandleRecoveryLink)
|
||||
|
||||
s.app.Post("/promocode/create", s.PromoCodeController.CreatePromoCode)
|
||||
s.app.Put("/promocode/edit", s.PromoCodeController.EditPromoCode)
|
||||
s.app.Post("/promocode/getList", s.PromoCodeController.GetList)
|
||||
s.app.Post("/promocode/activate", s.PromoCodeController.Activate)
|
||||
s.app.Delete("/promocode/:promocodeID", s.PromoCodeController.Delete)
|
||||
//... other
|
||||
}
|
||||
|
||||
func (s *Server) handleLiveness(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func (s *Server) handleReadiness(c *fiber.Ctx) error {
|
||||
startTime := time.Now()
|
||||
if err := s.RecoveryController.HandlePingDB(c); err != nil {
|
||||
s.Logger.Error("Failed to ping the database", zap.Error(err))
|
||||
return c.Status(fiber.StatusServiceUnavailable).SendString("DB ping failed")
|
||||
for _, c := range s.Controllers {
|
||||
router := s.app.Group(c.Name())
|
||||
c.Register(router)
|
||||
}
|
||||
}
|
||||
|
||||
type Controller interface {
|
||||
Register(router fiber.Router)
|
||||
Name() string
|
||||
}
|
||||
|
||||
func (s *Server) ListRoutes() {
|
||||
fmt.Println("Registered routes:")
|
||||
for _, stack := range s.app.Stack() {
|
||||
for _, route := range stack {
|
||||
fmt.Printf("%s %s\n", route.Method, route.Path)
|
||||
}
|
||||
}
|
||||
duration := time.Since(startTime)
|
||||
|
||||
durationMillis := duration.Milliseconds()
|
||||
responseMessage := fmt.Sprintf("DB ping success - Time taken: %d ms", durationMillis)
|
||||
|
||||
return c.Status(fiber.StatusOK).SendString(responseMessage)
|
||||
}
|
||||
|
@ -1,33 +1,58 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"codeword/internal/kafka/tariff"
|
||||
"codeword/internal/models"
|
||||
"codeword/internal/proto/discount"
|
||||
"codeword/internal/repository"
|
||||
"codeword/internal/utils/genID"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PromoCodeRepository interface {
|
||||
CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error)
|
||||
EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error)
|
||||
GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error)
|
||||
ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error)
|
||||
ActivatePromo(ctx context.Context, req *models.ActivateReq) (*models.PromoCode, error)
|
||||
DeletePromoCode(ctx context.Context, promoCodeID string) error
|
||||
GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error)
|
||||
AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error
|
||||
IncreaseActivationCount(ctx context.Context, promoCodeID primitive.ObjectID) error
|
||||
}
|
||||
|
||||
type PromoStatsRepository interface {
|
||||
UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error
|
||||
GetStatistics(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error)
|
||||
}
|
||||
|
||||
type PromoDeps struct {
|
||||
Logger *zap.Logger
|
||||
PromoCodeRepo PromoCodeRepository
|
||||
Logger *zap.Logger
|
||||
PromoCodeRepo PromoCodeRepository
|
||||
StatsRepo PromoStatsRepository
|
||||
Kafka *tariff.Producer
|
||||
DiscountClient discount.DiscountServiceClient
|
||||
}
|
||||
|
||||
type PromoCodeService struct {
|
||||
logger *zap.Logger
|
||||
promoCodeRepo PromoCodeRepository
|
||||
logger *zap.Logger
|
||||
promoCodeRepo PromoCodeRepository
|
||||
statsRepo PromoStatsRepository
|
||||
kafka *tariff.Producer
|
||||
discountClient discount.DiscountServiceClient
|
||||
}
|
||||
|
||||
func NewPromoCodeService(deps PromoDeps) *PromoCodeService {
|
||||
return &PromoCodeService{
|
||||
logger: deps.Logger,
|
||||
promoCodeRepo: deps.PromoCodeRepo,
|
||||
logger: deps.Logger,
|
||||
promoCodeRepo: deps.PromoCodeRepo,
|
||||
statsRepo: deps.StatsRepo,
|
||||
kafka: deps.Kafka,
|
||||
discountClient: deps.DiscountClient,
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,14 +86,92 @@ func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.Ge
|
||||
return promoCodes, count, nil
|
||||
}
|
||||
|
||||
func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) {
|
||||
greetings, err := s.promoCodeRepo.ActivatePromo(ctx, req)
|
||||
// todo одумать еще реализацию этого дела, надо уточнить как разделяется ответственность в бонусе между привилегией и скидкой
|
||||
// разделяется ли она или они всегда вместе, если разделяются то что-то из этого может быть пустым либо все заполеннное,
|
||||
// соответсвенно надо сделать соответствующие проверки до записи в кафку и до отправки в дискаунт сервис
|
||||
|
||||
func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq, userID string) (string, error) {
|
||||
promoCode, err := s.promoCodeRepo.ActivatePromo(ctx, req)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to activate promocode", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
//todo такая реализация проверок кажется довольно массивной, думаю как то это стоит сделать параллельно обхаживая все условия
|
||||
if promoCode.DueTo < time.Now().Unix() && promoCode.DueTo > 0 {
|
||||
err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", fmt.Errorf("%w: expired on %s", repository.ErrPromoCodeExpired, time.Unix(promoCode.DueTo, 0).Format(time.RFC3339))
|
||||
}
|
||||
|
||||
return greetings, nil
|
||||
if promoCode.DueTo == 0 && promoCode.ActivationCount < 0 {
|
||||
err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", repository.ErrPromoCodeExhausted
|
||||
}
|
||||
|
||||
err = s.statsRepo.UpdateStatistics(ctx, req, promoCode, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrPromoCodeAlreadyActivated) {
|
||||
err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", repository.ErrPromoCodeAlreadyActivated
|
||||
}
|
||||
s.logger.Error("Failed add in stats", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
var postfix string
|
||||
|
||||
if req.FastLink != "" {
|
||||
postfix = fmt.Sprintf(":(%s)", req.FastLink)
|
||||
}
|
||||
|
||||
var privileges []models.Privilege
|
||||
privilege := models.Privilege{
|
||||
PrivilegeID: promoCode.Bonus.Privilege.PrivilegeID,
|
||||
Amount: promoCode.Bonus.Privilege.Amount,
|
||||
}
|
||||
privileges = append(privileges, privilege)
|
||||
|
||||
fakeTariff := &models.Tariff{
|
||||
Name: promoCode.Codeword + postfix,
|
||||
Privileges: privileges,
|
||||
Deleted: promoCode.Delete,
|
||||
CreatedAt: promoCode.CreatedAt,
|
||||
}
|
||||
if err := s.kafka.Send(ctx, userID, fakeTariff); err != nil {
|
||||
s.logger.Error("Failed to send fake tariff to Kafka", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
disOverHelm := true
|
||||
discountRequest := &discount.CreateDiscountRequest{
|
||||
Name: promoCode.Codeword + postfix,
|
||||
Layer: promoCode.Bonus.Discount.Layer,
|
||||
Description: "",
|
||||
Condition: &discount.DiscountCondition{
|
||||
Coupon: &promoCode.Codeword,
|
||||
User: &userID,
|
||||
},
|
||||
Target: &discount.DiscountCalculationTarget{
|
||||
Factor: promoCode.Bonus.Discount.Factor,
|
||||
Overhelm: &disOverHelm,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = s.discountClient.CreateDiscount(ctx, discountRequest)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create discount", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
return promoCode.Greetings, nil
|
||||
}
|
||||
|
||||
func (s *PromoCodeService) DeletePromoCode(ctx context.Context, promoCodeID string) error {
|
||||
@ -80,3 +183,29 @@ func (s *PromoCodeService) DeletePromoCode(ctx context.Context, promoCodeID stri
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PromoCodeService) CreateFastLink(ctx context.Context, promoCodeID string) (string, error) {
|
||||
xid := genID.GenerateXID()
|
||||
promoID, err := primitive.ObjectIDFromHex(promoCodeID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed conversion promoCodeID to ObjectID", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = s.promoCodeRepo.AddFastLink(ctx, promoID, xid)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to add fastlink for promocode by promocode id", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
return xid, nil
|
||||
}
|
||||
|
||||
func (s *PromoCodeService) GetStats(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) {
|
||||
promoStats, err := s.statsRepo.GetStatistics(ctx, promoCodeID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed getting promo stats", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return promoStats, nil
|
||||
}
|
||||
|
@ -73,10 +73,6 @@ func (s *RecoveryService) FindUserByEmail(ctx context.Context, email string) (*m
|
||||
s.logger.Error("Failed to find user by email", zap.String("email", email), zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
s.logger.Info("No user found with email", zap.String("email", email))
|
||||
return nil, nil
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
|
8
internal/utils/genID/gen_id.go
Normal file
8
internal/utils/genID/gen_id.go
Normal file
@ -0,0 +1,8 @@
|
||||
package genID
|
||||
|
||||
import "github.com/rs/xid"
|
||||
|
||||
func GenerateXID() string {
|
||||
id := xid.New()
|
||||
return id.String()
|
||||
}
|
31
internal/utils/transfer/privilege.go
Normal file
31
internal/utils/transfer/privilege.go
Normal file
@ -0,0 +1,31 @@
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"codeword/internal/proto/broker"
|
||||
)
|
||||
|
||||
func PrivilegeModelToProto(privilege *models.Privilege) *broker.PrivilegeMessage {
|
||||
if privilege == nil {
|
||||
return &broker.PrivilegeMessage{}
|
||||
}
|
||||
|
||||
return &broker.PrivilegeMessage{
|
||||
PrivilegeID: privilege.PrivilegeID,
|
||||
ServiceKey: privilege.ServiceKey,
|
||||
Type: models.PrivilegeBrokerTypeMap[privilege.Type],
|
||||
Value: privilege.Value,
|
||||
Amount: privilege.Amount,
|
||||
}
|
||||
}
|
||||
|
||||
func PrivilegeArrayModelToProto(privileges []models.Privilege) []*broker.PrivilegeMessage {
|
||||
privilegesProto := make([]*broker.PrivilegeMessage, len(privileges))
|
||||
|
||||
for index, privilege := range privileges {
|
||||
privilegeCopy := privilege
|
||||
privilegesProto[index] = PrivilegeModelToProto(&privilegeCopy)
|
||||
}
|
||||
|
||||
return privilegesProto
|
||||
}
|
17
internal/utils/transfer/tariff.go
Normal file
17
internal/utils/transfer/tariff.go
Normal file
@ -0,0 +1,17 @@
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"codeword/internal/proto/broker"
|
||||
)
|
||||
|
||||
func TariffModelToProtoMessage(userID string, tariffModel *models.Tariff) *broker.TariffMessage {
|
||||
if tariffModel == nil {
|
||||
return &broker.TariffMessage{}
|
||||
}
|
||||
|
||||
return &broker.TariffMessage{
|
||||
UserID: userID,
|
||||
Privileges: PrivilegeArrayModelToProto(tariffModel.Privileges),
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ type PurgeWorker struct {
|
||||
mongo *mongo.Collection
|
||||
}
|
||||
|
||||
func NewRecoveryWC(deps Deps) *PurgeWorker {
|
||||
func NewPurgeWC(deps Deps) *PurgeWorker {
|
||||
return &PurgeWorker{
|
||||
logger: deps.Logger,
|
||||
mongo: deps.Mongo,
|
||||
@ -54,3 +54,7 @@ func (wc *PurgeWorker) processTasks(ctx context.Context) {
|
||||
wc.logger.Info("Deleted documents", zap.Int64("count", result.DeletedCount))
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *PurgeWorker) Stop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -37,14 +37,13 @@ func NewRecoveryWC(deps Deps) *RecoveryWorker {
|
||||
}
|
||||
|
||||
func (wc *RecoveryWorker) Start(ctx context.Context) {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
wc.processTasks(ctx)
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
@ -52,29 +51,39 @@ func (wc *RecoveryWorker) Start(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (wc *RecoveryWorker) processTasks(ctx context.Context) {
|
||||
result, err := wc.redis.BRPop(ctx, 1*time.Second, "recoveryQueue").Result()
|
||||
if err != nil {
|
||||
if err != redis.Nil {
|
||||
wc.logger.Error("Failed to BRPop from the recovery queue", zap.Error(err))
|
||||
var cursor uint64
|
||||
for {
|
||||
var keys []string
|
||||
var err error
|
||||
keys, cursor, err = wc.redis.Scan(ctx, cursor, "email:task:*", 0).Result()
|
||||
if err != nil {
|
||||
wc.logger.Error("Failed to scan for email tasks", zap.Error(err))
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(result) < 2 {
|
||||
wc.logger.Error("Received unexpected number of elements from BRPop", zap.Strings("result", result))
|
||||
return
|
||||
}
|
||||
for _, key := range keys {
|
||||
taskBytes, err := wc.redis.GetDel(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
continue
|
||||
} else if err != nil {
|
||||
wc.logger.Error("Failed to getdel recovery task", zap.String("key", key), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
var task models.RecoveryRecord
|
||||
if err = json.Unmarshal([]byte(result[1]), &task); err != nil {
|
||||
wc.logger.Error("Failed to unmarshal recovery task", zap.String("key", result[0]), zap.Error(err))
|
||||
return
|
||||
}
|
||||
var task models.RecoveryRecord
|
||||
if json.Unmarshal([]byte(taskBytes), &task) != nil {
|
||||
wc.logger.Error("Failed to unmarshal recovery task", zap.String("key", key), zap.String("task", taskBytes))
|
||||
continue
|
||||
}
|
||||
|
||||
err = wc.sendRecoveryTask(ctx, task)
|
||||
if err != nil {
|
||||
wc.logger.Error("Failed to send recovery task", zap.String("key", result[0]), zap.Error(err))
|
||||
return
|
||||
err = wc.sendRecoveryTask(ctx, task)
|
||||
if err != nil {
|
||||
wc.logger.Error("Failed to send recovery task", zap.String("key", key), zap.Error(err))
|
||||
}
|
||||
}
|
||||
if cursor == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,3 +123,7 @@ func (wc *RecoveryWorker) sendRecoveryTask(ctx context.Context, task models.Reco
|
||||
//wc.logger.Info("Recovery email sent and restore request updated successfully", zap.String("email", task.Email))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wc *RecoveryWorker) Stop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
37
pkg/closer/closer.go
Normal file
37
pkg/closer/closer.go
Normal file
@ -0,0 +1,37 @@
|
||||
package closer
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Closer interface {
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
type CloserFunc func(ctx context.Context) error
|
||||
|
||||
func (cf CloserFunc) Close(ctx context.Context) error {
|
||||
return cf(ctx)
|
||||
}
|
||||
|
||||
type CloserGroup struct {
|
||||
closers []Closer
|
||||
}
|
||||
|
||||
func NewCloserGroup() *CloserGroup {
|
||||
return &CloserGroup{}
|
||||
}
|
||||
|
||||
func (cg *CloserGroup) Add(c Closer) {
|
||||
cg.closers = append(cg.closers, c)
|
||||
}
|
||||
|
||||
func (cg *CloserGroup) Call(ctx context.Context) error {
|
||||
var closeErr error
|
||||
for i := len(cg.closers) - 1; i >= 0; i-- {
|
||||
if err := cg.closers[i].Close(ctx); err != nil && closeErr == nil {
|
||||
closeErr = err
|
||||
}
|
||||
}
|
||||
return closeErr
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"`
|
||||
MongoPort string `env:"MONGO_PORT" envDefault:"27020"`
|
||||
MongoUser string `env:"MONGO_USER" envDefault:"test"`
|
||||
MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"`
|
||||
MongoDatabase string `env:"MONGO_DB" envDefault:"admin"`
|
||||
MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"`
|
||||
}
|
||||
|
||||
type RequestSettings struct {
|
||||
Driver *mongo.Collection
|
||||
Options *options.FindOptions
|
||||
Filter primitive.M
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type ConnectDeps struct {
|
||||
Configuration *Configuration
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func Connect(ctx context.Context, deps *ConnectDeps) (*mongo.Database, error) {
|
||||
if deps == nil {
|
||||
return nil, ErrEmptyArgs
|
||||
}
|
||||
|
||||
mongoURI := &url.URL{
|
||||
Scheme: "mongodb",
|
||||
Host: net.JoinHostPort(deps.Configuration.MongoHost, deps.Configuration.MongoPort),
|
||||
}
|
||||
|
||||
connectionOptions := options.Client().
|
||||
ApplyURI(mongoURI.String()).
|
||||
SetAuth(options.Credential{
|
||||
AuthMechanism: "SCRAM-SHA-256",
|
||||
AuthSource: deps.Configuration.MongoAuth,
|
||||
Username: deps.Configuration.MongoUser,
|
||||
Password: deps.Configuration.MongoPassword,
|
||||
})
|
||||
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
timeoutExceeded := time.After(deps.Timeout)
|
||||
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
connection, err := mongo.Connect(ctx, connectionOptions)
|
||||
if err == nil {
|
||||
return connection.Database(deps.Configuration.MongoDatabase), nil
|
||||
}
|
||||
|
||||
log.Printf("failed to connect to db <%s>: %s", mongoURI.String(), err.Error())
|
||||
case <-timeoutExceeded:
|
||||
return nil, fmt.Errorf("db connection <%s> failed after %d timeout", mongoURI.String(), deps.Timeout)
|
||||
default:
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package mongo
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrEmptyArgs = errors.New("arguments are empty")
|
||||
)
|
599
tests/e2e/promo_test.go
Normal file
599
tests/e2e/promo_test.go
Normal file
@ -0,0 +1,599 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"codeword/tests/helpers"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pioz/faker"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var promoID string
|
||||
var fastLink string
|
||||
|
||||
// CreatePromoCode
|
||||
func TestCreatePromoCode(t *testing.T) {
|
||||
client := fiber.AcquireClient()
|
||||
|
||||
t.Run("CreatePromoCode-success", func(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
jsonString := `{
|
||||
"codeword": "example",
|
||||
"description": "Example description",
|
||||
"greetings": "Example greetings",
|
||||
"dueTo": 1734429225,
|
||||
"activationCount": 100,
|
||||
"bonus": {
|
||||
"privilege": {
|
||||
"privilegeID": "examplePrivilegeID",
|
||||
"amount": 50
|
||||
},
|
||||
"discount": {
|
||||
"layer": 1,
|
||||
"factor": 0.2,
|
||||
"target": "exampleTarget",
|
||||
"threshold": 500
|
||||
}
|
||||
},
|
||||
"outdated": false,
|
||||
"offLimit": false,
|
||||
"delete": false
|
||||
}`
|
||||
|
||||
var reqBody models.PromoCode
|
||||
err := json.Unmarshal([]byte(jsonString), &reqBody)
|
||||
assert.NoError(t, err)
|
||||
if i != 0 {
|
||||
reqBody.Codeword = reqBody.Codeword + faker.String() + strconv.Itoa(i)
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusCreated, statusCode)
|
||||
|
||||
var response models.PromoCode
|
||||
err = json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
promoID = response.ID.Hex()
|
||||
fmt.Println(response)
|
||||
}
|
||||
|
||||
})
|
||||
t.Run("CreatePromoCode-duplicate", func(t *testing.T) {
|
||||
jsonString := `{
|
||||
"codeword": "example",
|
||||
"description": "Example description",
|
||||
"greetings": "Example greetings",
|
||||
"dueTo": 1734429225,
|
||||
"activationCount": 100,
|
||||
"bonus": {
|
||||
"privilege": {
|
||||
"privilegeID": "examplePrivilegeID",
|
||||
"amount": 50
|
||||
},
|
||||
"discount": {
|
||||
"layer": 1,
|
||||
"factor": 0.2,
|
||||
"target": "exampleTarget",
|
||||
"threshold": 500
|
||||
}
|
||||
},
|
||||
"outdated": false,
|
||||
"offLimit": false,
|
||||
"delete": false
|
||||
}`
|
||||
|
||||
var reqBody models.PromoCode
|
||||
err := json.Unmarshal([]byte(jsonString), &reqBody)
|
||||
assert.NoError(t, err)
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.Error(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusBadRequest, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
t.Run("CreatePromoCode-invalid request payload", func(t *testing.T) {
|
||||
jsonString := `{
|
||||
"example": "example",
|
||||
"description": "Example description",
|
||||
"greetings": "Example greetings",
|
||||
"dueTo": 1734429225,
|
||||
"activationCount": 100,
|
||||
"bonus": {
|
||||
"privilege": {
|
||||
"privilegeID": "examplePrivilegeID",
|
||||
"amount": 50
|
||||
},
|
||||
"discount": {
|
||||
"layer": 1,
|
||||
"factor": 0.2,
|
||||
"target": "exampleTarget",
|
||||
"threshold": 500
|
||||
}
|
||||
},
|
||||
"outdated": false,
|
||||
"offLimit": false,
|
||||
"delete": false
|
||||
}`
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body([]byte(jsonString))
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.Error(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusBadRequest, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
t.Run("CreatePromoCode-nil codeword", func(t *testing.T) {
|
||||
jsonString := `{
|
||||
"description": "Example description",
|
||||
"greetings": "Example greetings",
|
||||
"dueTo": 1734429225,
|
||||
"activationCount": 100,
|
||||
"bonus": {
|
||||
"privilege": {
|
||||
"privilegeID": "examplePrivilegeID",
|
||||
"amount": 50
|
||||
},
|
||||
"discount": {
|
||||
"layer": 1,
|
||||
"factor": 0.2,
|
||||
"target": "exampleTarget",
|
||||
"threshold": 500
|
||||
}
|
||||
},
|
||||
"outdated": false,
|
||||
"offLimit": false,
|
||||
"delete": false
|
||||
}`
|
||||
|
||||
var reqBody models.PromoCode
|
||||
err := json.Unmarshal([]byte(jsonString), &reqBody)
|
||||
assert.NoError(t, err)
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.Error(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusBadRequest, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
}
|
||||
|
||||
// EditPromoCode
|
||||
func TestEditPromoCode(t *testing.T) {
|
||||
client := fiber.AcquireClient()
|
||||
|
||||
t.Run("EditPromoCode-success", func(t *testing.T) {
|
||||
reqBody := models.ReqEditPromoCode{
|
||||
ID: promoID,
|
||||
Description: toString("Updated description"),
|
||||
Greetings: toString("Updated greetings"),
|
||||
DueTo: toInt64(1734429225),
|
||||
ActivationCount: toInt64(150),
|
||||
Delete: toBool(false),
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
req := client.Put(BaseUrl+"/promocode/edit").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||
|
||||
var response models.PromoCode
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response)
|
||||
})
|
||||
|
||||
t.Run("EditPromoCode-success one column", func(t *testing.T) {
|
||||
reqBody := models.ReqEditPromoCode{
|
||||
ID: promoID,
|
||||
Greetings: toString("Updated greetings one"),
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
req := client.Put(BaseUrl+"/promocode/edit").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||
|
||||
var response models.PromoCode
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response)
|
||||
})
|
||||
|
||||
t.Run("EditPromoCode-promocod not found", func(t *testing.T) {
|
||||
reqBody := models.ReqEditPromoCode{
|
||||
ID: primitive.NewObjectID().Hex(),
|
||||
Description: toString("Updated description"),
|
||||
Greetings: toString("Updated greetings"),
|
||||
DueTo: toInt64(1734429225),
|
||||
ActivationCount: toInt64(150),
|
||||
Delete: toBool(false),
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
req := client.Put(BaseUrl+"/promocode/edit").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
assert.Equal(t, fiber.StatusNotFound, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
|
||||
t.Run("EditPromoCode-invalid request payload", func(t *testing.T) {
|
||||
reqBody := map[string]interface{}{
|
||||
"invalid_field": "example",
|
||||
"description": "Updated description",
|
||||
"greetings": "Updated greetings",
|
||||
"dueTo": 1734429225,
|
||||
"activationCount": 150,
|
||||
"delete": false,
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
req := client.Put(BaseUrl+"/promocode/edit").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
assert.Equal(t, fiber.StatusBadRequest, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
}
|
||||
|
||||
func toString(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func toInt64(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
func toBool(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// CreateFastLink
|
||||
func TestCreateFastLink(t *testing.T) {
|
||||
client := fiber.AcquireClient()
|
||||
|
||||
t.Run("CreateFastLink-success", func(t *testing.T) {
|
||||
reqBody := struct {
|
||||
PromoCodeID string `json:"id"`
|
||||
}{
|
||||
PromoCodeID: promoID,
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/fastlink").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
fmt.Println(string(resBody))
|
||||
assert.Equal(t, fiber.StatusCreated, statusCode)
|
||||
|
||||
var response map[string]string
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fastLink = response["fastlink"]
|
||||
fmt.Println(response["fastlink"])
|
||||
})
|
||||
|
||||
t.Run("CreateFastLink-missing promoCodeID", func(t *testing.T) {
|
||||
req := client.Post(BaseUrl+"/promocode/fastlink").Set("Content-Type", "application/json").Body([]byte(`{}`))
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.Error(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusBadRequest, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
|
||||
t.Run("CreateFastLink-promocode not found", func(t *testing.T) {
|
||||
reqBody := map[string]string{"id": primitive.NewObjectID().Hex()}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/fastlink").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.Error(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusNotFound, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
}
|
||||
|
||||
// GetPromoCodesList
|
||||
func TestGetPromoCodesList(t *testing.T) {
|
||||
client := fiber.AcquireClient()
|
||||
|
||||
t.Run("GetPromoCodesList-success", func(t *testing.T) {
|
||||
reqBody := models.GetPromoCodesListReq{
|
||||
Page: 0,
|
||||
Limit: 10,
|
||||
Filter: models.GetPromoCodesListReqFilter{
|
||||
Text: "example",
|
||||
Active: true,
|
||||
},
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
req := client.Post(BaseUrl+"/promocode/getList").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||
|
||||
var response models.GetPromoCodesListResp
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response)
|
||||
})
|
||||
|
||||
t.Run("GetPromoCodesList-invalid request payload", func(t *testing.T) {
|
||||
req := client.Post(BaseUrl+"/promocode/getList").Set("Content-Type", "application/json").Body([]byte("invalid json"))
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
assert.Equal(t, fiber.StatusBadRequest, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
}
|
||||
|
||||
// ActivatePromoCode
|
||||
func TestActivatePromoCode(t *testing.T) {
|
||||
client := fiber.AcquireClient()
|
||||
jwtUtil := helpers.InitializeJWT()
|
||||
token, tokenErr := jwtUtil.Create(ExampleUserID)
|
||||
fmt.Println(token)
|
||||
if isNoError := assert.NoError(t, tokenErr); !isNoError {
|
||||
return
|
||||
}
|
||||
t.Run("ActivatePromoCode-success codeword", func(t *testing.T) {
|
||||
reqBody := models.ActivateReq{
|
||||
Codeword: "example",
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
|
||||
fmt.Println(string(resBody))
|
||||
|
||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||
fmt.Println(statusCode)
|
||||
var response models.ActivateResp
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response)
|
||||
})
|
||||
|
||||
t.Run("ActivatePromoCode-success fastLink", func(t *testing.T) {
|
||||
reqBody := models.ActivateReq{
|
||||
FastLink: fastLink,
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||
var response models.ActivateResp
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response)
|
||||
})
|
||||
|
||||
t.Run("ActivatePromoCode-missing userid", func(t *testing.T) {
|
||||
reqBody := models.ActivateReq{
|
||||
Codeword: "example",
|
||||
}
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.Error(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusBadRequest, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
|
||||
t.Run("ActivatePromoCode-missing codeword and fastlink", func(t *testing.T) {
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(nil)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.Error(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusBadRequest, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
|
||||
t.Run("ActivatePromoCode-promocode not found", func(t *testing.T) {
|
||||
reqBody := models.ActivateReq{
|
||||
Codeword: "none",
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(reqJSON)
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.Error(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusNotFound, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
}
|
||||
|
||||
// GetPromoStats
|
||||
func TestGetPromoStats(t *testing.T) {
|
||||
client := fiber.AcquireClient()
|
||||
|
||||
t.Run("GetAllStats", func(t *testing.T) {
|
||||
|
||||
reqBody := struct {
|
||||
PromoCodeID string `json:"id"`
|
||||
}{
|
||||
PromoCodeID: promoID,
|
||||
}
|
||||
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
req := client.Get(BaseUrl+"/promocode/stats").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||
|
||||
var response []models.PromoCodeStats
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response)
|
||||
})
|
||||
}
|
||||
|
||||
// DeletePromoCode
|
||||
func TestDeletePromoCode(t *testing.T) {
|
||||
client := fiber.AcquireClient()
|
||||
|
||||
t.Run("DeletePromoCode-success", func(t *testing.T) {
|
||||
|
||||
req := client.Delete(BaseUrl + "/promocode/" + promoID)
|
||||
|
||||
statusCode, _, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||
})
|
||||
|
||||
t.Run("DeletePromoCode-promocode not found", func(t *testing.T) {
|
||||
|
||||
req := client.Delete(BaseUrl + "/promocode/" + primitive.NewObjectID().Hex())
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.Error(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusNotFound, statusCode)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &response)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(response["error"])
|
||||
})
|
||||
}
|
122
tests/e2e/recover_test.go
Normal file
122
tests/e2e/recover_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// todo добавить другие константы такие как exampleUserID
|
||||
const (
|
||||
BaseUrl = "http://localhost:8080"
|
||||
ValidSign = "GSiyv5zBITGshqnvYLHKtXE3e4yZjKGvruOVFWuUuj9Nvaps28-Zt6RDq9n47eaNUlay1-nUVld61I3xoAAgCA==65b286c2f13095d96792079d"
|
||||
ExampleUserID = "6597babdd1ba7e2dbd32d7e3"
|
||||
)
|
||||
|
||||
// post handler
|
||||
func TestRecoveryHandler(t *testing.T) {
|
||||
client := fiber.AcquireClient()
|
||||
|
||||
t.Run("HandleRecoveryRequest", func(t *testing.T) {
|
||||
reqBody := models.RecoveryRequest{
|
||||
Email: "adminSOLO",
|
||||
RedirectionURL: "http://redirect.com",
|
||||
}
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/recover").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, resBody, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||
|
||||
var responseMap map[string]interface{}
|
||||
err := json.Unmarshal(resBody, &responseMap)
|
||||
assert.NoError(t, err)
|
||||
fmt.Println(responseMap)
|
||||
})
|
||||
|
||||
t.Run("HandleRecoveryRequest-AlreadyReported", func(t *testing.T) {
|
||||
reqBody := models.RecoveryRequest{
|
||||
Email: "adminSOLO",
|
||||
RedirectionURL: "http://redirect.com",
|
||||
}
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/recover").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, _, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusAlreadyReported, statusCode)
|
||||
})
|
||||
|
||||
t.Run("HandleRecoveryRequest_MissingEmail", func(t *testing.T) {
|
||||
reqBody := models.RecoveryRequest{
|
||||
RedirectionURL: "http://redirect.com",
|
||||
}
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/recover").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, _, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusBadRequest, statusCode)
|
||||
})
|
||||
|
||||
t.Run("HandleRecoveryRequest_UserNotFound", func(t *testing.T) {
|
||||
reqBody := models.RecoveryRequest{
|
||||
Email: "nonexistent@example.com",
|
||||
RedirectionURL: "http://redirect.com",
|
||||
}
|
||||
reqJSON, _ := json.Marshal(reqBody)
|
||||
|
||||
req := client.Post(BaseUrl+"/recover").Set("Content-Type", "application/json").Body(reqJSON)
|
||||
|
||||
statusCode, _, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusNotFound, statusCode)
|
||||
})
|
||||
}
|
||||
|
||||
// get handler
|
||||
func TestRecoveryLinkHandler(t *testing.T) {
|
||||
client := fiber.AcquireClient()
|
||||
|
||||
t.Run("HandleRecoveryLink_ValidSign", func(t *testing.T) {
|
||||
req := client.Get(BaseUrl + "/recover/" + ValidSign)
|
||||
statusCode, _, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
|
||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||
|
||||
fmt.Println("Recovery link handled successfully")
|
||||
})
|
||||
time.Sleep(15 * time.Minute)
|
||||
t.Run("HandleRecoveryLink_ExpiredSign", func(t *testing.T) {
|
||||
req := client.Get(BaseUrl + "/recover/" + ValidSign)
|
||||
statusCode, _, errs := req.Bytes()
|
||||
if len(errs) != 0 {
|
||||
assert.NoError(t, errs[0])
|
||||
}
|
||||
assert.Equal(t, fiber.StatusNotAcceptable, statusCode)
|
||||
fmt.Println("Recovery link with expired sign handled correctly")
|
||||
})
|
||||
}
|
38
tests/helpers/jwt.go
Normal file
38
tests/helpers/jwt.go
Normal file
@ -0,0 +1,38 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"codeword/internal/initialize"
|
||||
"codeword/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func InitializeJWT() *utils.JWT {
|
||||
publicKey := strings.Replace(`-----BEGIN PUBLIC KEY-----
|
||||
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm69
|
||||
80fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6B
|
||||
dA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y
|
||||
+3GyaOY536H47qyXAgMBAAE=
|
||||
-----END PUBLIC KEY-----`, "\t", "", -1)
|
||||
|
||||
privateKey := strings.Replace(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2B
|
||||
iw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikH
|
||||
oKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAEC
|
||||
gYAOphnVPXbk6lpYzdkLC1Xn5EOEuNfOLLURLxBnPWozZo26r/Mtahu/9mYhrYlv
|
||||
PP8r6mxta3VIil8iOdZyOLa/4d1LPd+UehgEXIJEiYXLtn7RS5eUnoPuQxssfs1k
|
||||
OWjdN8p6SzppleegFTvGRX4KM3cDLfSphOk8JuBCrpSSYQJBAOdqizTSrdKMTuVe
|
||||
c7Jk1JOJkyFuFs+N5zeryyeFGH7IpRdWy0rkWMxIUAi8Ap1vYVBPHv4tDOo3sy5X
|
||||
VLc/knkCQQCE62pg+0TmsrhO/2Pgog6MLBkzlzXYMRp/01HbmznwYF+ejfPnzLkz
|
||||
hnUlxRUNK3lhXM/7H6oAjvqF2R72u/OPAkEAterkmdbQfEZ+MwNoEiH/lie9OLdx
|
||||
SSI1VGdBYcTYN7qFRW6eizYstBJYkDU0HQ0Uw+we4hMKJwk4W0KdvxxDiQJAeqlB
|
||||
V1QqBneBbK10PzVuFV8QtrJhJyxRVwrtbKq38iMNuqUnI4+ijXEUpJFWVvv6nKXo
|
||||
7McQvEk12dU/JNTX8wJAOlAtSNjp9tVwpMpC0w2St1eKc1L2SknjeohA5ldoBz8sGeZsPhTU3eHSD1neAZXLKN5K68z3zFBr20ubY9nyLw==
|
||||
-----END RSA PRIVATE KEY-----`, "\t", "", -1)
|
||||
|
||||
return utils.NewJWT(&initialize.Config{
|
||||
PrivateKey: privateKey,
|
||||
PublicKey: publicKey,
|
||||
Audience: "pena",
|
||||
Issuer: "pena-auth-service",
|
||||
})
|
||||
}
|
@ -7,9 +7,11 @@ import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"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"
|
||||
"log"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -17,10 +19,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// todo add another tests
|
||||
|
||||
const mongoURI = "mongodb://test:test@127.0.0.1:27020/?authMechanism=SCRAM-SHA-256&authSource=admin&directConnection=true"
|
||||
|
||||
// codeword unit tests
|
||||
func TestFindByEmail(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
@ -45,14 +46,15 @@ func TestFindByEmail(t *testing.T) {
|
||||
userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: db.Collection("users")})
|
||||
|
||||
t.Run("FindByEmail - existing user", func(t *testing.T) {
|
||||
user, err := userRepo.FindByEmail(ctx, "email@mail.ru")
|
||||
user, err := userRepo.FindByEmail(ctx, "admin")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, user)
|
||||
assert.Equal(t, "email@mail.ru", user.Email)
|
||||
fmt.Println(user.Email)
|
||||
assert.Equal(t, "admin", user.Login)
|
||||
})
|
||||
|
||||
t.Run("FindByEmail - non-existing user", func(t *testing.T) {
|
||||
user, err := userRepo.FindByEmail(ctx, "nonexisting@example.com")
|
||||
user, err := userRepo.FindByEmail(ctx, "neadmin")
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, user)
|
||||
})
|
||||
@ -60,7 +62,8 @@ func TestFindByEmail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStoreRecoveryRecord(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
@ -93,3 +96,423 @@ func TestStoreRecoveryRecord(t *testing.T) {
|
||||
|
||||
_ = database.Drop(ctx)
|
||||
}
|
||||
|
||||
func TestGetRecoveryRecord(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
codeword := database.Collection("codeword")
|
||||
_ = codeword.Drop(ctx)
|
||||
|
||||
userRepo := repository.NewCodewordRepository(repository.Deps{Rdb: nil, Mdb: codeword})
|
||||
|
||||
ID := primitive.NewObjectID()
|
||||
userID := "6597babdd1ba7e2dbd32d7e3"
|
||||
email := "test@mail.ru"
|
||||
key := "test_recovery_key"
|
||||
|
||||
record := models.RestoreRequest{
|
||||
ID: ID,
|
||||
UserID: userID,
|
||||
Email: email,
|
||||
Sign: key,
|
||||
SignUrl: "def.url",
|
||||
SignID: key + userID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
_, err = codeword.InsertOne(ctx, record)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := userRepo.GetRecoveryRecord(ctx, key+userID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, userID, result.UserID)
|
||||
assert.Equal(t, email, result.Email)
|
||||
assert.Equal(t, key, result.Sign)
|
||||
|
||||
_ = database.Drop(ctx)
|
||||
}
|
||||
|
||||
// promoCode unit tests
|
||||
|
||||
func TestInitPromoCodeIndexes(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
promoCode := database.Collection("promoCode")
|
||||
err = repository.InitPromoCodeIndexes(ctx, promoCode)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCreatePromoCode(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
promoCode := database.Collection("promoCode")
|
||||
_ = promoCode.Drop(ctx)
|
||||
|
||||
userRepo := repository.NewPromoCodeRepository(promoCode)
|
||||
|
||||
err = repository.InitPromoCodeIndexes(ctx, promoCode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("CreatePromoCode - success", func(t *testing.T) {
|
||||
req := &models.PromoCode{
|
||||
Codeword: "test_codeword",
|
||||
Description: faker.String(),
|
||||
Greetings: faker.String(),
|
||||
DueTo: 1737280065,
|
||||
ActivationCount: 100,
|
||||
}
|
||||
createdPromoCode, err := userRepo.CreatePromoCode(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, createdPromoCode)
|
||||
assert.Equal(t, "test_codeword", createdPromoCode.Codeword)
|
||||
})
|
||||
|
||||
t.Run("CreatePromoCode - duplicate codeword", func(t *testing.T) {
|
||||
req := &models.PromoCode{
|
||||
Codeword: "test_codeword",
|
||||
Description: faker.String(),
|
||||
Greetings: faker.String(),
|
||||
DueTo: 1737280065,
|
||||
ActivationCount: 100,
|
||||
}
|
||||
_, err := userRepo.CreatePromoCode(ctx, req)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEditPromoCode(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
promoCode := database.Collection("promoCode")
|
||||
_ = promoCode.Drop(ctx)
|
||||
|
||||
userRepo := repository.NewPromoCodeRepository(promoCode)
|
||||
|
||||
err = repository.InitPromoCodeIndexes(ctx, promoCode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := &models.PromoCode{
|
||||
Codeword: "test_codeword",
|
||||
Description: faker.String(),
|
||||
Greetings: faker.String(),
|
||||
DueTo: 1737280065,
|
||||
ActivationCount: 100,
|
||||
}
|
||||
createdPromoCode, err := userRepo.CreatePromoCode(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
newDescription := "New Description"
|
||||
|
||||
t.Run("EditPromoCode - success", func(t *testing.T) {
|
||||
editReq := &models.ReqEditPromoCode{
|
||||
ID: createdPromoCode.ID.Hex(),
|
||||
Description: &newDescription,
|
||||
}
|
||||
editedPromoCode, err := userRepo.EditPromoCode(ctx, editReq)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, editedPromoCode)
|
||||
assert.Equal(t, "New Description", editedPromoCode.Description)
|
||||
})
|
||||
|
||||
t.Run("EditPromoCode - promo code not found", func(t *testing.T) {
|
||||
nonExistingID := primitive.NewObjectID().Hex()
|
||||
editReq := &models.ReqEditPromoCode{
|
||||
ID: nonExistingID,
|
||||
}
|
||||
_, err := userRepo.EditPromoCode(ctx, editReq)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPromoCodeByID(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
promoCode := database.Collection("promoCode")
|
||||
_ = promoCode.Drop(ctx)
|
||||
|
||||
userRepo := repository.NewPromoCodeRepository(promoCode)
|
||||
|
||||
err = repository.InitPromoCodeIndexes(ctx, promoCode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := &models.PromoCode{
|
||||
Codeword: "test_codeword",
|
||||
Description: faker.String(),
|
||||
Greetings: faker.String(),
|
||||
DueTo: 1737280065,
|
||||
ActivationCount: 100,
|
||||
}
|
||||
createdPromoCode, err := userRepo.CreatePromoCode(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("GetPromoCodeByID - success", func(t *testing.T) {
|
||||
result, err := userRepo.GetPromoCodeByID(ctx, createdPromoCode.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, createdPromoCode.Codeword, result.Codeword)
|
||||
})
|
||||
|
||||
t.Run("GetPromoCodeByID - promo code not found", func(t *testing.T) {
|
||||
nonExistingID := primitive.NewObjectID()
|
||||
_, err := userRepo.GetPromoCodeByID(ctx, nonExistingID)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPromoCodesList(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
promoCode := database.Collection("promoCode")
|
||||
_ = promoCode.Drop(ctx)
|
||||
|
||||
userRepo := repository.NewPromoCodeRepository(promoCode)
|
||||
|
||||
err = repository.InitPromoCodeIndexes(ctx, promoCode)
|
||||
assert.NoError(t, err)
|
||||
for i := 0; i < 1111; i++ {
|
||||
|
||||
req := &models.PromoCode{
|
||||
Codeword: "test" + faker.String() + strconv.Itoa(i),
|
||||
Description: faker.String(),
|
||||
Greetings: faker.String(),
|
||||
DueTo: 1737280065,
|
||||
ActivationCount: 100,
|
||||
Delete: faker.Bool(),
|
||||
Outdated: faker.Bool(),
|
||||
OffLimit: faker.Bool(),
|
||||
}
|
||||
createdPromoCode, err := userRepo.CreatePromoCode(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, createdPromoCode)
|
||||
}
|
||||
t.Run("GetPromoCodesList - true", func(t *testing.T) {
|
||||
filter := models.GetPromoCodesListReqFilter{
|
||||
Text: "test",
|
||||
Active: true,
|
||||
}
|
||||
req := &models.GetPromoCodesListReq{
|
||||
Page: 0,
|
||||
Limit: 10,
|
||||
Filter: filter,
|
||||
}
|
||||
promoCodes, count, err := userRepo.GetPromoCodesList(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, promoCodes)
|
||||
assert.True(t, count >= 0)
|
||||
})
|
||||
|
||||
t.Run("GetPromoCodesList - false", func(t *testing.T) {
|
||||
filter := models.GetPromoCodesListReqFilter{
|
||||
Text: "test",
|
||||
Active: false,
|
||||
}
|
||||
req := &models.GetPromoCodesListReq{
|
||||
Page: 0,
|
||||
Limit: 10,
|
||||
Filter: filter,
|
||||
}
|
||||
promoCodes, count, err := userRepo.GetPromoCodesList(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, promoCodes)
|
||||
assert.True(t, count >= 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestActivatePromo(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
promoCode := database.Collection("promoCode")
|
||||
_ = promoCode.Drop(ctx)
|
||||
|
||||
userRepo := repository.NewPromoCodeRepository(promoCode)
|
||||
|
||||
err = repository.InitPromoCodeIndexes(ctx, promoCode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := &models.PromoCode{
|
||||
Codeword: "test_codeword",
|
||||
Description: faker.String(),
|
||||
Greetings: faker.String(),
|
||||
DueTo: 1737280065,
|
||||
ActivationCount: 100,
|
||||
}
|
||||
createdPromoCode, err := userRepo.CreatePromoCode(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, createdPromoCode)
|
||||
|
||||
xid := "test_xid"
|
||||
err = userRepo.AddFastLink(ctx, createdPromoCode.ID, xid)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("ActivatePromo Codeword - success", func(t *testing.T) {
|
||||
req := &models.ActivateReq{
|
||||
UserID: "6597babdd1ba7e2dbd32d7e3",
|
||||
Codeword: "test_codeword",
|
||||
}
|
||||
activatedPromoCode, err := userRepo.ActivatePromo(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, activatedPromoCode)
|
||||
})
|
||||
|
||||
t.Run("ActivatePromo FastLink - success", func(t *testing.T) {
|
||||
req := &models.ActivateReq{
|
||||
UserID: "6597babdd1ba7e2dbd32d7e3",
|
||||
FastLink: "test_xid",
|
||||
}
|
||||
activatedPromoCode, err := userRepo.ActivatePromo(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, activatedPromoCode)
|
||||
})
|
||||
|
||||
t.Run("ActivatePromo - promo code not found", func(t *testing.T) {
|
||||
req := &models.ActivateReq{
|
||||
UserID: "6597babdd1ba7e2dbd32d7e3",
|
||||
Codeword: "non_existing_codeword",
|
||||
}
|
||||
_, err := userRepo.ActivatePromo(ctx, req)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeletePromoCode(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
promoCode := database.Collection("promoCode")
|
||||
_ = promoCode.Drop(ctx)
|
||||
|
||||
userRepo := repository.NewPromoCodeRepository(promoCode)
|
||||
|
||||
err = repository.InitPromoCodeIndexes(ctx, promoCode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := &models.PromoCode{
|
||||
Codeword: "test_codeword",
|
||||
Description: faker.String(),
|
||||
Greetings: faker.String(),
|
||||
DueTo: 1737280065,
|
||||
ActivationCount: 100,
|
||||
}
|
||||
createdPromoCode, err := userRepo.CreatePromoCode(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, createdPromoCode)
|
||||
|
||||
t.Run("DeletePromoCode - success", func(t *testing.T) {
|
||||
err := userRepo.DeletePromoCode(ctx, createdPromoCode.ID.Hex())
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("DeletePromoCode - promo code not found", func(t *testing.T) {
|
||||
nonExistingID := primitive.NewObjectID().Hex()
|
||||
err := userRepo.DeletePromoCode(ctx, nonExistingID)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddFastLink(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
promoCode := database.Collection("promoCode")
|
||||
_ = promoCode.Drop(ctx)
|
||||
|
||||
userRepo := repository.NewPromoCodeRepository(promoCode)
|
||||
|
||||
err = repository.InitPromoCodeIndexes(ctx, promoCode)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req := &models.PromoCode{
|
||||
Codeword: "test_codeword",
|
||||
Description: faker.String(),
|
||||
Greetings: faker.String(),
|
||||
DueTo: 1737280065,
|
||||
ActivationCount: 100,
|
||||
}
|
||||
createdPromoCode, err := userRepo.CreatePromoCode(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, createdPromoCode)
|
||||
|
||||
t.Run("AddFastLink - success", func(t *testing.T) {
|
||||
xid := "test_xid"
|
||||
err := userRepo.AddFastLink(ctx, createdPromoCode.ID, xid)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("AddFastLink - promo code not found", func(t *testing.T) {
|
||||
nonExistingID := primitive.NewObjectID()
|
||||
xid := "test_xid"
|
||||
err := userRepo.AddFastLink(ctx, nonExistingID, xid)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
46
utils/authenticator.go
Normal file
46
utils/authenticator.go
Normal file
@ -0,0 +1,46 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
prefix = "Bearer "
|
||||
)
|
||||
|
||||
func NewAuthenticator(jwtUtil *JWT) func(*fiber.Ctx) error {
|
||||
return func(c *fiber.Ctx) error {
|
||||
if jwtUtil == nil {
|
||||
return fmt.Errorf("jwt util is nil")
|
||||
}
|
||||
|
||||
if err := authenticate(c, jwtUtil); err != nil {
|
||||
return fmt.Errorf("authentication error:%d", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func authenticate(c *fiber.Ctx, jwtUtil *JWT) error {
|
||||
authHeader := c.Get("Authorization")
|
||||
if authHeader == "" || !strings.HasPrefix(authHeader, prefix) {
|
||||
return fmt.Errorf("failed to parse jws from request header: %s", authHeader)
|
||||
}
|
||||
|
||||
jws := strings.TrimPrefix(authHeader, prefix)
|
||||
|
||||
userID, validateErr := jwtUtil.Validate(jws)
|
||||
if validateErr != nil {
|
||||
return validateErr
|
||||
}
|
||||
|
||||
c.Locals(models.AuthJWTDecodedUserIDKey, userID)
|
||||
c.Locals(models.AuthJWTDecodedAccessTokenKey, jws)
|
||||
|
||||
return nil
|
||||
}
|
89
utils/jwt.go
Normal file
89
utils/jwt.go
Normal file
@ -0,0 +1,89 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"codeword/internal/initialize"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JWT struct {
|
||||
privateKey []byte
|
||||
publicKey []byte
|
||||
algorithm *jwt.SigningMethodRSA
|
||||
expiresIn time.Duration
|
||||
issuer string
|
||||
audience string
|
||||
}
|
||||
|
||||
func NewJWT(configuration *initialize.Config) *JWT {
|
||||
return &JWT{
|
||||
privateKey: []byte(configuration.PrivateKey),
|
||||
publicKey: []byte(configuration.PublicKey),
|
||||
issuer: configuration.Issuer,
|
||||
audience: configuration.Audience,
|
||||
algorithm: jwt.SigningMethodRS256,
|
||||
expiresIn: 15 * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *JWT) Create(id string) (string, error) {
|
||||
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(j.privateKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse private key on <Create> of <JWT>: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
claims := jwt.MapClaims{
|
||||
"id": id,
|
||||
"exp": now.Add(j.expiresIn).Unix(),
|
||||
"aud": j.audience,
|
||||
"iss": j.issuer,
|
||||
}
|
||||
|
||||
token, err := jwt.NewWithClaims(j.algorithm, claims).SignedString(privateKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sing on <Create> of <JWT>: %w", err)
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (j *JWT) Validate(tokenString string) (string, error) {
|
||||
key, err := jwt.ParseRSAPublicKeyFromPEM(j.publicKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse rsa public key on <Validate> of <JWT>: %w", err)
|
||||
}
|
||||
|
||||
parseCallback := func(token *jwt.Token) (any, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %s", token.Header["alg"])
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(
|
||||
tokenString,
|
||||
parseCallback,
|
||||
jwt.WithAudience(j.audience),
|
||||
jwt.WithIssuer(j.issuer),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse jwt token on <Validate> of <JWT>: %w", err)
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok || !token.Valid {
|
||||
return "", errors.New("token is invalid on <Validate> of <JWT>")
|
||||
}
|
||||
|
||||
data, ok := claims["id"].(string)
|
||||
if !ok {
|
||||
return "", errors.New("data is empty or not a string on <Validate> of <JWT>")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user