Compare commits

...

2 Commits

Author SHA1 Message Date
Pasha
2dc837be38 update with pagination 2024-11-15 14:21:04 +03:00
c4f7c33c03 test bp 2024-03-03 23:18:42 +03:00
87 changed files with 1070 additions and 6500 deletions

65
.env

@ -1,65 +0,0 @@
# General application settings
APP_NAME=codeword
HTTP_HOST="localhost"
HTTP_PORT="8080"
# MongoDB settings
MONGO_HOST="127.0.0.1"
MONGO_PORT="27020"
MONGO_USER="test"
MONGO_PASSWORD="test"
MONGO_DB="admin"
MONGO_AUTH="admin"
# Redis settings
REDIS_ADDR="localhost:6379"
REDIS_PASS="admin"
REDIS_DB=2
# Keys
PUBLIC_CURVE_KEY="-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAEbnIvjIMle4rqVol6K2XUqOxHy1KJoNoZdKJrRUPKL4=\n-----END PUBLIC KEY-----"
PRIVATE_CURVE_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZvODgWAnUIwQhd8de5oZhY48gc23EWfrfs\n-----END PRIVATE KEY-----"
JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2B
iw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikH
oKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAEC
gYAOphnVPXbk6lpYzdkLC1Xn5EOEuNfOLLURLxBnPWozZo26r/Mtahu/9mYhrYlv
PP8r6mxta3VIil8iOdZyOLa/4d1LPd+UehgEXIJEiYXLtn7RS5eUnoPuQxssfs1k
OWjdN8p6SzppleegFTvGRX4KM3cDLfSphOk8JuBCrpSSYQJBAOdqizTSrdKMTuVe
c7Jk1JOJkyFuFs+N5zeryyeFGH7IpRdWy0rkWMxIUAi8Ap1vYVBPHv4tDOo3sy5X
VLc/knkCQQCE62pg+0TmsrhO/2Pgog6MLBkzlzXYMRp/01HbmznwYF+ejfPnzLkz
hnUlxRUNK3lhXM/7H6oAjvqF2R72u/OPAkEAterkmdbQfEZ+MwNoEiH/lie9OLdx
SSI1VGdBYcTYN7qFRW6eizYstBJYkDU0HQ0Uw+we4hMKJwk4W0KdvxxDiQJAeqlB
V1QqBneBbK10PzVuFV8QtrJhJyxRVwrtbKq38iMNuqUnI4+ijXEUpJFWVvv6nKXo
7McQvEk12dU/JNTX8wJAOlAtSNjp9tVwpMpC0w2St1eKc1L2SknjeohA5ldoBz8sGeZsPhTU3eHSD1neAZXLKN5K68z3zFBr20ubY9nyLw==
-----END RSA PRIVATE KEY-----"
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAE=\n-----END PUBLIC KEY-----"
JWT_ISSUER="pena-auth-service"
JWT_AUDIENCE="pena"
# SIGN_SECRET="group"
SIGN_SECRET="secret"
# SMTP settings
SMTP_API_URL="https://api.smtp.bz/v1/smtp/send"
SMTP_HOST="connect.mailclient.bz"
SMTP_PORT="587"
SMTP_UNAME="kotilion.95@gmail.com"
SMTP_PASS="vWwbCSg4bf0p"
SMTP_API_KEY="P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev"
SMTP_SENDER="noreply@mailing.pena.digital"
# URL settings
DEFAULT_REDIRECTION_URL = "def.url"
AUTH_EXCHANGE_URL = "http://localhost:8000/auth/exchange"
DISCOUNT_ADDRESS = "http://CHANGEME:1234"
RECOVERY_URL = "http://127.0.0.1:8080/recover/"
# Kafka settings
KAFKA_BROKERS="localhost:9092"
KAFKA_TOPIC_TARIFF="tariffs"

@ -1,4 +1,4 @@
ProjectName: codeword
templateProjectName: codeword
Description: Service for exchanging codewords
Template:
@ -12,3 +12,10 @@ Modules:
- name: APP_NAME
type: string
default: "{{.ProjectName}}"
openapi:
model_save_path: ./internal/models
controller_save_path: ./internal/controllers
service_save_path: ./internal/service
repository_save_path: ./internal/repository
server_save_path: ./internal/server/http
db: postgres

@ -1,32 +0,0 @@
package main
import (
"codeword/internal/app"
"codeword/internal/initialize"
"context"
"fmt"
"go.uber.org/zap"
"os"
"os/signal"
"syscall"
)
func main() {
logger, err := zap.NewProduction()
if err != nil {
fmt.Printf("Failed to initialize logger: %v\n", err)
os.Exit(1)
}
config, err := initialize.LoadConfig()
if err != nil {
logger.Fatal("Failed to load config", zap.Error(err))
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
if err = app.Run(ctx, *config, logger); err != nil {
logger.Fatal("App exited with error", zap.Error(err))
}
}

38
cmd/main.go Normal file

@ -0,0 +1,38 @@
package main
import (
"codeword/internal/app"
"context"
"os/signal"
"syscall"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/caarlos0/env/v8"
)
func main() {
cfgLogger := zap.NewDevelopmentConfig()
cfgLogger.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
cfgLogger.EncoderConfig.ConsoleSeparator = " "
logger, err := cfgLogger.Build()
if err != nil {
panic(err)
}
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
var config app.Config
err = env.Parse(config)
if err != nil {
panic(err)
}
if err := app.Run(ctx, config, logger); err != nil {
logger.Fatal("Failed to run app", zap.Any("Error", err))
}
}

48
database.puml Normal file

@ -0,0 +1,48 @@
@startuml Database
map Usage {
key => **string**
time => **string** //date-time//
}
map Discount {
factor => **integer**
layer => **integer**
target => **string**
threshold => **integer**
}
map Filter {
active => **boolean**
text => **string**
}
map Privilege {
amount => **integer**
privilegeID => **string**
}
map PromoCode {
id => **string** //primary_key//
createdAt => **string** //date-time//
dueTo => **integer**
offLimit => **boolean**
codeword => **string**
outdated => **boolean**
activationCount => **integer**
bonus => **bonus**
delete => **boolean**
description => **string**
fastLinks => **[]string**
greetings => **string**
}
map Bonus {
discount => **discount**
privilege => **privilege**
}
Bonus::discount -> Discount
Bonus::privilege -> Privilege
@enduml

@ -1,28 +0,0 @@
version: '3.8'
services:
mongo:
image: mongo
ports:
- "27020:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=test
- MONGO_INITDB_ROOT_PASSWORD=test
- MONGO_INITDB_AUTH_MECHANISM=SCRAM-SHA-1
volumes:
- mongo_data:/data/db
redis:
image: redis:latest
ports:
- "6379:6379"
environment:
- REDIS_PASSWORD=admin
- REDIS_DB=2
command: [ "redis-server", "--requirepass", "admin", "--databases", "16", "--maxmemory", "2gb", "--maxmemory-policy", "allkeys-lru" ]
volumes:
- redis_data:/data
volumes:
mongo_data:
redis_data:

24
go.mod

@ -1,6 +1,8 @@
module codeword
go 1.21
go 1.21.0
toolchain go1.23.1
require (
github.com/caarlos0/env/v8 v8.0.0
@ -10,10 +12,10 @@ require (
github.com/joho/godotenv v1.5.1
github.com/pioz/faker v1.7.3
github.com/rs/xid v1.5.0
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/twmb/franz-go v1.15.4
go.mongodb.org/mongo-driver v1.13.1
go.uber.org/zap v1.26.0
go.uber.org/zap v1.27.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
@ -25,18 +27,27 @@ require (
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/getkin/kin-openapi v0.122.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // 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/invopop/yaml v0.3.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mailru/easyjson v0.7.7 // 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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/perimeterx/marshmallow v1.1.5 // 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/rs/zerolog v1.33.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
@ -49,10 +60,11 @@ require (
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/sys v0.17.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/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
penahub.gitlab.yandexcloud.net/pena-services/blueprint v0.0.0-20241114114746-100696f35c90 // indirect
)

36
go.sum

@ -4,6 +4,8 @@ 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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -11,8 +13,15 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10=
github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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=
@ -28,24 +37,35 @@ 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/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
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/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
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=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@ -54,20 +74,28 @@ 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/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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=
@ -95,6 +123,8 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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=
@ -119,8 +149,11 @@ 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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.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=
@ -151,6 +184,7 @@ google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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=
@ -160,3 +194,5 @@ 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=
penahub.gitlab.yandexcloud.net/pena-services/blueprint v0.0.0-20241114114746-100696f35c90 h1:sSDKSdjexf5e6BOOxcuODf9FrctQGtDPb/udma5nq18=
penahub.gitlab.yandexcloud.net/pena-services/blueprint v0.0.0-20241114114746-100696f35c90/go.mod h1:NJEEmcYZDD5IzHOMvGgCWwtE0T6nNnaCBEQKPRapHts=

@ -1,66 +0,0 @@
package client
import (
"codeword/internal/models"
"encoding/json"
"fmt"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
type AuthClientDeps struct {
AuthUrl string
FiberClient *fiber.Client
Logger *zap.Logger
}
type AuthClient struct {
deps AuthClientDeps
}
func NewAuthClient(deps AuthClientDeps) *AuthClient {
if deps.FiberClient == nil {
deps.FiberClient = fiber.AcquireClient()
}
return &AuthClient{
deps: deps,
}
}
func (a *AuthClient) RefreshAuthToken(userID, signature string) (*models.RefreshResponse, error) {
body := models.AuthRequestBody{
UserID: userID,
Signature: signature,
}
bodyBytes, err := json.Marshal(body)
if err != nil {
a.deps.Logger.Error("Failed to encode request body", zap.Error(err))
return nil, err
}
agent := a.deps.FiberClient.Post(a.deps.AuthUrl)
agent.Set("Content-Type", "application/json").Body(bodyBytes)
statusCode, resBody, errs := agent.Bytes()
if len(errs) > 0 {
for _, err := range errs {
a.deps.Logger.Error("Error in exchange auth token request", zap.Error(err))
}
return nil, fmt.Errorf("request failed: %v", errs)
}
if statusCode != fiber.StatusOK {
errorMessage := fmt.Sprintf("received an incorrect response from the authentication service: %d", statusCode)
a.deps.Logger.Error(errorMessage, zap.Int("status", statusCode))
return nil, fmt.Errorf(errorMessage)
}
var tokens models.RefreshResponse
if err := json.Unmarshal(resBody, &tokens); err != nil {
a.deps.Logger.Error("failed to unmarshal auth service response", zap.Error(err))
return nil, err
}
return &tokens, nil
}

@ -1,84 +0,0 @@
package client
import (
"bytes"
"fmt"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"mime/multipart"
)
type RecoveryEmailSenderDeps struct {
SmtpApiUrl string
SmtpHost string
SmtpPort string
SmtpSender string
Username string
Password string
ApiKey string
FiberClient *fiber.Client
Logger *zap.Logger
RecoveryUrl string
}
type RecoveryEmailSender struct {
deps RecoveryEmailSenderDeps
}
func NewRecoveryEmailSender(deps RecoveryEmailSenderDeps) *RecoveryEmailSender {
if deps.FiberClient == nil {
deps.FiberClient = fiber.AcquireClient()
}
return &RecoveryEmailSender{
deps: deps,
}
}
func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature string) error {
url := r.deps.SmtpApiUrl
fmt.Println(email, signature)
message := r.deps.RecoveryUrl + signature
form := new(bytes.Buffer)
writer := multipart.NewWriter(form)
defer writer.Close()
fields := map[string]string{
"from": r.deps.SmtpSender,
"to": email,
"subject": "Восстановление доступа",
"html": message,
}
for key, value := range fields {
if err := writer.WriteField(key, value); err != nil {
return err
}
}
if err := writer.Close(); err != nil {
return err
}
req := r.deps.FiberClient.Post(url).Body(form.Bytes()).ContentType(writer.FormDataContentType())
if r.deps.ApiKey != "" {
req.Set("Authorization", r.deps.ApiKey)
}
statusCode, body, errs := req.Bytes()
if errs != nil {
r.deps.Logger.Error("Error sending request", zap.Error(errs[0]))
return errs[0]
}
if statusCode != fiber.StatusOK {
err := fmt.Errorf("the SMTP service returned an error: %s Response body: %s", statusCode, body)
r.deps.Logger.Error("Error sending email", zap.Error(err))
return err
}
//r.deps.Logger.Info("Recovery email sent", zap.String("email", email))
return nil
}

@ -1,159 +1,84 @@
package app
import (
"codeword/internal/controller/promocode"
"codeword/internal/controller/recovery"
"codeword/internal/initialize"
"codeword/internal/controllers"
"codeword/internal/repository"
httpserver "codeword/internal/server/http"
"codeword/internal/services"
"codeword/internal/worker/purge_worker"
"codeword/internal/worker/recovery_worker"
"codeword/pkg/closer"
"codeword/utils"
"codeword/internal/server/http"
"codeword/internal/service"
"context"
"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 {
type Config struct {
AppName string `env:"APP_NAME"`
}
func Run(ctx context.Context, config Config, logger *zap.Logger) error {
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))
logger.Info("App started", zap.Any("config", config))
ctx, cancel := context.WithCancel(ctx)
defer cancel()
shutdownGroup := closer.NewCloserGroup()
// Инициализация репозиториев
mdb, err := initialize.MongoDB(ctx, cfg)
if err != nil {
logger.Error("Failed to initialize MongoDB", zap.Error(err))
return err
}
promocodeRepository := repository.NewPromocodeRepository()
if err = initialize.InitDatabaseIndexes(ctx, mdb, logger); err != nil {
logger.Error("Failed to initialize db indexes", zap.Error(err))
return err
}
recoverRepository := repository.NewRecoverRepository()
kafkaTariffClient, err := kgo.NewClient(
kgo.SeedBrokers(cfg.KafkaBrokers),
kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()),
kgo.DefaultProduceTopic(cfg.KafkaTopic),
)
if err != nil {
return err
}
statsRepository := repository.NewStatsRepository()
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
}
promocodeService := service.NewPromocodeService(promocodeRepository)
brokers := initialize.NewBrokers(initialize.BrokersDeps{
Logger: logger,
TariffClient: kafkaTariffClient,
Topic: cfg.KafkaTopic,
})
recoverService := service.NewRecoverService(recoverRepository)
rdb, err := initialize.Redis(ctx, cfg)
if err != nil {
logger.Error("failed to connect to redis db", zap.Error(err))
return err
}
statsService := service.NewStatsService(statsRepository)
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")})
promocodeController := controllers.NewPromocodeController(promocodeService)
recoveryEmailSender := initialize.RecoveryEmailSender(cfg, logger)
authClient := initialize.AuthClient(cfg, logger)
recoverController := controllers.NewRecoverController(recoverService)
recoveryService := services.NewRecoveryService(services.Deps{
Logger: logger,
CodewordRepository: codewordRepo,
UserRepository: userRepo,
Encrypt: encrypt,
AuthClient: authClient,
})
statsController := controllers.NewStatsController(statsService)
promoService := services.NewPromoCodeService(services.PromoDeps{
Logger: logger,
PromoCodeRepo: promoCodeRepo,
StatsRepo: statsRepo,
Kafka: brokers.TariffProducer,
DiscountClient: discountRpcClient,
})
// Создание сервера
server := http.NewServer(http.ServerConfig{
Controllers: []http.Controller{
jwtUtil := utils.NewJWT(&cfg)
authMiddleware := utils.NewAuthenticator(jwtUtil)
promocodeController,
recoveryController := recovery.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL)
promoCodeController := promocode.NewPromoCodeController(promocode.Deps{Logger: logger, PromoCodeService: promoService, AuthMiddleware: authMiddleware})
recoverController,
recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{
Logger: logger,
Redis: rdb,
EmailSender: recoveryEmailSender,
Mongo: mdb.Collection("codeword"),
})
purgeWC := purge_worker.NewPurgeWC(purge_worker.Deps{
Logger: logger,
Mongo: mdb.Collection("codeword"),
})
go recoveryWC.Start(ctx)
go purgeWC.Start(ctx)
server := httpserver.NewServer(httpserver.ServerConfig{
Logger: logger,
Controllers: []httpserver.Controller{recoveryController, promoCodeController},
statsController,
},
})
go func() {
if err := server.Start(cfg.HTTPHost + ":" + cfg.HTTPPort); err != nil {
err := server.Start("Host + : + Port")
if 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()
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("App shutting down gracefully")
//TODO
// Остановка сервера
logger.Info("Application has stopped")
return nil
}

@ -1,206 +0,0 @@
package promocode
import (
"codeword/internal/models"
"codeword/internal/repository"
"codeword/internal/services"
"errors"
"github.com/gofiber/fiber/v2"
"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(deps Deps) *PromoCodeController {
return &PromoCodeController{
logger: deps.Logger,
promoCodeService: deps.PromoCodeService,
authMiddleware: deps.AuthMiddleware,
}
}
func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error {
var req models.PromoCode
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"})
}
createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &req)
if err != nil {
p.logger.Error("Failed to create promocode", zap.Error(err))
if errors.Is(err, repository.ErrDuplicateCodeword) {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Duplicate Codeword"})
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
}
return c.Status(fiber.StatusOK).JSON(createdPromoCode)
}
func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error {
var req models.ReqEditPromoCode
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
if req.ID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "promocode ID is required"})
}
editedPromoCode, err := p.promoCodeService.EditPromoCode(c.Context(), &req)
if err != nil {
p.logger.Error("Failed to edit promocode", 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.StatusOK).JSON(editedPromoCode)
}
func (p *PromoCodeController) GetList(c *fiber.Ctx) error {
var req models.GetPromoCodesListReq
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
promoCodes, count, err := p.promoCodeService.GetPromoCodesList(c.Context(), &req)
if err != nil {
p.logger.Error("Failed to retrieve promocode list", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
}
resp := models.GetPromoCodesListResp{
Count: count,
Items: promoCodes,
}
return c.Status(fiber.StatusOK).JSON(resp)
}
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 == "" && req.FastLink == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword or fastlink is required"})
}
greetings, err := p.promoCodeService.ActivatePromo(c.Context(), &req, userID)
if err != nil {
p.logger.Error("Failed to activate promocode", zap.Error(err))
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.StatusOK).JSON(models.ActivateResp{Greetings: greetings})
}
func (p *PromoCodeController) Delete(c *fiber.Ctx) error {
promoCodeID := c.Params("promocodeID")
if promoCodeID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PromoCode ID is required"})
}
err := p.promoCodeService.DeletePromoCode(c.Context(), promoCodeID)
if err != nil {
p.logger.Error("Failed to delete promocode", 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.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)
}

@ -1,18 +0,0 @@
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"
}

@ -1,134 +0,0 @@
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"
)
type RecoveryController struct {
logger *zap.Logger
service *services.RecoveryService
defaultURL string
}
func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService, defaultRedirectionURL string) *RecoveryController {
return &RecoveryController{
logger: logger,
service: service,
defaultURL: defaultRedirectionURL,
}
}
func (r *RecoveryController) HandleLiveness(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
}
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)
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"})
}
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"})
}
key, err := r.service.GenerateKey()
if err != nil {
r.logger.Error("Failed to generate key", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
}
signUrl := req.RedirectionURL
sign := base64.URLEncoding.EncodeToString(key)
id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{UserID: user.ID.Hex(), Email: user.Email, Key: sign, Url: signUrl})
if err != nil {
r.logger.Error("Failed to store recovery record", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
}
signWithID := sign + 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{"message": "Recovery email sent successfully"})
}
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
sign := c.Params("sign")
record, err := r.service.GetRecoveryRecord(c.Context(), sign)
if err != nil {
r.logger.Error("Failed to get recovery record", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
}
if time.Since(record.CreatedAt) > 15*time.Minute {
r.logger.Error("Recovery link expired", zap.String("signature", sign))
return c.Status(fiber.StatusNotAcceptable).JSON(fiber.Map{"error": "Recovery link expired"})
}
tokens, err := r.service.ExchangeForTokens(record.UserID, record.Sign)
if err != nil {
r.logger.Error("Failed to exchange recovery link for tokens", zap.Error(err))
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
}
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"])
}

@ -1,14 +0,0 @@
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 ""
}

@ -0,0 +1,135 @@
package controllers
import (
"codeword/internal/models"
"codeword/internal/service"
"github.com/gofiber/fiber/v2"
)
type PromocodeController struct {
PromocodeService *service.PromocodeService
}
func NewPromocodeController(service *service.PromocodeService) *PromocodeController {
return &PromocodeController{
PromocodeService: service,
}
}
func (c *PromocodeController) Register(router fiber.Router) {
router.Post("/promocode/activate", c.Activate)
router.Post("/promocode/getList", c.Getlist)
router.Delete("/promocode/{promocodeID}", c.Delete)
router.Post("/promocode/create", c.Createpromocode)
router.Put("/promocode/edit", c.Editpromocode)
router.Post("/promocode/fastlink", c.Createfastlink)
}
func (c *PromocodeController) Name() string {
return ""
}
func (c *PromocodeController) Activate(ctx *fiber.Ctx) error {
// Обработчик для метода Activate
var request models.ActivateReq
if err := ctx.BodyParser(&request); err != nil {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
response, err := c.PromocodeService.Activate(ctx.Context(), &request)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.Status(fiber.StatusOK).JSON(response)
}
func (c *PromocodeController) Getlist(ctx *fiber.Ctx) error {
// Обработчик для метода Getlist
var request models.GetPromoCodesListReq
if err := ctx.BodyParser(&request); err != nil {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
response, err := c.PromocodeService.Getlist(ctx.Context(), &request)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.Status(fiber.StatusOK).JSON(response)
}
func (c *PromocodeController) Delete(ctx *fiber.Ctx) error {
// Обработчик для метода Delete
err := c.PromocodeService.Delete(ctx.Context())
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.SendStatus(fiber.StatusOK)
}
func (c *PromocodeController) Createpromocode(ctx *fiber.Ctx) error {
// Обработчик для метода Createpromocode
var request models.PromoCodeReq
if err := ctx.BodyParser(&request); err != nil {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
response, err := c.PromocodeService.Createpromocode(ctx.Context(), &request)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.Status(fiber.StatusOK).JSON(response)
}
func (c *PromocodeController) Editpromocode(ctx *fiber.Ctx) error {
// Обработчик для метода Editpromocode
var request models.EditPromoCodeReq
if err := ctx.BodyParser(&request); err != nil {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
response, err := c.PromocodeService.Editpromocode(ctx.Context(), &request)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.Status(fiber.StatusOK).JSON(response)
}
func (c *PromocodeController) Createfastlink(ctx *fiber.Ctx) error {
// Обработчик для метода Createfastlink
var request models.CreateFastLinkReq
if err := ctx.BodyParser(&request); err != nil {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
response, err := c.PromocodeService.Createfastlink(ctx.Context(), &request)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.Status(fiber.StatusOK).JSON(response)
}

@ -0,0 +1,87 @@
package controllers
import (
"codeword/internal/models"
"codeword/internal/service"
"github.com/gofiber/fiber/v2"
)
type RecoverController struct {
RecoverService *service.RecoverService
}
func NewRecoverController(service *service.RecoverService) *RecoverController {
return &RecoverController{
RecoverService: service,
}
}
func (c *RecoverController) Register(router fiber.Router) {
router.Get("/liveness", c.Liveness)
router.Post("/recover", c.Recovery)
router.Get("/recover/{sign}", c.Recoverylink)
router.Get("/readiness", c.Readiness)
}
func (c *RecoverController) Name() string {
return ""
}
func (c *RecoverController) Liveness(ctx *fiber.Ctx) error {
// Обработчик для метода Liveness
err := c.RecoverService.Liveness(ctx.Context())
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.SendStatus(fiber.StatusOK)
}
func (c *RecoverController) Recovery(ctx *fiber.Ctx) error {
// Обработчик для метода Recovery
var request models.RecoveryReq
if err := ctx.BodyParser(&request); err != nil {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
err := c.RecoverService.Recovery(ctx.Context(), &request)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.SendStatus(fiber.StatusOK)
}
func (c *RecoverController) Recoverylink(ctx *fiber.Ctx) error {
// Обработчик для метода Recoverylink
err := c.RecoverService.Recoverylink(ctx.Context())
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.SendStatus(fiber.StatusOK)
}
func (c *RecoverController) Readiness(ctx *fiber.Ctx) error {
// Обработчик для метода Readiness
err := c.RecoverService.Readiness(ctx.Context())
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.SendStatus(fiber.StatusOK)
}

@ -0,0 +1,45 @@
package controllers
import (
"codeword/internal/models"
"codeword/internal/service"
"github.com/gofiber/fiber/v2"
)
type StatsController struct {
StatsService *service.StatsService
}
func NewStatsController(service *service.StatsService) *StatsController {
return &StatsController{
StatsService: service,
}
}
func (c *StatsController) Register(router fiber.Router) {
router.Get("/promocode/stats", c.Getstats)
}
func (c *StatsController) Name() string {
return ""
}
func (c *StatsController) Getstats(ctx *fiber.Ctx) error {
// Обработчик для метода Getstats
var request models.PromoCodeStatsReq
if err := ctx.BodyParser(&request); err != nil {
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
}
response, err := c.StatsService.Getstats(ctx.Context(), &request)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}
return ctx.Status(fiber.StatusOK).JSON(response)
}

@ -1,3 +0,0 @@
package errors
// пока не нужен

@ -1,30 +0,0 @@
package initialize
import (
"codeword/internal/adapters/client"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
func RecoveryEmailSender(cfg Config, logger *zap.Logger) *client.RecoveryEmailSender {
return client.NewRecoveryEmailSender(client.RecoveryEmailSenderDeps{
SmtpApiUrl: cfg.SmtpApiUrl,
SmtpHost: cfg.SmtpHost,
SmtpPort: cfg.SmtpPort,
SmtpSender: cfg.SmtpSender,
Username: cfg.SmtpUsername,
Password: cfg.SmtpPassword,
ApiKey: cfg.SmtpApiKey,
FiberClient: &fiber.Client{},
Logger: logger,
RecoveryUrl: cfg.RecoveryUrl,
})
}
func AuthClient(cfg Config, logger *zap.Logger) *client.AuthClient {
return client.NewAuthClient(client.AuthClientDeps{
AuthUrl: cfg.AuthURL,
Logger: logger,
FiberClient: &fiber.Client{},
})
}

@ -1,53 +0,0 @@
package initialize
import (
"github.com/caarlos0/env/v8"
"github.com/joho/godotenv"
"log"
)
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"`
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) {
if err := godotenv.Load(); err != nil {
log.Print("No .env file found")
}
var config Config
if err := env.Parse(&config); err != nil {
return nil, err
}
return &config, nil
}

@ -1,13 +0,0 @@
package initialize
import (
"codeword/internal/utils/encrypt"
)
func Encrypt(cfg Config) *encrypt.Encrypt {
return encrypt.New(&encrypt.EncryptDeps{
PublicKey: cfg.PublicCurveKey,
PrivateKey: cfg.PrivateCurveKey,
SignSecret: cfg.SignSecret,
})
}

@ -1,26 +0,0 @@
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
}

@ -1,27 +0,0 @@
package initialize
import (
"codeword/internal/kafka/tariff"
"github.com/twmb/franz-go/pkg/kgo"
"go.uber.org/zap"
)
type BrokersDeps struct {
Logger *zap.Logger
TariffClient *kgo.Client
Topic string
}
type Brokers struct {
TariffProducer *tariff.Producer
}
func NewBrokers(deps BrokersDeps) *Brokers {
return &Brokers{
TariffProducer: tariff.NewProducer(tariff.ProducerDeps{
Logger: deps.Logger,
Client: deps.TariffClient,
Topic: deps.Topic,
}),
}
}

@ -1,50 +0,0 @@
package initialize
import (
"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{
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(newCtx, mongoDeps)
if err != nil {
return nil, err
}
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
}

@ -1,21 +0,0 @@
package initialize
import (
"context"
"github.com/go-redis/redis/v8"
)
func Redis(ctx context.Context, cfg Config) (*redis.Client, error) {
rdb := redis.NewClient(&redis.Options{
Addr: cfg.RedisAddr,
Password: cfg.RedisPassword,
DB: cfg.RedisDB,
})
status := rdb.Ping(ctx)
if err := status.Err(); err != nil {
return nil, err
}
return rdb, nil
}

@ -1,56 +0,0 @@
package tariff
import (
"codeword/internal/models"
"codeword/internal/utils/transfer"
"context"
"github.com/twmb/franz-go/pkg/kgo"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
"log"
)
type ProducerDeps struct {
Logger *zap.Logger
Client *kgo.Client
Topic string
}
type Producer struct {
logger *zap.Logger
client *kgo.Client
topic string
}
func NewProducer(deps ProducerDeps) *Producer {
if deps.Logger == nil {
log.Panicln("logger is nil on <NewTariffProducer>")
}
if deps.Client == nil {
log.Panicln("Kafka client is nil on <NewTariffProducer>")
}
return &Producer{
logger: deps.Logger,
client: deps.Client,
topic: deps.Topic,
}
}
func (p *Producer) Send(ctx context.Context, userID string, tariff *models.Tariff) error {
bytes, err := proto.Marshal(transfer.TariffModelToProtoMessage(userID, tariff))
if err != nil {
p.logger.Error("failed to marshal tariff model", zap.Error(err))
return err
}
// упростил, возможно зря, но теперь возвращаем одну ошибку, просто прерываем цикл при первой встретившейся ошибке
err = p.client.ProduceSync(ctx, &kgo.Record{Topic: p.topic, Value: bytes}).FirstErr()
if err != nil {
p.logger.Error("failed to send tariff to Kafka", zap.Error(err))
return err
}
return nil
}

@ -0,0 +1,8 @@
package models
type ActivateReq struct {
/* - Кодовое слово для активации промокода*/
Codeword string `json:"codeword"`
/* - Быстрая ссылка для активации промокода*/
Fastlink string `json:"fastLink"`
}

@ -0,0 +1,6 @@
package models
type ActivateResp struct {
/* - Слово успешной активации промокода*/
Greetings string `json:"greetings"`
}

@ -1,14 +0,0 @@
package models
type AuthRequestBody struct {
UserID string `json:"userId"`
Signature string `json:"signature"`
}
type RefreshResponse struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
}
const AuthJWTDecodedUserIDKey = "userID"
const AuthJWTDecodedAccessTokenKey = "access-token"

@ -1,79 +1,8 @@
package models
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)
type PromoCode struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Codeword string `json:"codeword" bson:"codeword"` // то, что будет вводить пользователь, чтобы получить плюшки
Description string `json:"description" bson:"description"` // описание, необходимое менеджеру в админке
Greetings string `json:"greetings" bson:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода
DueTo int64 `json:"dueTo" bson:"dueTo"` // таймштамп времени окончания работы активации промокода
ActivationCount int64 `json:"activationCount" bson:"activationCount"` // предел количества активаций промокода
Bonus struct {
Privilege struct {
PrivilegeID string `json:"privilegeID" bson:"privilegeID"` // айдишник привилегии, которая будет выдаваться
Amount uint64 `json:"amount" bson:"amount"` // количество
} `json:"privilege" bson:"privilege"`
Discount struct {
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"` // граничное значение, при пересечении которого применяется эта скидка
} `json:"discount" bson:"discount"`
} `json:"bonus" bson:"bonus"`
Outdated bool `json:"outdated" bson:"outdated"`
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 {
ID string `json:"id" bson:"_id"` //айдишник промокода, который обновляем
Description *string `json:"description,omitempty" bson:"description"` // описание, необходимое менеджеру в админке
Greetings *string `json:"greetings,omitempty" bson:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода
DueTo *int64 `json:"dueTo,omitempty" bson:"dueTo"` // таймштамп времени окончания работы активации промокода
ActivationCount *int64 `json:"activationCount,omitempty" bson:"activationCount"` // предел количества активаций промокода
Delete *bool `json:"delete,omitempty" bson:"delete"`
}
type GetPromoCodesListReqFilter struct {
Text string `json:"text"` // полнотекстовый поиск пo Codeword, Decription, Greetings полям
Active bool `json:"active"` // если true, то выбирать deleted==false && outdated== false && offlimit == false
}
type GetPromoCodesListReq struct {
Page int `json:"page"` //номер страницы выборки. начинается с 0. по сути, skip для выборки из mongodb
Limit int `json:"limit"` //размер страницы выборки. больше 10, меньше 250. отвечает за skip = page*limit, и за limit
Filter GetPromoCodesListReqFilter `json:"filter"`
}
type GetPromoCodesListResp struct {
Count int64 `json:"count"` // количество в выборке всего
Items []PromoCode `json:"items"` // "страница" промокодов
}
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"`
type Bonus struct {
/* - Скидка*/
Discount Discount `json:"discount"`
/* - Привилегия*/
Privilege Privilege `json:"privilege"`
}

@ -0,0 +1,6 @@
package models
type CreateFastLinkReq struct {
/* - ID промокода, для которого нужно создать быструю ссылку*/
ID string `json:"id"`
}

@ -0,0 +1,6 @@
package models
type CreateFastLinkResp struct {
/* - Быстрая ссылка для активации промокода*/
Fastlink string `json:"fastlink"`
}

@ -1,15 +0,0 @@
package models
type StoreRecDeps struct {
UserID string
Email string
Key string
Url string
}
type RecEmailDeps struct {
UserID string
Email string
SignWithID string
ID string
}

@ -0,0 +1,12 @@
package models
type Discount struct {
/* - Цель скидки*/
Target string `json:"target"`
/* - Порог скидки*/
Threshold int `json:"threshold"`
/* - Множитель скидки*/
Factor int `json:"factor"`
/* - Уровень скидки*/
Layer int `json:"layer"`
}

@ -0,0 +1,16 @@
package models
type EditPromoCodeReq struct {
/* - Приветственное сообщение после активации промокода*/
Greetings string `json:"Greetings"`
/* - Идентификатор промокода, который требуется обновить*/
ID string `json:"ID"`
/* - Количество активаций промокода*/
Activationcount int `json:"ActivationCount"`
/* - Флаг удаления промокода*/
Delete bool `json:"Delete"`
/* - Описание промокода*/
Description string `json:"Description"`
/* - Дата окончания промокода в формате Unix time*/
Dueto int `json:"DueTo"`
}

@ -0,0 +1,8 @@
package models
type Filter struct {
/* - Флаг для фильтрации активных промокодов*/
Active bool `json:"active"`
/* - Текстовый фильтр для поиска промокодов*/
Text string `json:"text"`
}

@ -0,0 +1,10 @@
package models
type GetPromoCodesListReq struct {
/* - Номер страницы*/
Page int `json:"page"`
/* - */
Filter Filter `json:"filter"`
/* - Максимальное количество элементов на странице*/
Limit int `json:"limit"`
}

@ -0,0 +1,8 @@
package models
type GetPromoCodesListResp struct {
/* - Общее количество промокодов*/
Count int `json:"count"`
/* - */
Items []PromoCode `json:"items"`
}

@ -0,0 +1,8 @@
package models
type Privilege struct {
/* - Идентификатор привилегии*/
Privilegeid string `json:"privilegeID"`
/* - Количество привилегии*/
Amount int `json:"amount"`
}

@ -0,0 +1,28 @@
package models
type PromoCode struct {
/* - Идентификатор промокода*/
ID string `json:"id"`
/* - Бонус, предоставляемый с промокодом*/
Bonus Bonus `json:"bonus"`
/* - Флаг*/
Delete bool `json:"delete"`
/* - Описание промокода*/
Description string `json:"description"`
/* - Список быстрых ссылок для активации промокода*/
Fastlinks []string `json:"fastLinks"`
/* - Приветственное сообщение после активации промокода*/
Greetings string `json:"greetings"`
/* - Количество активаций промокода*/
Activationcount int `json:"activationCount"`
/* - Флаг*/
Outdated bool `json:"outdated"`
/* - Кодовое слово для активации промокода*/
Codeword string `json:"codeword"`
/* - Дата и время создания промокода*/
Createdat string `json:"createdAt"`
/* - Дата истечения действия промокода в формате Unix time*/
Dueto int `json:"dueTo"`
/* - Флаг*/
Offlimit bool `json:"offLimit"`
}

@ -0,0 +1,18 @@
package models
type PromoCodeReq struct {
/* - Бонус*/
Bonus Bonus `json:"bonus"`
/* - Кодовое слово для активации промокода*/
Codeword string `json:"codeword"`
/* - Описание промокода*/
Description string `json:"description"`
/* - Дата истечения действия промокода в формате Unix time*/
Dueto int `json:"dueTo"`
/* - Список быстрых ссылок для активации промокода*/
Fastlinks []string `json:"fastLinks"`
/* - Приветственное сообщение после активации промокода*/
Greetings string `json:"greetings"`
/* - Количество активаций промокода*/
Activationcount int `json:"activationCount"`
}

@ -0,0 +1,6 @@
package models
type PromoCodeStatsReq struct {
/* - */
Promocodeid string `json:"promoCodeID"`
}

@ -0,0 +1,10 @@
package models
type PromoCodeStatsResp struct {
/* - Идентификатор промокода*/
ID string `json:"id"`
/* - Количество использований промокода*/
Usagecount int `json:"usageCount"`
/* - Карта использования промокода*/
Usagemap Usagemap `json:"usageMap"`
}

@ -0,0 +1,8 @@
package models
type RecoveryReq struct {
/* - Электронная почта, на которую нужно отправить инструкции по восстановлению*/
Email string `json:"email"`
/* - URL-адрес, на который перенаправляется пользователь*/
Redirectionurl string `json:"redirectionURL"`
}

@ -1,46 +0,0 @@
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,
}
)

8
internal/models/usage.go Normal file

@ -0,0 +1,8 @@
package models
type Usage struct {
/* - fastlink или codeword в зависимости от того что применялось*/
Key string `json:"key"`
/* - Время использования промокода*/
Time string `json:"time"`
}

@ -1,46 +0,0 @@
package models
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)
type User struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Login string `bson:"login,omitempty"`
Email string `bson:"email,omitempty"`
Password string `bson:"password,omitempty"`
PhoneNumber string `bson:"phoneNumber,omitempty"`
IsDeleted bool `bson:"isDeleted,omitempty"`
CreatedAt time.Time `bson:"createdAt,omitempty"`
UpdatedAt time.Time `bson:"updatedAt,omitempty"`
DeletedAt *time.Time `bson:"deletedAt,omitempty"`
}
type RestoreRequest struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
CreatedAt time.Time `bson:"created_at,omitempty"`
Sign string `bson:"sign,omitempty"`
SignUrl string `bson:"sign_url,omitempty"`
SignID string `bson:"sign_id"`
Email string `bson:"email,omitempty"`
UserID string `bson:"user_id,omitempty"`
Sent bool `bson:"sent"`
SentAt time.Time `bson:"sent_at"`
}
type RecoveryRecord struct {
ID string
UserID string
Email string
Key string
}
type RecoveryRequest struct {
Email string `json:"email"`
RedirectionURL string `json:"redirectionURL"`
}
type RecoveryLinkRequest struct {
Sign string `json:"sign"`
}

@ -1,314 +0,0 @@
// 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
}

@ -1,192 +0,0 @@
// 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
}

File diff suppressed because it is too large Load Diff

@ -1,524 +0,0 @@
// 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
}

@ -1,404 +0,0 @@
// 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",
}

@ -1,100 +0,0 @@
package repository
import (
"codeword/internal/models"
"context"
"encoding/json"
"github.com/go-redis/redis/v8"
"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/readpref"
"time"
)
type CodewordRepository struct {
mdb *mongo.Collection
rdb *redis.Client
}
func NewCodewordRepository(deps Deps) *CodewordRepository {
return &CodewordRepository{mdb: deps.Mdb, rdb: deps.Rdb}
}
// сохраняем полученные данные о пользователе и подписи в бд
func (r *CodewordRepository) StoreRecoveryRecord(ctx context.Context, deps models.StoreRecDeps) (string, error) {
newID := primitive.NewObjectID()
signID := deps.Key + newID.Hex()
record := models.RestoreRequest{
ID: newID,
UserID: deps.UserID,
Email: deps.Email,
Sign: deps.Key,
SignUrl: deps.Url,
SignID: signID,
CreatedAt: time.Now(),
}
_, err := r.mdb.InsertOne(ctx, record)
if err != nil {
return "", err
}
return newID.Hex(), nil
}
// добавляем в очередь данные для отправки на почту в редис
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,
Email: deps.Email,
Key: deps.SignWithID,
}
taskBytes, err := json.Marshal(task)
if err != nil {
return err
}
return r.rdb.Set(ctx, "email:task:"+deps.Email, taskBytes, ttl).Err()
}
// получаем данные юзера по подписи
func (r *CodewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) {
var restoreRequest models.RestoreRequest
filter := bson.M{"sign_id": key}
err := r.mdb.FindOne(ctx, filter).Decode(&restoreRequest)
if err != nil {
return nil, err
}
return &restoreRequest, nil
}
// пингует в монгу чтобы проверить подключение
func (r *CodewordRepository) Ping(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := r.mdb.Database().Client().Ping(ctx, readpref.Primary()); err != nil {
return err
}
if err := r.rdb.Ping(ctx).Err(); err != nil {
return err
}
return nil
}

@ -1,13 +0,0 @@
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")
)

@ -0,0 +1,74 @@
package repository
import (
"codeword/internal/models"
"context"
"database/sql"
)
type PromocodeRepository struct {
queries *sqlcgen.Queries
pool *sql.DB
}
type PromocodeDeps struct {
Queries *sqlcgen.Queries
Pool *sql.DB
}
func NewPromocodeRepository(deps PromocodeDeps) *PromocodeRepository {
return &PromocodeRepository{
queries: deps.Queries,
pool: deps.Pool,
}
}
func (r *PromocodeRepository) Activate(ctx context.Context, request *models.ActivateReq) (*models.ActivateResp, error) {
//TODO:IMPLEMENT ME
return &models.ActivateResp{}, nil
}
func (r *PromocodeRepository) Getlist(ctx context.Context, request *models.GetPromoCodesListReq) (*models.GetPromoCodesListResp, error) {
rows, err := r.queries.Getlist(ctx, sqlcgen.GetlistParams{
Page: request.Page,
Limit: request.Size,
})
if err != nil {
return nil, err
}
return &models.GetPromoCodesListResp{}, nil
}
func (r *PromocodeRepository) Delete(ctx context.Context) error {
//TODO:IMPLEMENT ME
return nil
}
func (r *PromocodeRepository) Createpromocode(ctx context.Context, request *models.PromoCodeReq) (*models.PromoCode, error) {
//TODO:IMPLEMENT ME
return &models.PromoCode{}, nil
}
func (r *PromocodeRepository) Editpromocode(ctx context.Context, request *models.EditPromoCodeReq) (*models.PromoCode, error) {
//TODO:IMPLEMENT ME
return &models.PromoCode{}, nil
}
func (r *PromocodeRepository) Createfastlink(ctx context.Context, request *models.CreateFastLinkReq) (*models.CreateFastLinkResp, error) {
//TODO:IMPLEMENT ME
return &models.CreateFastLinkResp{}, nil
}

@ -1,272 +0,0 @@
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 countResult struct {
count int64
err error
}
type PromoCodeRepository struct {
mdb *mongo.Collection
}
func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository {
return &PromoCodeRepository{mdb: mdb}
}
func InitPromoCodeIndexes(ctx context.Context, mdb *mongo.Collection) error {
uniqueIndexModel := mongo.IndexModel{
Keys: bson.D{
{Key: "codeword", Value: 1},
{Key: "delete", Value: 1},
},
Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"delete": false}),
}
_, err := mdb.Indexes().CreateOne(ctx, uniqueIndexModel)
if err != nil {
return err
}
textIndexModel := mongo.IndexModel{
Keys: bson.D{
{Key: "codeword", Value: "text"},
{Key: "description", Value: "text"},
{Key: "greetings", Value: "text"},
},
Options: options.Index().SetName("TextIndex"),
}
_, err = mdb.Indexes().CreateOne(ctx, textIndexModel)
if err != nil {
return err
}
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 {
if writeErr, ok := err.(mongo.WriteException); ok {
for _, writeError := range writeErr.WriteErrors {
if writeError.Code == 11000 {
return nil, ErrDuplicateCodeword
}
}
}
return nil, err
}
return req, nil
}
func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) {
promoCodeID, err := primitive.ObjectIDFromHex(req.ID)
if err != nil {
return nil, err
}
updateFields := bson.M{}
if req.Description != nil {
updateFields["description"] = *req.Description
}
if req.Greetings != nil {
updateFields["greetings"] = *req.Greetings
}
if req.DueTo != nil {
updateFields["dueTo"] = *req.DueTo
}
if req.ActivationCount != nil {
updateFields["activationCount"] = *req.ActivationCount
}
if req.Delete != nil {
updateFields["delete"] = *req.Delete
}
if len(updateFields) == 0 {
return r.GetPromoCodeByID(ctx, promoCodeID)
}
update := bson.M{"$set": updateFields}
result, err := r.mdb.UpdateOne(ctx, bson.M{"_id": promoCodeID}, update)
if err != nil {
return nil, err
}
if result.MatchedCount == 0 {
return nil, ErrPromoCodeNotFound
}
return r.GetPromoCodeByID(ctx, promoCodeID)
}
func (r *PromoCodeRepository) GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error) {
var promoCode models.PromoCode
err := r.mdb.FindOne(ctx, bson.M{"_id": promoCodeID}).Decode(&promoCode)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, ErrPromoCodeNotFound
}
return nil, err
}
return &promoCode, nil
}
func (r *PromoCodeRepository) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) {
filter := bson.M{}
if req.Filter.Text != "" {
filter["$text"] = bson.M{"$search": req.Filter.Text}
}
if req.Filter.Active {
filter["delete"] = false
filter["outdated"] = false
filter["offLimit"] = false
} else {
filter["$or"] = []interface{}{
bson.M{"delete": true},
bson.M{"outdated": true},
bson.M{"offLimit": true},
}
}
opt := options.Find().SetSkip(int64(req.Page * req.Limit)).SetLimit(int64(req.Limit))
var countChan = make(chan countResult)
go func() {
defer close(countChan)
count, err := r.mdb.CountDocuments(ctx, filter)
countChan <- countResult{count, err}
}()
cursor, err := r.mdb.Find(ctx, filter, opt)
if err != nil {
return nil, 0, err
}
defer cursor.Close(ctx)
var promoCodes = make([]models.PromoCode, 0)
for cursor.Next(ctx) {
var p models.PromoCode
if err := cursor.Decode(&p); err != nil {
return nil, 0, err
}
promoCodes = append(promoCodes, p)
}
if err := cursor.Err(); err != nil {
return nil, 0, err
}
result := <-countChan
if result.err != nil {
return nil, 0, result.err
}
count := result.count
return promoCodes, count, nil
}
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 {
if err == mongo.ErrNoDocuments {
return nil, ErrPromoCodeNotFound
}
return nil, err
}
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 &promoCode, nil
}
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 nil
}
func (r *PromoCodeRepository) DeletePromoCode(ctx context.Context, promoCodeID string) error {
id, err := primitive.ObjectIDFromHex(promoCodeID)
if err != nil {
return err
}
result, err := r.mdb.UpdateOne(
ctx,
bson.M{"_id": id, "delete": false},
bson.M{"$set": bson.M{"delete": true}},
)
if err != nil {
return err
}
if result.MatchedCount == 0 {
return ErrPromoCodeNotFound
}
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
}

@ -1,72 +0,0 @@
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
}

@ -0,0 +1,52 @@
package repository
import (
"codeword/internal/models"
"context"
"database/sql"
)
type RecoverRepository struct {
queries *sqlcgen.Queries
pool *sql.DB
}
type RecoverDeps struct {
Queries *sqlcgen.Queries
Pool *sql.DB
}
func NewRecoverRepository(deps RecoverDeps) *RecoverRepository {
return &RecoverRepository{
queries: deps.Queries,
pool: deps.Pool,
}
}
func (r *RecoverRepository) Liveness(ctx context.Context) error {
//TODO:IMPLEMENT ME
return nil
}
func (r *RecoverRepository) Recovery(ctx context.Context, request *models.RecoveryReq) error {
//TODO:IMPLEMENT ME
return nil
}
func (r *RecoverRepository) Recoverylink(ctx context.Context) error {
//TODO:IMPLEMENT ME
return nil
}
func (r *RecoverRepository) Readiness(ctx context.Context) error {
//TODO:IMPLEMENT ME
return nil
}

@ -0,0 +1,31 @@
package repository
import (
"codeword/internal/models"
"context"
"database/sql"
)
type StatsRepository struct {
queries *sqlcgen.Queries
pool *sql.DB
}
type StatsDeps struct {
Queries *sqlcgen.Queries
Pool *sql.DB
}
func NewStatsRepository(deps StatsDeps) *StatsRepository {
return &StatsRepository{
queries: deps.Queries,
pool: deps.Pool,
}
}
func (r *StatsRepository) Getstats(ctx context.Context, request *models.PromoCodeStatsReq) (*models.PromoCodeStatsResp, error) {
//TODO:IMPLEMENT ME
return &models.PromoCodeStatsResp{}, nil
}

@ -1,37 +0,0 @@
package repository
import (
"codeword/internal/models"
"context"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
type Deps struct {
Mdb *mongo.Collection
Rdb *redis.Client
}
type UserRepository struct {
mdb *mongo.Collection
}
func NewUserRepository(deps Deps) *UserRepository {
return &UserRepository{mdb: deps.Mdb}
}
// ищем пользователя по мейлу в коллекции users
func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) {
var user models.User
err := r.mdb.FindOne(ctx, bson.M{"login": email}).Decode(&user)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, nil
}
return nil, ErrPromoUserNotFound
}
return &user, nil
}

@ -3,17 +3,15 @@ package http
import (
"context"
"fmt"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
)
type ServerConfig struct {
Logger *zap.Logger
Controllers []Controller
}
type Server struct {
Logger *zap.Logger
Controllers []Controller
app *fiber.App
}
@ -22,7 +20,6 @@ func NewServer(config ServerConfig) *Server {
app := fiber.New()
s := &Server{
Logger: config.Logger,
Controllers: config.Controllers,
app: app,
}
@ -34,7 +31,6 @@ func NewServer(config ServerConfig) *Server {
func (s *Server) Start(addr string) error {
if err := s.app.Listen(addr); err != nil {
s.Logger.Error("Failed to start server", zap.Error(err))
return err
}
return nil

@ -0,0 +1,77 @@
package service
import (
"codeword/internal/models"
"codeword/internal/repository"
"context"
)
type PromocodeService struct {
PromocodeRepository *repository.PromocodeRepository
}
func NewPromocodeService(repository *repository.PromocodeRepository) *PromocodeService {
return &PromocodeService{
PromocodeRepository: repository,
}
}
func (s *PromocodeService) Activate(ctx context.Context, request *models.ActivateReq) (*models.ActivateResp, error) {
response, err := s.PromocodeRepository.Activate(ctx, request)
if err != nil {
return nil, err
}
return response, nil
}
func (s *PromocodeService) Getlist(ctx context.Context, request *models.GetPromoCodesListReq) (*models.GetPromoCodesListResp, error) {
response, err := s.PromocodeRepository.Getlist(ctx, request)
if err != nil {
return nil, err
}
return response, nil
}
func (s *PromocodeService) Delete(ctx context.Context) error {
err := s.PromocodeRepository.Delete(ctx)
if err != nil {
return err
}
return nil
}
func (s *PromocodeService) Createpromocode(ctx context.Context, request *models.PromoCodeReq) (*models.PromoCode, error) {
response, err := s.PromocodeRepository.Createpromocode(ctx, request)
if err != nil {
return nil, err
}
return response, nil
}
func (s *PromocodeService) Editpromocode(ctx context.Context, request *models.EditPromoCodeReq) (*models.PromoCode, error) {
response, err := s.PromocodeRepository.Editpromocode(ctx, request)
if err != nil {
return nil, err
}
return response, nil
}
func (s *PromocodeService) Createfastlink(ctx context.Context, request *models.CreateFastLinkReq) (*models.CreateFastLinkResp, error) {
response, err := s.PromocodeRepository.Createfastlink(ctx, request)
if err != nil {
return nil, err
}
return response, nil
}

@ -0,0 +1,57 @@
package service
import (
"codeword/internal/models"
"codeword/internal/repository"
"context"
)
type RecoverService struct {
RecoverRepository *repository.RecoverRepository
}
func NewRecoverService(repository *repository.RecoverRepository) *RecoverService {
return &RecoverService{
RecoverRepository: repository,
}
}
func (s *RecoverService) Liveness(ctx context.Context) error {
err := s.RecoverRepository.Liveness(ctx)
if err != nil {
return err
}
return nil
}
func (s *RecoverService) Recovery(ctx context.Context, request *models.RecoveryReq) error {
err := s.RecoverRepository.Recovery(ctx, request)
if err != nil {
return err
}
return nil
}
func (s *RecoverService) Recoverylink(ctx context.Context) error {
err := s.RecoverRepository.Recoverylink(ctx)
if err != nil {
return err
}
return nil
}
func (s *RecoverService) Readiness(ctx context.Context) error {
err := s.RecoverRepository.Readiness(ctx)
if err != nil {
return err
}
return nil
}

27
internal/service/stats.go Normal file

@ -0,0 +1,27 @@
package service
import (
"codeword/internal/models"
"codeword/internal/repository"
"context"
)
type StatsService struct {
StatsRepository *repository.StatsRepository
}
func NewStatsService(repository *repository.StatsRepository) *StatsService {
return &StatsService{
StatsRepository: repository,
}
}
func (s *StatsService) Getstats(ctx context.Context, request *models.PromoCodeStatsReq) (*models.PromoCodeStatsResp, error) {
response, err := s.StatsRepository.Getstats(ctx, request)
if err != nil {
return nil, err
}
return response, nil
}

@ -1,211 +0,0 @@
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) (*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
StatsRepo PromoStatsRepository
Kafka *tariff.Producer
DiscountClient discount.DiscountServiceClient
}
type PromoCodeService struct {
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,
statsRepo: deps.StatsRepo,
kafka: deps.Kafka,
discountClient: deps.DiscountClient,
}
}
func (s *PromoCodeService) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) {
promoCode, err := s.promoCodeRepo.CreatePromoCode(ctx, req)
if err != nil {
s.logger.Error("Failed to add promocode in database", zap.Error(err))
return nil, err
}
return promoCode, nil
}
func (s *PromoCodeService) EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) {
editedPromoCode, err := s.promoCodeRepo.EditPromoCode(ctx, req)
if err != nil {
s.logger.Error("Failed to edit promocode in database", zap.Error(err))
return nil, err
}
return editedPromoCode, nil
}
func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) {
promoCodes, count, err := s.promoCodeRepo.GetPromoCodesList(ctx, req)
if err != nil {
s.logger.Error("Failed to get list promocodes from database", zap.Error(err))
return nil, 0, err
}
return promoCodes, count, nil
}
// 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.OffLimit {
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))
}
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 {
err := s.promoCodeRepo.DeletePromoCode(ctx, promoCodeID)
if err != nil {
s.logger.Error("Failed simple delete promocode from database", zap.Error(err))
return err
}
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
}

@ -1,135 +0,0 @@
package services
import (
"codeword/internal/adapters/client"
"codeword/internal/models"
"codeword/internal/utils/encrypt"
"context"
"encoding/base64"
"go.uber.org/zap"
)
type CodewordRepository interface {
StoreRecoveryRecord(ctx context.Context, deps models.StoreRecDeps) (string, error)
InsertToQueue(ctx context.Context, deps models.RecEmailDeps) error
Ping(ctx context.Context) error
GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error)
}
type UserRepository interface {
FindByEmail(ctx context.Context, email string) (*models.User, error)
}
type Deps struct {
Logger *zap.Logger
CodewordRepository CodewordRepository
UserRepository UserRepository
Encrypt *encrypt.Encrypt
AuthClient *client.AuthClient
}
type RecoveryService struct {
logger *zap.Logger
repositoryCodeword CodewordRepository
repositoryUser UserRepository
encrypt *encrypt.Encrypt
authClient *client.AuthClient
}
func NewRecoveryService(deps Deps) *RecoveryService {
return &RecoveryService{
logger: deps.Logger,
repositoryCodeword: deps.CodewordRepository,
repositoryUser: deps.UserRepository,
encrypt: deps.Encrypt,
authClient: deps.AuthClient,
}
}
// GenerateKey генерирует ключ, используя шифрование на основе эллиптической кривой
func (s *RecoveryService) GenerateKey() ([]byte, error) {
key, err := s.encrypt.SignCommonSecret()
if err != nil {
s.logger.Error("Failed to generate unique key for user", zap.Error(err))
return nil, err
}
return key, nil
}
// вызывает пингование в бд
func (s *RecoveryService) Ping(ctx context.Context) error {
err := s.repositoryCodeword.Ping(ctx)
if err != nil {
s.logger.Error("Failed to ping database", zap.Error(err))
return err
}
return nil
}
// FindUserByEmail ищет пользователя по электронной почте
func (s *RecoveryService) FindUserByEmail(ctx context.Context, email string) (*models.User, error) {
user, err := s.repositoryUser.FindByEmail(ctx, email)
if err != nil {
s.logger.Error("Failed to find user by email", zap.String("email", email), zap.Error(err))
return nil, err
}
return user, nil
}
// StoreRecoveryRecord сохраняет запись восстановления в базе данных
func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, deps models.StoreRecDeps) (string, error) {
id, err := s.repositoryCodeword.StoreRecoveryRecord(ctx, models.StoreRecDeps{UserID: deps.UserID, Email: deps.Email, Key: deps.Key, Url: deps.Url})
if err != nil {
s.logger.Error("Failed save data in mongoDB for email", zap.String("email", deps.Email), zap.Error(err))
return "", err
}
return id, nil
}
// RecoveryEmailTask посылает письмо для восстановления доступа пользователю
func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, deps models.RecEmailDeps) error {
err := s.repositoryCodeword.InsertToQueue(ctx, models.RecEmailDeps{UserID: deps.UserID, Email: deps.Email, SignWithID: deps.SignWithID, ID: deps.ID})
if err != nil {
s.logger.Error("Failed creating a task to send a worker by email", zap.String("email", deps.Email), zap.Error(err))
return err
}
return nil
}
// GetRecoveryRecord получает запись восстановления из базы данных
func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) {
req, err := s.repositoryCodeword.GetRecoveryRecord(ctx, key)
if err != nil {
s.logger.Error("Failed to obtain signature recovery data", zap.String("signature", key), zap.Error(err))
return nil, err
}
byteKey, err := base64.URLEncoding.DecodeString(req.Sign)
if err != nil {
s.logger.Error("Failed to decode string signature to []byte format", zap.String("signature", key), zap.Error(err))
return nil, err
}
// сомнительный вариант но как я думаю верный, что false==err
result, err := s.encrypt.VerifySignature(byteKey)
if err != nil || !result {
s.logger.Error("Failed to verify signature", zap.String("signature", key), zap.Error(err))
return nil, err
}
return req, nil
}
// меняет подпись на токены идя в auth сервис
func (s *RecoveryService) ExchangeForTokens(userID string, signature string) (map[string]string, error) {
tokens, err := s.authClient.RefreshAuthToken(userID, signature)
if err != nil {
s.logger.Error("Failed to refresh auth token", zap.Error(err))
return nil, err
}
return map[string]string{
"accessToken": tokens.AccessToken,
"refreshToken": tokens.RefreshToken,
}, nil
}

@ -0,0 +1,5 @@
-- name: Getlist :many
SELECT *
FROM {{ .TableName }} AS a
ORDER BY a.created_at DESC
LIMIT $2 OFFSET ($1-1)*$2;

@ -1,81 +0,0 @@
package encrypt
import (
"crypto/ed25519"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
)
type EncryptDeps struct {
PublicKey string
PrivateKey string
SignSecret string
}
type Encrypt struct {
publicKey string
privateKey string
signSecret string
}
func New(deps *EncryptDeps) *Encrypt {
return &Encrypt{
publicKey: deps.PublicKey,
privateKey: deps.PrivateKey,
signSecret: deps.SignSecret,
}
}
func (receiver *Encrypt) VerifySignature(signature []byte) (isValid bool, err error) {
defer func() {
if recovered := recover(); recovered != nil {
err = fmt.Errorf("recovered verify error on <VerifySignature> of <EncryptService>: %v", recovered)
}
}()
block, _ := pem.Decode([]byte(receiver.publicKey))
if block == nil {
return false, fmt.Errorf("public key block is nil")
}
rawPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return false, fmt.Errorf("failed parse public key on <VerifySignature> of <EncryptService>: %w", err)
}
publicKey, ok := rawPublicKey.(ed25519.PublicKey)
if !ok {
return false, errors.New("public key is not of type ed25519.PublicKey")
}
return ed25519.Verify(publicKey, []byte(receiver.signSecret), signature), nil
}
// TODO подумать над тем чтобы подпись генерилась каждый раз разгая
func (receiver *Encrypt) SignCommonSecret() (signature []byte, err error) {
defer func() {
if recovered := recover(); recovered != nil {
fmt.Printf("recovered sign error: \n%+v\n", receiver)
err = fmt.Errorf("recovered sign error on <SignCommonSecret> of <EncryptService>: %v", recovered)
}
}()
block, _ := pem.Decode([]byte(receiver.privateKey))
if block == nil {
return []byte{}, fmt.Errorf("failed decode private key %s on <SignCommonSecret> of <EncryptService>: %w", receiver.privateKey, err)
}
rawPrivateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return []byte{}, fmt.Errorf("failed parse private key on <SignCommonSecret> of <EncryptService>: %w", err)
}
privateKey, ok := rawPrivateKey.(ed25519.PrivateKey)
if !ok {
return []byte{}, fmt.Errorf("failed convert to ed25519.PrivateKey on <SignCommonSecret> of <EncryptService>: %w", err)
}
return ed25519.Sign(privateKey, []byte(receiver.signSecret)), nil
}

@ -1,8 +0,0 @@
package genID
import "github.com/rs/xid"
func GenerateXID() string {
id := xid.New()
return id.String()
}

@ -1,31 +0,0 @@
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
}

@ -1,17 +0,0 @@
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),
}
}

@ -1,60 +0,0 @@
package purge_worker
import (
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/zap"
"time"
)
type Deps struct {
Logger *zap.Logger
Mongo *mongo.Collection
}
type PurgeWorker struct {
logger *zap.Logger
mongo *mongo.Collection
}
func NewPurgeWC(deps Deps) *PurgeWorker {
return &PurgeWorker{
logger: deps.Logger,
mongo: deps.Mongo,
}
}
func (wc *PurgeWorker) Start(ctx context.Context) {
ticker := time.NewTicker(1 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ticker.C:
wc.processTasks(ctx)
case <-ctx.Done():
return
}
}
}
func (wc *PurgeWorker) processTasks(ctx context.Context) {
wc.logger.Info("Checking cleaning records")
oneHourAgo := time.Now().Add(-1 * time.Hour)
filter := bson.M{"created_at": bson.M{"$lt": oneHourAgo}}
result, err := wc.mongo.DeleteMany(ctx, filter)
if err != nil {
wc.logger.Error("Error when trying to delete old entries", zap.Error(err))
} else {
wc.logger.Info("Deleted documents", zap.Int64("count", result.DeletedCount))
}
}
func (wc *PurgeWorker) Stop(ctx context.Context) error {
return nil
}

@ -1,129 +0,0 @@
package recovery_worker
import (
"codeword/internal/adapters/client"
"codeword/internal/models"
"context"
"encoding/json"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/zap"
"time"
)
type Deps struct {
Logger *zap.Logger
Redis *redis.Client
EmailSender *client.RecoveryEmailSender
Mongo *mongo.Collection
}
type RecoveryWorker struct {
logger *zap.Logger
redis *redis.Client
emailSender *client.RecoveryEmailSender
mongo *mongo.Collection
}
func NewRecoveryWC(deps Deps) *RecoveryWorker {
return &RecoveryWorker{
logger: deps.Logger,
redis: deps.Redis,
emailSender: deps.EmailSender,
mongo: deps.Mongo,
}
}
func (wc *RecoveryWorker) Start(ctx context.Context) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
wc.processTasks(ctx)
case <-ctx.Done():
return
}
}
}
func (wc *RecoveryWorker) processTasks(ctx context.Context) {
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
}
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 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", key), zap.Error(err))
}
}
if cursor == 0 {
break
}
}
}
func (wc *RecoveryWorker) sendRecoveryTask(ctx context.Context, task models.RecoveryRecord) error {
err := wc.emailSender.SendRecoveryEmail(task.Email, task.Key)
if err != nil {
wc.logger.Error("Failed to send recovery email", zap.Error(err))
return err
}
update := bson.M{
"$set": bson.M{
"sent": true,
"sent_at": time.Now(),
},
}
objectID, err := primitive.ObjectIDFromHex(task.ID)
if err != nil {
wc.logger.Error("Invalid ObjectID", zap.String("ID", task.ID), zap.Error(err))
return err
}
filter := bson.M{"_id": objectID}
result, err := wc.mongo.UpdateOne(ctx, filter, update)
if err != nil {
wc.logger.Error("Failed to update restore request", zap.Error(err))
return err
}
if result.ModifiedCount == 0 {
wc.logger.Warn("No documents were updated - this may indicate the document was not found",
zap.String("ID", task.ID))
}
//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
}

@ -45,7 +45,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/RecoveryRequest'
$ref: '#/components/schemas/RecoveryReq'
responses:
'200':
description: Запрос на восстановление принят
@ -109,7 +109,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/ReqEditPromoCode'
$ref: '#/components/schemas/EditPromoCodeReq'
responses:
'200':
description: Промокод успешно обновлен
@ -208,14 +208,14 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateFastLinkRequest'
$ref: '#/components/schemas/CreateFastLinkReq'
responses:
'200':
description: Быстрая ссылка для промокода успешно создана
content:
application/json:
schema:
$ref: '#/components/schemas/CreateFastLinkResponse'
$ref: '#/components/schemas/CreateFastLinkResp'
'400':
description: Неверный запрос, отсутствует идентификатор промокода
'404':
@ -235,14 +235,14 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/PromoCodeStatsRequest'
$ref: '#/components/schemas/PromoCodeStatsReq'
responses:
'200':
description: Статистика промокода успешно получена
content:
application/json:
schema:
$ref: '#/components/schemas/PromoCodeStats'
$ref: '#/components/schemas/PromoCodeStatsResp'
'400':
description: Неверный запрос
'500':
@ -251,7 +251,7 @@ paths:
components:
schemas:
RecoveryRequest:
RecoveryReq:
type: object
required:
- email
@ -263,14 +263,14 @@ components:
redirectionURL:
type: string
description: URL-адрес, на который перенаправляется пользователь
PromoCodeStatsRequest:
PromoCodeStatsReq:
type: object
properties:
promoCodeID:
type: string
required:
- promoCodeID
PromoCodeStats:
PromoCodeStatsResp:
type: object
properties:
id:
@ -298,7 +298,7 @@ components:
format: date-time
description: Время использования промокода
CreateFastLinkRequest:
CreateFastLinkReq:
type: object
properties:
id:
@ -307,7 +307,7 @@ components:
required:
- id
CreateFastLinkResponse:
CreateFastLinkResp:
type: object
properties:
fastlink:
@ -393,33 +393,8 @@ components:
bonus:
type: object
description: Бонус, предоставляемый с промокодом
properties:
privilege:
type: object
description: Привилегия
properties:
privilegeID:
type: string
description: Идентификатор привилегии
amount:
type: integer
description: Количество привилегии
discount:
type: object
description: Скидка
properties:
layer:
type: integer
description: Уровень скидки
factor:
type: number
description: Множитель скидки
target:
type: string
description: Слой
threshold:
type: integer
description: Граничное значение
items:
$ref: '#/components/schemas/Bonus'
outdated:
type: boolean
description: Флаг
@ -439,7 +414,7 @@ components:
type: string
description: Список быстрых ссылок для активации промокода
ReqEditPromoCode:
EditPromoCodeReq:
type: object
properties:
ID:
@ -486,35 +461,48 @@ components:
bonus:
type: object
description: Бонус
properties:
privilege:
type: object
description: Привилегия
properties:
privilegeID:
type: string
description: Идентификатор привилегии
amount:
type: integer
description: Количество привилегии
discount:
type: object
description: Скидка
properties:
layer:
type: integer
description: Уровень скидки
factor:
type: number
description: Множитель скидки
target:
type: string
description: Слой
threshold:
type: integer
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: Порог скидки

@ -1,37 +0,0 @@
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
}

15
sqlc.yaml Normal file

@ -0,0 +1,15 @@
version: "1"
packages:
- name: "sqlcgen"
path: ".internal/sqlc/sqlcgen"
queries: ".internal/sqlc/db_query/queries.sql"
schema:
- "todo"
engine: "postgresql"
emit_json_tags: true
emit_db_tags: true
emit_prepared_queries: false
emit_interface: false
emit_exact_table_names: false
emit_empty_slices: false
sql_package: "database/sql"

@ -7,14 +7,14 @@ import (
"{{.Vars.ProjectName}}/internal/app"
{{.Modules.logger.Import}}
{{.Modules.logger.ImportCore}}
"{{.Modules.logger.Import}}"
"{{.Modules.logger.ImportCore}}"
{{.Modules.env.Import}}
"{{.Modules.env.Import}}"
)
func main() {
{{.Modules.logger.Init}}
{{.Modules.logger.Declaration "logger"}}
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
@ -26,4 +26,4 @@ func main() {
if err := app.Run(ctx, config, logger); err != nil {
{{.Modules.logger.Message "Fatal" "Failed to run app" "Error" "err"}}
}
}
}

@ -3,17 +3,65 @@ package app
import (
"context"
{{.Modules.logger.Import}}
"{{.Modules.logger.Import}}"
)
{{.Modules.env.Struct}}
func Run(ctx context.Context, config Config, logger {{.Modules.logger.Type}}) error {
defer func() {
if r := recover(); r != nil {
logger.Error("Recovered from a panic", zap.Any("error", r))
}
}()
{{.Modules.logger.Message "info" "App started" "config" "config"}}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// Инициализация репозиториев
{{range $key, $value := .LayersData.Repositories}}
{{$key}}Repository := {{$value.PackageName}}.New{{$value.Name}}Repository()
{{end}}
// Инициализация сервисов
{{range $key, $value := .LayersData.Services}}
{{$key}}Service := {{$value.PackageName}}.New{{$value.Name}}Service({{$key}}Repository)
{{end}}
// Инициализация контроллеров
{{range $key, $value := .LayersData.Controllers}}
{{$key}}Controller := {{$value.PackageName}}.New{{$value.Name}}Controller({{$key}}Service)
{{end}}
// Создание сервера
server := {{.LayersData.ServerData}}.NewServer({{.LayersData.ServerData}}.ServerConfig{
Controllers: []{{.LayersData.ServerData}}.Controller{
{{range $key, $value := .LayersData.Controllers}}
{{$key}}Controller,
{{end}}
},
})
go func() {
err := server.Start("Host + : + Port")
if err != nil {
logger.Error("Server startup error", zap.Error(err))
cancel()
}
}()
// Вывод маршрутов
server.ListRoutes()
<-ctx.Done()
logger.Info("App shutting down gracefully")
//TODO
// Остановка сервера
return nil
}
}

@ -1,599 +0,0 @@
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"])
})
}

@ -1,122 +0,0 @@
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")
})
}

@ -1,38 +0,0 @@
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",
})
}

@ -1,518 +0,0 @@
package repository_test
import (
"codeword/internal/models"
"codeword/internal/repository"
"context"
"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"
"github.com/pioz/faker"
"github.com/stretchr/testify/assert"
)
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()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
if err != nil {
log.Fatalf("Failed to connect to MongoDB: %v", err)
}
defer func() {
if err := client.Disconnect(ctx); err != nil {
log.Fatalf("Failed to disconnect MongoDB client: %v", err)
}
}()
if err := client.Ping(ctx, nil); err != nil {
log.Fatalf("Failed to ping MongoDB: %v", err)
}
db := client.Database("admin")
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, "admin")
assert.NoError(t, err)
assert.NotNil(t, user)
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, "neadmin")
assert.NoError(t, err)
assert.Nil(t, user)
})
}
func TestStoreRecoveryRecord(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})
for i := 0; i < 10; i++ {
userID := faker.String()
email := faker.Email()
key := "test_recovery_key"
id, err := userRepo.StoreRecoveryRecord(ctx, models.StoreRecDeps{UserID: userID, Email: email, Key: key, Url: "def.url"})
assert.NoError(t, err)
fmt.Println(id)
var storedRecord models.RestoreRequest
err = codeword.FindOne(ctx, bson.M{"user_id": userID}).Decode(&storedRecord)
assert.NoError(t, err)
assert.Equal(t, email, storedRecord.Email)
assert.Equal(t, key, storedRecord.Sign)
}
_ = 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)
})
}

@ -1,46 +0,0 @@
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
}

@ -1,89 +0,0 @@
package utils
import (
"codeword/internal/initialize"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v5"
"time"
)
type JWT struct {
privateKey []byte
publicKey []byte
algorithm *jwt.SigningMethodRSA
expiresIn time.Duration
issuer string
audience string
}
func NewJWT(configuration *initialize.Config) *JWT {
return &JWT{
privateKey: []byte(configuration.PrivateKey),
publicKey: []byte(configuration.PublicKey),
issuer: configuration.Issuer,
audience: configuration.Audience,
algorithm: jwt.SigningMethodRS256,
expiresIn: 15 * time.Minute,
}
}
func (j *JWT) Create(id string) (string, error) {
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(j.privateKey)
if err != nil {
return "", fmt.Errorf("failed to parse private key on <Create> of <JWT>: %w", err)
}
now := time.Now().UTC()
claims := jwt.MapClaims{
"id": id,
"exp": now.Add(j.expiresIn).Unix(),
"aud": j.audience,
"iss": j.issuer,
}
token, err := jwt.NewWithClaims(j.algorithm, claims).SignedString(privateKey)
if err != nil {
return "", fmt.Errorf("failed to sing on <Create> of <JWT>: %w", err)
}
return token, nil
}
func (j *JWT) Validate(tokenString string) (string, error) {
key, err := jwt.ParseRSAPublicKeyFromPEM(j.publicKey)
if err != nil {
return "", fmt.Errorf("failed to parse rsa public key on <Validate> of <JWT>: %w", err)
}
parseCallback := func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %s", token.Header["alg"])
}
return key, nil
}
token, err := jwt.Parse(
tokenString,
parseCallback,
jwt.WithAudience(j.audience),
jwt.WithIssuer(j.issuer),
)
if err != nil {
return "", fmt.Errorf("failed to parse jwt token on <Validate> of <JWT>: %w", err)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
return "", errors.New("token is invalid on <Validate> of <JWT>")
}
data, ok := claims["id"].(string)
if !ok {
return "", errors.New("data is empty or not a string on <Validate> of <JWT>")
}
return data, nil
}