diff --git a/.env b/.env index 40ce312..38c8fcd 100644 --- a/.env +++ b/.env @@ -22,6 +22,26 @@ PUBLIC_CURVE_KEY="-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAEbnIvjIMle4rqVol6K PRIVATE_CURVE_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZvODgWAnUIwQhd8de5oZhY48gc23EWfrfs\n-----END PRIVATE KEY-----" SIGN_SECRET="pena-auth-microservice-group" +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="secret" @@ -37,4 +57,9 @@ SMTP_SENDER="noreply@mailing.pena.digital" # URL settings DEFAULT_REDIRECTION_URL = "https://shub.pena.digital/recover/" AUTH_EXCHANGE_URL = "http://10.6.0.11:59300/auth/exchange" -RECOVER_URL = "https://shub.pena.digital/recover/" \ No newline at end of file +RECOVER_URL = "https://shub.pena.digital/recover/" +DISCOUNT_ADDRESS = "http://10.6.0.11:9001" + +# Kafka settings +KAFKA_BROKERS="10.6.0.11:9092" +KAFKA_TOPIC_TARIFF="tariffs" diff --git a/deployments/local/docker-compose.yaml b/deployments/local/docker-compose.yaml index 249e549..d77ec4a 100644 --- a/deployments/local/docker-compose.yaml +++ b/deployments/local/docker-compose.yaml @@ -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 diff --git a/docs/openapi.yaml b/docs/openapi.yaml index aff5967..308a548 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -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: Поле из активированного промокода \ No newline at end of file + 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: Порог скидки + diff --git a/go.mod b/go.mod index 3dbfa38..fc04dda 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 05dc7a4..523b7ea 100644 --- a/go.sum +++ b/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= diff --git a/internal/app/app.go b/internal/app/app.go index ef17660..9e082d9 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -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 } diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 903aef0..f7ef478 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -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) +} diff --git a/internal/controller/promocode/route.go b/internal/controller/promocode/route.go new file mode 100644 index 0000000..af93df7 --- /dev/null +++ b/internal/controller/promocode/route.go @@ -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" +} diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 4b8de66..5a85980 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -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,11 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } +<<<<<<< HEAD signUrl := redirectionURL +======= + signUrl := req.RedirectionURL +>>>>>>> dev sign := base64.URLEncoding.EncodeToString(key) id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{ @@ -67,22 +95,24 @@ 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{"sent": email}) + 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("Recovery link expired", zap.String("signature", key)) return c.Redirect("https://shub.pena.digital/recover/expired") @@ -107,5 +137,14 @@ func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { HTTPOnly: true, }) - return c.Redirect(record.SignUrl+"?auth="+tokens["accessToken"]) + 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"]) } diff --git a/internal/controller/recovery/route.go b/internal/controller/recovery/route.go new file mode 100644 index 0000000..c2d6431 --- /dev/null +++ b/internal/controller/recovery/route.go @@ -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 "" +} diff --git a/internal/initialize/config.go b/internal/initialize/config.go index f80906b..23c78b1 100644 --- a/internal/initialize/config.go +++ b/internal/initialize/config.go @@ -7,31 +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"` - RecoveryUrl string `env:"RECOVER_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) { diff --git a/internal/initialize/grpc.go b/internal/initialize/grpc.go new file mode 100644 index 0000000..692ca21 --- /dev/null +++ b/internal/initialize/grpc.go @@ -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 +} diff --git a/internal/initialize/kafka.go b/internal/initialize/kafka.go new file mode 100644 index 0000000..8b4019d --- /dev/null +++ b/internal/initialize/kafka.go @@ -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, + }), + } +} diff --git a/internal/initialize/mongo.go b/internal/initialize/mongo.go index 64fea74..c947b4a 100644 --- a/internal/initialize/mongo.go +++ b/internal/initialize/mongo.go @@ -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 +} diff --git a/internal/kafka/tariff/producer.go b/internal/kafka/tariff/producer.go new file mode 100644 index 0000000..69a5e92 --- /dev/null +++ b/internal/kafka/tariff/producer.go @@ -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 ") + } + + if deps.Client == nil { + log.Panicln("Kafka client is nil on ") + } + + 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 +} diff --git a/internal/models/auth.go b/internal/models/auth.go index a159a74..204def2 100644 --- a/internal/models/auth.go +++ b/internal/models/auth.go @@ -9,3 +9,6 @@ type RefreshResponse struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` } + +const AuthJWTDecodedUserIDKey = "userID" +const AuthJWTDecodedAccessTokenKey = "access-token" diff --git a/internal/models/bonus.go b/internal/models/bonus.go index de7de8c..9c3cf6e 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -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"` +} diff --git a/internal/models/tariff.go b/internal/models/tariff.go new file mode 100644 index 0000000..00da357 --- /dev/null +++ b/internal/models/tariff.go @@ -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, + } +) diff --git a/internal/models/user.go b/internal/models/user.go index dfe0a2f..b1e32bf 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -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"` +} diff --git a/internal/proto/broker/models.pb.go b/internal/proto/broker/models.pb.go new file mode 100644 index 0000000..cb81617 --- /dev/null +++ b/internal/proto/broker/models.pb.go @@ -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 +} diff --git a/internal/proto/discount/audit.model.pb.go b/internal/proto/discount/audit.model.pb.go new file mode 100644 index 0000000..7c2347e --- /dev/null +++ b/internal/proto/discount/audit.model.pb.go @@ -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 +} diff --git a/internal/proto/discount/discount.model.pb.go b/internal/proto/discount/discount.model.pb.go new file mode 100644 index 0000000..c0fce2b --- /dev/null +++ b/internal/proto/discount/discount.model.pb.go @@ -0,0 +1,1132 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: discount/discount.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 TargetScope int32 + +const ( + TargetScope_Sum TargetScope = 0 + TargetScope_Group TargetScope = 1 + TargetScope_Each TargetScope = 2 +) + +// Enum value maps for TargetScope. +var ( + TargetScope_name = map[int32]string{ + 0: "Sum", + 1: "Group", + 2: "Each", + } + TargetScope_value = map[string]int32{ + "Sum": 0, + "Group": 1, + "Each": 2, + } +) + +func (x TargetScope) Enum() *TargetScope { + p := new(TargetScope) + *p = x + return p +} + +func (x TargetScope) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TargetScope) Descriptor() protoreflect.EnumDescriptor { + return file_discount_discount_model_proto_enumTypes[0].Descriptor() +} + +func (TargetScope) Type() protoreflect.EnumType { + return &file_discount_discount_model_proto_enumTypes[0] +} + +func (x TargetScope) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TargetScope.Descriptor instead. +func (TargetScope) EnumDescriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{0} +} + +type DiscountOptional struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Name *string `protobuf:"bytes,2,opt,name=Name,proto3,oneof" json:"Name,omitempty"` + Layer *uint32 `protobuf:"varint,3,opt,name=Layer,proto3,oneof" json:"Layer,omitempty"` + Description *string `protobuf:"bytes,4,opt,name=Description,proto3,oneof" json:"Description,omitempty"` + Condition *DiscountCondition `protobuf:"bytes,5,opt,name=Condition,proto3,oneof" json:"Condition,omitempty"` + Target *DiscountCalculationTarget `protobuf:"bytes,6,opt,name=Target,proto3,oneof" json:"Target,omitempty"` + Deprecated *bool `protobuf:"varint,7,opt,name=Deprecated,proto3,oneof" json:"Deprecated,omitempty"` +} + +func (x *DiscountOptional) Reset() { + *x = DiscountOptional{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DiscountOptional) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiscountOptional) ProtoMessage() {} + +func (x *DiscountOptional) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_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 DiscountOptional.ProtoReflect.Descriptor instead. +func (*DiscountOptional) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{0} +} + +func (x *DiscountOptional) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *DiscountOptional) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *DiscountOptional) GetLayer() uint32 { + if x != nil && x.Layer != nil { + return *x.Layer + } + return 0 +} + +func (x *DiscountOptional) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *DiscountOptional) GetCondition() *DiscountCondition { + if x != nil { + return x.Condition + } + return nil +} + +func (x *DiscountOptional) GetTarget() *DiscountCalculationTarget { + if x != nil { + return x.Target + } + return nil +} + +func (x *DiscountOptional) GetDeprecated() bool { + if x != nil && x.Deprecated != nil { + return *x.Deprecated + } + return false +} + +type Discounts struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Discounts []*Discount `protobuf:"bytes,1,rep,name=Discounts,proto3" json:"Discounts,omitempty"` +} + +func (x *Discounts) Reset() { + *x = Discounts{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Discounts) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Discounts) ProtoMessage() {} + +func (x *Discounts) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_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 Discounts.ProtoReflect.Descriptor instead. +func (*Discounts) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{1} +} + +func (x *Discounts) GetDiscounts() []*Discount { + if x != nil { + return x.Discounts + } + return nil +} + +type Discount struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` + Layer uint32 `protobuf:"varint,3,opt,name=Layer,proto3" json:"Layer,omitempty"` + Description string `protobuf:"bytes,4,opt,name=Description,proto3" json:"Description,omitempty"` + Condition *DiscountCondition `protobuf:"bytes,5,opt,name=Condition,proto3" json:"Condition,omitempty"` + Target *DiscountCalculationTarget `protobuf:"bytes,6,opt,name=Target,proto3" json:"Target,omitempty"` + Audit *Audit `protobuf:"bytes,7,opt,name=Audit,proto3" json:"Audit,omitempty"` + Deprecated bool `protobuf:"varint,8,opt,name=Deprecated,proto3" json:"Deprecated,omitempty"` +} + +func (x *Discount) Reset() { + *x = Discount{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Discount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Discount) ProtoMessage() {} + +func (x *Discount) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_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 Discount.ProtoReflect.Descriptor instead. +func (*Discount) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{2} +} + +func (x *Discount) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *Discount) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Discount) GetLayer() uint32 { + if x != nil { + return x.Layer + } + return 0 +} + +func (x *Discount) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Discount) GetCondition() *DiscountCondition { + if x != nil { + return x.Condition + } + return nil +} + +func (x *Discount) GetTarget() *DiscountCalculationTarget { + if x != nil { + return x.Target + } + return nil +} + +func (x *Discount) GetAudit() *Audit { + if x != nil { + return x.Audit + } + return nil +} + +func (x *Discount) GetDeprecated() bool { + if x != nil { + return x.Deprecated + } + return false +} + +type DiscountCalculationTarget struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Products []*ProductTarget `protobuf:"bytes,1,rep,name=Products,proto3" json:"Products,omitempty"` + Factor float64 `protobuf:"fixed64,2,opt,name=Factor,proto3" json:"Factor,omitempty"` + TargetScope *TargetScope `protobuf:"varint,3,opt,name=TargetScope,proto3,enum=discount.TargetScope,oneof" json:"TargetScope,omitempty"` + TargetGroup *string `protobuf:"bytes,4,opt,name=TargetGroup,proto3,oneof" json:"TargetGroup,omitempty"` + Overhelm *bool `protobuf:"varint,5,opt,name=Overhelm,proto3,oneof" json:"Overhelm,omitempty"` +} + +func (x *DiscountCalculationTarget) Reset() { + *x = DiscountCalculationTarget{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DiscountCalculationTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiscountCalculationTarget) ProtoMessage() {} + +func (x *DiscountCalculationTarget) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_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 DiscountCalculationTarget.ProtoReflect.Descriptor instead. +func (*DiscountCalculationTarget) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{3} +} + +func (x *DiscountCalculationTarget) GetProducts() []*ProductTarget { + if x != nil { + return x.Products + } + return nil +} + +func (x *DiscountCalculationTarget) GetFactor() float64 { + if x != nil { + return x.Factor + } + return 0 +} + +func (x *DiscountCalculationTarget) GetTargetScope() TargetScope { + if x != nil && x.TargetScope != nil { + return *x.TargetScope + } + return TargetScope_Sum +} + +func (x *DiscountCalculationTarget) GetTargetGroup() string { + if x != nil && x.TargetGroup != nil { + return *x.TargetGroup + } + return "" +} + +func (x *DiscountCalculationTarget) GetOverhelm() bool { + if x != nil && x.Overhelm != nil { + return *x.Overhelm + } + return false +} + +type DiscountCondition struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Period *PeriodCondition `protobuf:"bytes,1,opt,name=Period,proto3,oneof" json:"Period,omitempty"` + User *string `protobuf:"bytes,2,opt,name=User,proto3,oneof" json:"User,omitempty"` + UserType *string `protobuf:"bytes,3,opt,name=UserType,proto3,oneof" json:"UserType,omitempty"` + Coupon *string `protobuf:"bytes,4,opt,name=Coupon,proto3,oneof" json:"Coupon,omitempty"` + PurchasesAmount *uint64 `protobuf:"varint,5,opt,name=PurchasesAmount,proto3,oneof" json:"PurchasesAmount,omitempty"` + CartPurchasesAmount *uint64 `protobuf:"varint,6,opt,name=CartPurchasesAmount,proto3,oneof" json:"CartPurchasesAmount,omitempty"` + Product *string `protobuf:"bytes,7,opt,name=Product,proto3,oneof" json:"Product,omitempty"` + Term *uint64 `protobuf:"varint,8,opt,name=Term,proto3,oneof" json:"Term,omitempty"` + Usage *uint64 `protobuf:"varint,9,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"` + PriceFrom *uint64 `protobuf:"varint,10,opt,name=PriceFrom,proto3,oneof" json:"PriceFrom,omitempty"` + Group *string `protobuf:"bytes,11,opt,name=Group,proto3,oneof" json:"Group,omitempty"` +} + +func (x *DiscountCondition) Reset() { + *x = DiscountCondition{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DiscountCondition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiscountCondition) ProtoMessage() {} + +func (x *DiscountCondition) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiscountCondition.ProtoReflect.Descriptor instead. +func (*DiscountCondition) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{4} +} + +func (x *DiscountCondition) GetPeriod() *PeriodCondition { + if x != nil { + return x.Period + } + return nil +} + +func (x *DiscountCondition) GetUser() string { + if x != nil && x.User != nil { + return *x.User + } + return "" +} + +func (x *DiscountCondition) GetUserType() string { + if x != nil && x.UserType != nil { + return *x.UserType + } + return "" +} + +func (x *DiscountCondition) GetCoupon() string { + if x != nil && x.Coupon != nil { + return *x.Coupon + } + return "" +} + +func (x *DiscountCondition) GetPurchasesAmount() uint64 { + if x != nil && x.PurchasesAmount != nil { + return *x.PurchasesAmount + } + return 0 +} + +func (x *DiscountCondition) GetCartPurchasesAmount() uint64 { + if x != nil && x.CartPurchasesAmount != nil { + return *x.CartPurchasesAmount + } + return 0 +} + +func (x *DiscountCondition) GetProduct() string { + if x != nil && x.Product != nil { + return *x.Product + } + return "" +} + +func (x *DiscountCondition) GetTerm() uint64 { + if x != nil && x.Term != nil { + return *x.Term + } + return 0 +} + +func (x *DiscountCondition) GetUsage() uint64 { + if x != nil && x.Usage != nil { + return *x.Usage + } + return 0 +} + +func (x *DiscountCondition) GetPriceFrom() uint64 { + if x != nil && x.PriceFrom != nil { + return *x.PriceFrom + } + return 0 +} + +func (x *DiscountCondition) GetGroup() string { + if x != nil && x.Group != nil { + return *x.Group + } + return "" +} + +type ProductTarget struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Factor float64 `protobuf:"fixed64,2,opt,name=Factor,proto3" json:"Factor,omitempty"` + Overhelm *bool `protobuf:"varint,3,opt,name=Overhelm,proto3,oneof" json:"Overhelm,omitempty"` +} + +func (x *ProductTarget) Reset() { + *x = ProductTarget{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProductTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProductTarget) ProtoMessage() {} + +func (x *ProductTarget) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProductTarget.ProtoReflect.Descriptor instead. +func (*ProductTarget) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{5} +} + +func (x *ProductTarget) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ProductTarget) GetFactor() float64 { + if x != nil { + return x.Factor + } + return 0 +} + +func (x *ProductTarget) GetOverhelm() bool { + if x != nil && x.Overhelm != nil { + return *x.Overhelm + } + return false +} + +type PeriodCondition struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + From *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=From,proto3" json:"From,omitempty"` + To *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=To,proto3" json:"To,omitempty"` +} + +func (x *PeriodCondition) Reset() { + *x = PeriodCondition{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PeriodCondition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeriodCondition) ProtoMessage() {} + +func (x *PeriodCondition) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PeriodCondition.ProtoReflect.Descriptor instead. +func (*PeriodCondition) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{6} +} + +func (x *PeriodCondition) GetFrom() *timestamppb.Timestamp { + if x != nil { + return x.From + } + return nil +} + +func (x *PeriodCondition) GetTo() *timestamppb.Timestamp { + if x != nil { + return x.To + } + return nil +} + +type UserInformation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"` + PurchasesAmount uint64 `protobuf:"varint,3,opt,name=PurchasesAmount,proto3" json:"PurchasesAmount,omitempty"` + CartPurchasesAmount uint64 `protobuf:"varint,4,opt,name=CartPurchasesAmount,proto3" json:"CartPurchasesAmount,omitempty"` +} + +func (x *UserInformation) Reset() { + *x = UserInformation{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UserInformation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserInformation) ProtoMessage() {} + +func (x *UserInformation) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UserInformation.ProtoReflect.Descriptor instead. +func (*UserInformation) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{7} +} + +func (x *UserInformation) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *UserInformation) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *UserInformation) GetPurchasesAmount() uint64 { + if x != nil { + return x.PurchasesAmount + } + return 0 +} + +func (x *UserInformation) GetCartPurchasesAmount() uint64 { + if x != nil { + return x.CartPurchasesAmount + } + return 0 +} + +type ProductInformation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Price uint64 `protobuf:"varint,2,opt,name=Price,proto3" json:"Price,omitempty"` + Term *uint64 `protobuf:"varint,3,opt,name=Term,proto3,oneof" json:"Term,omitempty"` + Usage *uint64 `protobuf:"varint,4,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"` + Group *string `protobuf:"bytes,5,opt,name=Group,proto3,oneof" json:"Group,omitempty"` +} + +func (x *ProductInformation) Reset() { + *x = ProductInformation{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProductInformation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProductInformation) ProtoMessage() {} + +func (x *ProductInformation) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProductInformation.ProtoReflect.Descriptor instead. +func (*ProductInformation) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{8} +} + +func (x *ProductInformation) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ProductInformation) GetPrice() uint64 { + if x != nil { + return x.Price + } + return 0 +} + +func (x *ProductInformation) GetTerm() uint64 { + if x != nil && x.Term != nil { + return *x.Term + } + return 0 +} + +func (x *ProductInformation) GetUsage() uint64 { + if x != nil && x.Usage != nil { + return *x.Usage + } + return 0 +} + +func (x *ProductInformation) GetGroup() string { + if x != nil && x.Group != nil { + return *x.Group + } + return "" +} + +var File_discount_discount_model_proto protoreflect.FileDescriptor + +var file_discount_discount_model_proto_rawDesc = []byte{ + 0x0a, 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, 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, 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, 0x22, 0xef, 0x02, 0x0a, 0x10, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x17, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x19, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, + 0x52, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x02, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, + 0x01, 0x12, 0x3e, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, + 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, 0x48, 0x03, 0x52, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, + 0x01, 0x12, 0x40, 0x0a, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x06, 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, 0x48, 0x04, 0x52, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x88, 0x01, 0x01, 0x12, 0x23, 0x0a, 0x0a, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x05, 0x52, 0x0a, 0x44, 0x65, 0x70, 0x72, 0x65, + 0x63, 0x61, 0x74, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x4e, 0x61, 0x6d, + 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, + 0x74, 0x65, 0x64, 0x22, 0x3d, 0x0a, 0x09, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x12, 0x30, 0x0a, 0x09, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 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, 0x09, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x22, 0xa5, 0x02, 0x0a, 0x08, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, + 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 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, 0x04, 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, 0x05, 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, 0x06, 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, 0x12, 0x25, 0x0a, 0x05, 0x41, 0x75, 0x64, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x75, + 0x64, 0x69, 0x74, 0x52, 0x05, 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x44, 0x65, + 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x22, 0x9b, 0x02, 0x0a, 0x19, 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, 0x12, 0x33, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x46, + 0x61, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x3c, 0x0a, 0x0b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, + 0x65, 0x48, 0x00, 0x52, 0x0b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, + 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x4f, 0x76, + 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, 0x08, + 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x42, 0x0b, 0x0a, 0x09, 0x5f, + 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x22, 0xa8, 0x04, 0x0a, 0x11, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, + 0x0a, 0x06, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x06, 0x50, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x04, 0x55, 0x73, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, + 0x1f, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x02, 0x52, 0x08, 0x55, 0x73, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, + 0x12, 0x1b, 0x0a, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x03, 0x52, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, + 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x48, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, + 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x13, + 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x48, 0x05, 0x52, 0x13, 0x43, 0x61, 0x72, + 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x88, + 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, + 0x48, 0x07, 0x52, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x48, 0x08, 0x52, 0x05, 0x55, 0x73, + 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, + 0x72, 0x6f, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x48, 0x09, 0x52, 0x09, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0a, 0x52, 0x05, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x42, + 0x07, 0x0a, 0x05, 0x5f, 0x55, 0x73, 0x65, 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x55, 0x73, 0x65, + 0x72, 0x54, 0x79, 0x70, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, + 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, + 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x0a, 0x0a, 0x08, + 0x5f, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x54, 0x65, 0x72, + 0x6d, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x55, 0x73, 0x61, 0x67, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, + 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x22, 0x65, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x08, + 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, + 0x52, 0x08, 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, + 0x09, 0x5f, 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x22, 0x6d, 0x0a, 0x0f, 0x50, 0x65, + 0x72, 0x69, 0x6f, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, + 0x04, 0x46, 0x72, 0x6f, 0x6d, 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, 0x04, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x2a, 0x0a, + 0x02, 0x54, 0x6f, 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, 0x02, 0x54, 0x6f, 0x22, 0x91, 0x01, 0x0a, 0x0f, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, + 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, + 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x13, 0x43, + 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, + 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa6, 0x01, + 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x54, 0x65, + 0x72, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x04, 0x54, 0x65, 0x72, 0x6d, + 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x48, 0x01, 0x52, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x19, + 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, + 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x54, 0x65, + 0x72, 0x6d, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x55, 0x73, 0x61, 0x67, 0x65, 0x42, 0x08, 0x0a, 0x06, + 0x5f, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2a, 0x2b, 0x0a, 0x0b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x75, 0x6d, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x61, 0x63, + 0x68, 0x10, 0x02, 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_discount_model_proto_rawDescOnce sync.Once + file_discount_discount_model_proto_rawDescData = file_discount_discount_model_proto_rawDesc +) + +func file_discount_discount_model_proto_rawDescGZIP() []byte { + file_discount_discount_model_proto_rawDescOnce.Do(func() { + file_discount_discount_model_proto_rawDescData = protoimpl.X.CompressGZIP(file_discount_discount_model_proto_rawDescData) + }) + return file_discount_discount_model_proto_rawDescData +} + +var file_discount_discount_model_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_discount_discount_model_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_discount_discount_model_proto_goTypes = []interface{}{ + (TargetScope)(0), // 0: discount.TargetScope + (*DiscountOptional)(nil), // 1: discount.DiscountOptional + (*Discounts)(nil), // 2: discount.Discounts + (*Discount)(nil), // 3: discount.Discount + (*DiscountCalculationTarget)(nil), // 4: discount.DiscountCalculationTarget + (*DiscountCondition)(nil), // 5: discount.DiscountCondition + (*ProductTarget)(nil), // 6: discount.ProductTarget + (*PeriodCondition)(nil), // 7: discount.PeriodCondition + (*UserInformation)(nil), // 8: discount.UserInformation + (*ProductInformation)(nil), // 9: discount.ProductInformation + (*Audit)(nil), // 10: discount.Audit + (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp +} +var file_discount_discount_model_proto_depIdxs = []int32{ + 5, // 0: discount.DiscountOptional.Condition:type_name -> discount.DiscountCondition + 4, // 1: discount.DiscountOptional.Target:type_name -> discount.DiscountCalculationTarget + 3, // 2: discount.Discounts.Discounts:type_name -> discount.Discount + 5, // 3: discount.Discount.Condition:type_name -> discount.DiscountCondition + 4, // 4: discount.Discount.Target:type_name -> discount.DiscountCalculationTarget + 10, // 5: discount.Discount.Audit:type_name -> discount.Audit + 6, // 6: discount.DiscountCalculationTarget.Products:type_name -> discount.ProductTarget + 0, // 7: discount.DiscountCalculationTarget.TargetScope:type_name -> discount.TargetScope + 7, // 8: discount.DiscountCondition.Period:type_name -> discount.PeriodCondition + 11, // 9: discount.PeriodCondition.From:type_name -> google.protobuf.Timestamp + 11, // 10: discount.PeriodCondition.To:type_name -> google.protobuf.Timestamp + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_discount_discount_model_proto_init() } +func file_discount_discount_model_proto_init() { + if File_discount_discount_model_proto != nil { + return + } + file_discount_audit_model_proto_init() + if !protoimpl.UnsafeEnabled { + file_discount_discount_model_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DiscountOptional); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Discounts); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Discount); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DiscountCalculationTarget); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DiscountCondition); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProductTarget); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeriodCondition); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UserInformation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProductInformation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_discount_discount_model_proto_msgTypes[0].OneofWrappers = []interface{}{} + file_discount_discount_model_proto_msgTypes[3].OneofWrappers = []interface{}{} + file_discount_discount_model_proto_msgTypes[4].OneofWrappers = []interface{}{} + file_discount_discount_model_proto_msgTypes[5].OneofWrappers = []interface{}{} + file_discount_discount_model_proto_msgTypes[8].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_discount_discount_model_proto_rawDesc, + NumEnums: 1, + NumMessages: 9, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_discount_discount_model_proto_goTypes, + DependencyIndexes: file_discount_discount_model_proto_depIdxs, + EnumInfos: file_discount_discount_model_proto_enumTypes, + MessageInfos: file_discount_discount_model_proto_msgTypes, + }.Build() + File_discount_discount_model_proto = out.File + file_discount_discount_model_proto_rawDesc = nil + file_discount_discount_model_proto_goTypes = nil + file_discount_discount_model_proto_depIdxs = nil +} diff --git a/internal/proto/discount/service.pb.go b/internal/proto/discount/service.pb.go new file mode 100644 index 0000000..84ea7d9 --- /dev/null +++ b/internal/proto/discount/service.pb.go @@ -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 +} diff --git a/internal/proto/discount/service_grpc.pb.go b/internal/proto/discount/service_grpc.pb.go new file mode 100644 index 0000000..e9161fb --- /dev/null +++ b/internal/proto/discount/service_grpc.pb.go @@ -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", +} diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go index 6f19925..d2a9fec 100644 --- a/internal/repository/codeword_repository.go +++ b/internal/repository/codeword_repository.go @@ -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,6 +88,8 @@ 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 } diff --git a/internal/repository/errors.go b/internal/repository/errors.go new file mode 100644 index 0000000..c941611 --- /dev/null +++ b/internal/repository/errors.go @@ -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") +) diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 5b0f04c..d17180f 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -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 +} diff --git a/internal/repository/promocode_stats.go b/internal/repository/promocode_stats.go new file mode 100644 index 0000000..1daf023 --- /dev/null +++ b/internal/repository/promocode_stats.go @@ -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 +} diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index ac7e88f..97a62ae 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -31,7 +31,7 @@ func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*models if err == mongo.ErrNoDocuments { return nil, nil } - return nil, err + return nil, ErrPromoUserNotFound } return &user, nil } diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index 3133054..4bb0aed 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -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,33 +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) } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 2197c85..3947f3e 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -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 +} diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index 664d447..348b834 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -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 } diff --git a/internal/utils/genID/gen_id.go b/internal/utils/genID/gen_id.go new file mode 100644 index 0000000..2a6610f --- /dev/null +++ b/internal/utils/genID/gen_id.go @@ -0,0 +1,8 @@ +package genID + +import "github.com/rs/xid" + +func GenerateXID() string { + id := xid.New() + return id.String() +} diff --git a/internal/utils/transfer/privilege.go b/internal/utils/transfer/privilege.go new file mode 100644 index 0000000..60bb9c5 --- /dev/null +++ b/internal/utils/transfer/privilege.go @@ -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 +} diff --git a/internal/utils/transfer/tariff.go b/internal/utils/transfer/tariff.go new file mode 100644 index 0000000..592c56e --- /dev/null +++ b/internal/utils/transfer/tariff.go @@ -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), + } +} diff --git a/internal/worker/purge_worker/purge_worker.go b/internal/worker/purge_worker/purge_worker.go index e621641..017c7f5 100644 --- a/internal/worker/purge_worker/purge_worker.go +++ b/internal/worker/purge_worker/purge_worker.go @@ -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 +} diff --git a/internal/worker/recovery_worker/recovery_worker.go b/internal/worker/recovery_worker/recovery_worker.go index 7237278..df79493 100644 --- a/internal/worker/recovery_worker/recovery_worker.go +++ b/internal/worker/recovery_worker/recovery_worker.go @@ -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 +} diff --git a/pkg/closer/closer.go b/pkg/closer/closer.go new file mode 100644 index 0000000..fdfbaf1 --- /dev/null +++ b/pkg/closer/closer.go @@ -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 +} diff --git a/pkg/mongo/config.go b/pkg/mongo/config.go deleted file mode 100644 index dc809b4..0000000 --- a/pkg/mongo/config.go +++ /dev/null @@ -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 -} diff --git a/pkg/mongo/connection.go b/pkg/mongo/connection.go deleted file mode 100644 index 726b277..0000000 --- a/pkg/mongo/connection.go +++ /dev/null @@ -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) - } - } -} diff --git a/pkg/mongo/errors.go b/pkg/mongo/errors.go deleted file mode 100644 index 2e592f2..0000000 --- a/pkg/mongo/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package mongo - -import "errors" - -var ( - ErrEmptyArgs = errors.New("arguments are empty") -) diff --git a/tests/e2e/promo_test.go b/tests/e2e/promo_test.go new file mode 100644 index 0000000..822dfd1 --- /dev/null +++ b/tests/e2e/promo_test.go @@ -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"]) + }) +} diff --git a/tests/e2e/recover_test.go b/tests/e2e/recover_test.go new file mode 100644 index 0000000..0aab88f --- /dev/null +++ b/tests/e2e/recover_test.go @@ -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") + }) +} diff --git a/tests/helpers/jwt.go b/tests/helpers/jwt.go new file mode 100644 index 0000000..a0134ab --- /dev/null +++ b/tests/helpers/jwt.go @@ -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", + }) +} diff --git a/tests/repository_test/repository_test.go b/tests/repository_test/repository_test.go index 6b69033..724a137 100644 --- a/tests/repository_test/repository_test.go +++ b/tests/repository_test/repository_test.go @@ -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) + }) +} diff --git a/utils/authenticator.go b/utils/authenticator.go new file mode 100644 index 0000000..193fcd4 --- /dev/null +++ b/utils/authenticator.go @@ -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 +} diff --git a/utils/jwt.go b/utils/jwt.go new file mode 100644 index 0000000..4584dee --- /dev/null +++ b/utils/jwt.go @@ -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 of : %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 of : %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 of : %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 of : %w", err) + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok || !token.Valid { + return "", errors.New("token is invalid on of ") + } + + data, ok := claims["id"].(string) + if !ok { + return "", errors.New("data is empty or not a string on of ") + } + + return data, nil +}