feat: account crud complete & errors wrapper & security

This commit is contained in:
Kirill 2023-05-18 15:26:37 +03:00
parent 7060ec31f7
commit a57d1db0ab
27 changed files with 316 additions and 322 deletions

@ -3,40 +3,18 @@ HTTP_HOST=0.0.0.0
HTTP_PORT=8080
# MONGO settings
MONGO_HOST=mongo
MONGO_HOST=localhost
MONGO_PORT=27017
MONGO_USER=test
MONGO_PASSWORD=test
MONGO_AUTH=admin
MONGO_DB_NAME=admin
# GOOGLE settings
GOOGLE_REDIRECT_URL=http://localhost:8080/google/callback
GOOGLE_CLIENT_ID=test.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=test-0VOAAx87vr7epBTG
GOOGLE_OAUTH_HOST=https://www.googleapis.com/oauth2/v3
# VK settings
VK_REDIRECT_URL=http://localhost:8080/vk/callback
VK_CLIENT_ID=51394112
VK_CLIENT_SECRET=test_ursJCpEY
# Amocrm settings
AMOCRM_CLIENT_ID=d677e851-03e0-467e-a5de-2ebcf6d47529
AMOCRM_CLIENT_SECRET=fOzWUMyF6fGmAkgLULN0H4wFUClVTsaVTPOmVazit4cQzuDM1JhGr3MP6IZIcKmj
AMOCRM_REDIRECT_URL=https://oauth.pena.digital/amocrm/callback
AMOCRM_OAUTH_HOST=https://www.amocrm.ru/oauth
AMOCRM_USER_INFO_URL=http://localhost:8000/api/v4/account
AMOCRM_ACCESS_TOKEN_URL=http://localhost:8000/oauth2/access_token
# Auth Microservice settings
AUTH_MICROSERVICE_GROUP=group
AUTH_MICROSERVICE_PRIVATE_SIGN_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZvODgWAnUIwQhd8de5oZhY48gc23EWfrfs\n-----END PRIVATE KEY-----"
AUTH_MICROSERVICE_EXHANGE_URL=http://localhost:8000/exchange
AUTH_MICROSERVICE_REGISTER_URL=http://localhost:8000/register
AUTH_MICROSERVICE_USER_URL=http://localhost:8000/user
# JWT settings
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyt4XuLovUY7i12K2PIMbQZOKn+wFFKUvxvKQDel049/+VMpHMx1FLolUKuyGp9zi6gOwjHsBPgc9oqr/eaXGQSh7Ult7i9f+Ht563Y0er5UU9Zc5ZPSxf9O75KYD48ruGkqiFoncDqPENK4dtUa7w0OqlN4bwVBbmIsP8B3EDC5Dof+vtiNTSHSXPx+zifKeZGyknp+nyOHVrRDhPjOhzQzCom0MSZA/sJYmps8QZgiPA0k4Z6jTupDymPOIwYeD2C57zSxnAv0AfC3/pZYJbZYH/0TszRzmy052DME3zMnhMK0ikdN4nzYqU0dkkA5kb5GtKDymspHIJ9eWbUuwgtg8Rq/LrVBj1I3UFgs0ibio40k6gqinLKslc5Y1I5mro7J3OSEP5eO/XeDLOLlOJjEqkrx4fviI1cL3m5L6QV905xmcoNZG1+RmOg7D7cZQUf27TXqM381jkbNdktm1JLTcMScxuo3vaRftnIVw70V8P8sIkaKY8S8HU1sQgE2LB9t04oog5u59htx2FHv4B13NEm8tt8Tv1PexpB4UVh7PIualF6SxdFBrKbraYej72wgjXVPQ0eGXtGGD57j8DUEzk7DK2OvIWhehlVqtiRnFdAvdBj2ynHT2/5FJ/Zpd4n5dKGJcQvy1U1qWMs+8M7AHfWyt2+nZ04s48+bK3yMCAwEAAQ==\n-----END PUBLIC KEY-----"
JWT_ISSUER="issuer"
JWT_AUDIENCE="audience"
JWT_ISSUER="pena-auth-service"
JWT_AUDIENCE="pena"
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAE=\n-----END PUBLIC KEY-----"

@ -9,7 +9,8 @@ lint:
image: golangci/golangci-lint:v1.52-alpine
stage: lint
before_script:
- go install github.com/vektra/mockery/v2@v2.26.0
- go install github.com/vektra/mockery/v2@v2.26.0
- go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4
script:
- go generate ./...
- golangci-lint version

13
go.mod

@ -19,11 +19,16 @@ require (
require (
cloud.google.com/go/compute/metadata v0.2.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.2.0 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/brpaz/echozap v1.1.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
@ -43,6 +48,7 @@ require (
github.com/perimeterx/marshmallow v1.1.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/swaggo/swag v1.16.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
@ -54,10 +60,11 @@ require (
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

23
go.sum

@ -1,5 +1,11 @@
cloud.google.com/go/compute/metadata v0.2.0 h1:nBbNSZyDpkNlo3DepaaLKVuO7ClyifSAmNloSCZrHnQ=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.2.0 h1:/Jdm5QfyM8zdlqT6WVZU4cfP23sot6CEHA4CS49Ezig=
github.com/PuerkitoBio/purell v1.2.0/go.mod h1:OhLRTaaIzhvIyofkJfB24gokC7tM42Px5UhoT32THBk=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
@ -17,11 +23,22 @@ github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGn
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/getkin/kin-openapi v0.116.0 h1:o986hwgMzR972JzOG5j6+WTwWqllZLs1EJKMKCivs2E=
github.com/getkin/kin-openapi v0.116.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
@ -118,6 +135,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg=
github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
@ -169,6 +188,8 @@ golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -196,6 +217,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=

@ -40,7 +40,6 @@ func Run(ctx context.Context, config *models.Config, logger *zap.Logger) error {
services := initialize.NewServices(&initialize.ServicesDeps{
Logger: logger,
Config: &config.Service,
Repositories: repositories,
Clients: clients,
})
@ -50,21 +49,21 @@ func Run(ctx context.Context, config *models.Config, logger *zap.Logger) error {
Services: services,
})
openAPI, err := swagger.GetSwagger()
openapi, err := swagger.GetSwagger()
if err != nil {
return fmt.Errorf("failed to loading openapi spec: %w", err)
}
swagger := initialize.NewSwagger(controllers)
api := initialize.NewAPI(controllers)
closer := closer.New()
httpServer := server.New(&server.Deps{
Logger: logger,
Swagger: openAPI,
JWTUtil: utils.NewJWT[models.AuthJWTDecoded](&config.Service.JWT),
Logger: logger,
Swagger: openapi,
AuthenticationFunc: utils.NewAuthenticator(utils.NewJWT(&config.Service.JWT)),
})
httpServer.Register(swagger)
httpServer.Register(api)
go httpServer.Run(&config.HTTP)

@ -15,7 +15,7 @@ import (
type accountService interface {
GetAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error)
GetAccountsList(ctx context.Context, pagination *models.Pagination) ([]models.Account, errors.Error)
GetAccountsList(ctx context.Context, pagination *models.Pagination) (*models.PaginationResponse[models.Account], errors.Error)
CreateAccount(ctx context.Context, account *models.Account) (*models.Account, errors.Error)
CreateAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error)
RemoveAccount(ctx context.Context, userID string) (*models.Account, errors.Error)
@ -56,8 +56,8 @@ func (receiver *Controller) GetAccount(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, account)
}
func (receiver *Controller) GetDirectAccount(ctx echo.Context, userId string) error {
account, err := receiver.service.GetAccountByUserID(ctx.Request().Context(), userId)
func (receiver *Controller) GetDirectAccount(ctx echo.Context, userID string) error {
account, err := receiver.service.GetAccountByUserID(ctx.Request().Context(), userID)
if err != nil {
return utils.DetermineEchoErrorResponse(ctx, err)
}
@ -66,7 +66,7 @@ func (receiver *Controller) GetDirectAccount(ctx echo.Context, userId string) er
}
func (receiver *Controller) GetAccounts(ctx echo.Context, params swagger.PaginationAccountsParams) error {
accounts, err := receiver.service.GetAccountsList(
response, err := receiver.service.GetAccountsList(
ctx.Request().Context(),
utils.DeterminePagination(params.Page, params.Limit),
)
@ -74,7 +74,7 @@ func (receiver *Controller) GetAccounts(ctx echo.Context, params swagger.Paginat
return utils.DetermineEchoErrorResponse(ctx, err)
}
return ctx.JSON(http.StatusOK, accounts)
return ctx.JSON(http.StatusOK, response)
}
func (receiver *Controller) CreateAccount(ctx echo.Context) error {
@ -111,8 +111,8 @@ func (receiver *Controller) RemoveAccount(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, account)
}
func (receiver *Controller) RemoveDirectAccount(ctx echo.Context, userId string) error {
account, err := receiver.service.RemoveAccount(ctx.Request().Context(), userId)
func (receiver *Controller) RemoveDirectAccount(ctx echo.Context, userID string) error {
account, err := receiver.service.RemoveAccount(ctx.Request().Context(), userID)
if err != nil {
return utils.DetermineEchoErrorResponse(ctx, err)
}

@ -13,6 +13,7 @@ var (
ErrMethodNotImplemented ErrorType = errors.New("method is not implemented")
ErrNotFound ErrorType = errors.New("record not found")
ErrNoAccess ErrorType = errors.New("no access")
ErrConflict ErrorType = errors.New("record already exist")
)
type Error interface {

@ -0,0 +1,9 @@
package initialize
import "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/swagger"
func NewAPI(controllers *Controllers) *swagger.API {
return swagger.New(&swagger.Deps{
AccountController: controllers.AccountController,
})
}

@ -0,0 +1,49 @@
package initialize_test
import (
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/initialize"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)
func TestNewAPI(t *testing.T) {
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
mt.Run("API сваггера должен успешно инициализироваться", func(t *mtest.T) {
assert.NotPanics(t, func() {
logger := zap.New(zap.L().Core())
repositories := initialize.NewRepositories(&initialize.RepositoriesDeps{
Logger: logger,
MongoDB: t.Client.Database("test"),
})
clients := initialize.NewClients(&initialize.ClientsDeps{
Logger: logger,
AuthURL: &models.AuthMicroServiceURL{
User: "",
},
})
services := initialize.NewServices(&initialize.ServicesDeps{
Logger: logger,
Repositories: repositories,
Clients: clients,
})
controllers := initialize.NewControllers(&initialize.ControllersDeps{
Logger: logger,
Services: services,
})
api := initialize.NewAPI(controllers)
assert.NotNil(t, api)
assert.NotNil(t, api.AccountController)
})
})
}

@ -11,6 +11,40 @@ import (
"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/mongo"
)
func TestConfiguration(t *testing.T) {
t.Run("Успешная инициализация конфигурации", func(t *testing.T) {
defaultConfiguration := setDefaultTestingENV(t)
assert.NotPanics(t, func() {
configuration, err := initialize.Configuration("")
assert.NoError(t, err)
assert.Equal(t, defaultConfiguration, configuration)
})
})
t.Run("Ошибка при наличии кривого url", func(t *testing.T) {
setDefaultTestingENV(t)
t.Setenv("AUTH_MICROSERVICE_USER_URL", "url")
assert.NotPanics(t, func() {
configuration, err := initialize.Configuration("")
assert.Error(t, err)
assert.Nil(t, configuration)
})
})
t.Run("Ошибка при отсутствии обязательного env", func(t *testing.T) {
assert.NotPanics(t, func() {
configuration, err := initialize.Configuration("")
assert.Error(t, err)
assert.Nil(t, configuration)
})
})
}
func setDefaultTestingENV(t *testing.T) *models.Config {
t.Helper()
@ -62,37 +96,3 @@ func setDefaultTestingENV(t *testing.T) *models.Config {
return &defaultConfiguration
}
func TestConfiguration(t *testing.T) {
t.Run("Успешная инициализация конфигурации", func(t *testing.T) {
defaultConfiguration := setDefaultTestingENV(t)
assert.NotPanics(t, func() {
configuration, err := initialize.Configuration("")
assert.NoError(t, err)
assert.Equal(t, defaultConfiguration, configuration)
})
})
t.Run("Ошибка при наличии кривого url", func(t *testing.T) {
setDefaultTestingENV(t)
t.Setenv("AMOCRM_USER_INFO_URL", "url")
assert.NotPanics(t, func() {
configuration, err := initialize.Configuration("")
assert.Error(t, err)
assert.Nil(t, configuration)
})
})
t.Run("Ошибка при отсутствии обязательного env", func(t *testing.T) {
assert.NotPanics(t, func() {
configuration, err := initialize.Configuration("")
assert.Error(t, err)
assert.Nil(t, configuration)
})
})
}

@ -15,6 +15,7 @@ func TestNewControllers(t *testing.T) {
})
assert.NotNil(t, controllers)
assert.NotNil(t, controllers.AccountController)
})
})
}

@ -2,13 +2,11 @@ package initialize
import (
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/service/account"
)
type ServicesDeps struct {
Logger *zap.Logger
Config *models.ServiceConfiguration
Repositories *Repositories
Clients *Clients
}
@ -22,6 +20,7 @@ func NewServices(deps *ServicesDeps) *Services {
AccountService: account.New(&account.Deps{
Logger: deps.Logger,
Repository: deps.Repositories.AccountRepository,
AuthClient: deps.Clients.AuthClient,
}),
}
}

@ -4,21 +4,28 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/initialize"
)
func TestNewServices(t *testing.T) {
configuration := setDefaultTestingENV(t)
mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
t.Run("Сервисы должны успешно инициализироваться", func(t *testing.T) {
mt.Run("Сервисы должны успешно инициализироваться", func(t *mtest.T) {
assert.NotPanics(t, func() {
clients := initialize.NewClients(&initialize.ClientsDeps{})
repositories := initialize.NewRepositories(&initialize.RepositoriesDeps{
MongoDB: t.Client.Database("test"),
})
services := initialize.NewServices(&initialize.ServicesDeps{
Config: &configuration.Service,
Clients: &initialize.Clients{},
Repositories: &initialize.Repositories{},
Clients: clients,
Repositories: repositories,
})
assert.NotNil(t, services)
assert.NotNil(t, services.AccountService)
assert.NotNil(t, services.AccountService.AuthClient)
assert.NotNil(t, services.AccountService.Repository)
})
})
}

@ -1,7 +0,0 @@
package initialize
import "penahub.gitlab.yandexcloud.net/pena-services/customer/internal/swagger"
func NewSwagger(controllers *Controllers) *swagger.API {
return swagger.New(&swagger.Deps{})
}

@ -3,7 +3,7 @@ package models
import "time"
type Account struct {
ID string `json:"id" bson:"_id"`
ID string `json:"id" bson:"_id,omitempty"`
UserID string `json:"userId" bson:"userId"`
Cart []string `json:"cart" bson:"cart"`
Wallet Wallet `json:"wallet" bson:"wallet"`

@ -13,8 +13,4 @@ type User struct {
DeletedAt *time.Time `json:"deletedAt,omitempty"`
}
type AuthJWTDecoded struct {
UserID string `json:"id"`
}
const AuthJWTDecodedUserIDKey = "userID"

@ -11,6 +11,11 @@ type ResponseErrorHTTP struct {
Message string `json:"message"`
}
type PaginationResponse[T any] struct {
TotalPages int64 `json:"totalPages"`
Records []T `json:"records"`
}
type Pagination struct {
Page int64
Limit int64

@ -177,3 +177,19 @@ func (receiver *AccountRepository) Delete(ctx context.Context, id string) (*mode
return &account, nil
}
func (receiver *AccountRepository) CountAll(ctx context.Context) (int64, errors.Error) {
count, err := receiver.mongoDB.CountDocuments(ctx, bson.M{fields.Account.Deleted: false})
if err != nil {
receiver.logger.Error("failed to count all documents on <CountAll> of <AccountRepository>",
zap.Error(err),
)
return 0, errors.New(
fmt.Errorf("failed to count all documents on <CountAll> of <AccountRepository>: %w", err),
errors.ErrInternalError,
)
}
return count, nil
}

@ -6,8 +6,6 @@ import (
"net/http"
"time"
"github.com/brpaz/echozap"
oapiMiddleware "github.com/deepmap/oapi-codegen/pkg/middleware"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/labstack/echo/v4"
@ -15,13 +13,12 @@ import (
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/swagger"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils"
)
type Deps struct {
Logger *zap.Logger
Swagger *openapi3.T
JWTUtil *utils.JWT[models.AuthJWTDecoded]
Logger *zap.Logger
Swagger *openapi3.T
AuthenticationFunc openapi3filter.AuthenticationFunc
}
type HTTP struct {
@ -33,12 +30,8 @@ type HTTP struct {
func New(deps *Deps) *HTTP {
echo := echo.New()
echo.Use(echozap.ZapLogger(deps.Logger))
echo.Use(middleware.Recover())
echo.Use(oapiMiddleware.OapiRequestValidator(deps.Swagger))
echo.Use(oapiMiddleware.OapiRequestValidatorWithOptions(deps.Swagger, &oapiMiddleware.Options{
Options: openapi3filter.Options{AuthenticationFunc: utils.NewAuthenticator(deps.JWTUtil)},
}))
echo.Use(swagger.CreateMiddleware(deps.Swagger, deps.AuthenticationFunc))
return &HTTP{
echo: echo,

@ -3,6 +3,7 @@ package account
import (
"context"
"fmt"
"math"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
@ -15,6 +16,7 @@ type accountRepository interface {
Insert(ctx context.Context, account *models.Account) (*models.Account, errors.Error)
Remove(ctx context.Context, id string) (*models.Account, errors.Error)
Delete(ctx context.Context, id string) (*models.Account, errors.Error)
CountAll(ctx context.Context) (int64, errors.Error)
}
type authClient interface {
@ -29,20 +31,20 @@ type Deps struct {
type Service struct {
logger *zap.Logger
repository accountRepository
authClient authClient
Repository accountRepository
AuthClient authClient
}
func New(deps *Deps) *Service {
return &Service{
logger: deps.Logger,
repository: deps.Repository,
authClient: deps.AuthClient,
Repository: deps.Repository,
AuthClient: deps.AuthClient,
}
}
func (receiver *Service) GetAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error) {
account, err := receiver.repository.FindByUserID(ctx, userID)
account, err := receiver.Repository.FindByUserID(ctx, userID)
if err != nil {
receiver.logger.Error("failed to get account by id on <GetAccountByUserID> of <AccountService>",
zap.Error(err.Extract()),
@ -55,7 +57,7 @@ func (receiver *Service) GetAccountByUserID(ctx context.Context, userID string)
return account, nil
}
func (receiver *Service) GetAccountsList(ctx context.Context, pagination *models.Pagination) ([]models.Account, errors.Error) {
func (receiver *Service) GetAccountsList(ctx context.Context, pagination *models.Pagination) (*models.PaginationResponse[models.Account], errors.Error) {
if pagination == nil {
return nil, errors.New(
fmt.Errorf("pagination is nil on <GetAccountsList> of <AccountService>: %w", errors.ErrInternalError),
@ -63,7 +65,22 @@ func (receiver *Service) GetAccountsList(ctx context.Context, pagination *models
)
}
accounts, err := receiver.repository.FindMany(ctx, pagination.Page, pagination.Limit)
count, err := receiver.Repository.CountAll(ctx)
if err != nil {
receiver.logger.Error("failed to count accounts on <GetAccountsList> of <AccountService>",
zap.Error(err.Extract()),
)
return nil, err
}
if count == 0 {
return &models.PaginationResponse[models.Account]{TotalPages: 0, Records: []models.Account{}}, nil
}
totalPages := int64(math.Ceil(float64(count) / float64(pagination.Limit)))
accounts, err := receiver.Repository.FindMany(ctx, pagination.Page, pagination.Limit)
if err != nil {
receiver.logger.Error("failed to get accounts list on <GetAccountsList> of <AccountService>",
zap.Error(err.Extract()),
@ -74,11 +91,33 @@ func (receiver *Service) GetAccountsList(ctx context.Context, pagination *models
return nil, err
}
return accounts, nil
return &models.PaginationResponse[models.Account]{
TotalPages: totalPages,
Records: accounts,
}, nil
}
func (receiver *Service) CreateAccount(ctx context.Context, account *models.Account) (*models.Account, errors.Error) {
accounts, err := receiver.repository.Insert(ctx, account)
findedAccount, err := receiver.GetAccountByUserID(ctx, account.UserID)
if err != nil && err.Type() != errors.ErrNotFound {
receiver.logger.Error("failed to find account on <CreateAccount> of <AccountService>",
zap.Error(err.Extract()),
)
return nil, err
}
if findedAccount != nil {
return nil, errors.New(
fmt.Errorf("failed to create account with <%s> on <CreateAccount> of <AccountService>: %w",
account.UserID,
errors.ErrConflict,
),
errors.ErrConflict,
)
}
createdAccount, err := receiver.Repository.Insert(ctx, account)
if err != nil {
receiver.logger.Error("failed to create account on <CreateAccount> of <AccountService>",
zap.Error(err.Extract()),
@ -88,16 +127,35 @@ func (receiver *Service) CreateAccount(ctx context.Context, account *models.Acco
return nil, err
}
return accounts, nil
return createdAccount, nil
}
/*
CreateAccountByUserID
CreateAccountByUserID возвращает аккаут по id пользователя
TODO: Дополнить проверку на дефолтное значение Currency
TODO: Дополнить проверку на дефолтное значение Currency.
*/
func (receiver *Service) CreateAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error) {
user, err := receiver.authClient.GetUser(ctx, userID)
account, err := receiver.GetAccountByUserID(ctx, userID)
if err != nil && err.Type() != errors.ErrNotFound {
receiver.logger.Error("failed to find account on <CreateAccountByUserID> of <AccountService>",
zap.Error(err.Extract()),
)
return nil, err
}
if account != nil {
return nil, errors.New(
fmt.Errorf("failed to create account with <%s> on <CreateAccountByUserID> of <AccountService>: %w",
userID,
errors.ErrConflict,
),
errors.ErrConflict,
)
}
user, err := receiver.AuthClient.GetUser(ctx, userID)
if err != nil {
receiver.logger.Error("failed to get user on <CreateAccountByUserID> of <AccountService>",
zap.Error(err.Extract()),
@ -107,7 +165,7 @@ func (receiver *Service) CreateAccountByUserID(ctx context.Context, userID strin
return nil, err
}
account, err := receiver.repository.Insert(ctx, &models.Account{
createdAccount, err := receiver.Repository.Insert(ctx, &models.Account{
UserID: user.ID,
Cart: make([]string, 0),
Wallet: models.Wallet{
@ -125,11 +183,11 @@ func (receiver *Service) CreateAccountByUserID(ctx context.Context, userID strin
return nil, err
}
return account, nil
return createdAccount, nil
}
func (receiver *Service) RemoveAccount(ctx context.Context, userID string) (*models.Account, errors.Error) {
account, err := receiver.repository.Remove(ctx, userID)
account, err := receiver.Repository.Remove(ctx, userID)
if err != nil {
receiver.logger.Error("failed to remove account on <RemoveAccount> of <AccountService>",
zap.Error(err.Extract()),
@ -143,7 +201,7 @@ func (receiver *Service) RemoveAccount(ctx context.Context, userID string) (*mod
}
func (receiver *Service) DeleteAccount(ctx context.Context, userID string) (*models.Account, errors.Error) {
account, err := receiver.repository.Delete(ctx, userID)
account, err := receiver.Repository.Delete(ctx, userID)
if err != nil {
receiver.logger.Error("failed to delete account on <DeleteAccount> of <AccountService>",
zap.Error(err.Extract()),

@ -13,8 +13,8 @@ type AccountController interface {
RemoveAccount(ctx echo.Context) error
GetAccount(ctx echo.Context) error
CreateAccount(ctx echo.Context) error
RemoveDirectAccount(ctx echo.Context, userId string) error
GetDirectAccount(ctx echo.Context, userId string) error
RemoveDirectAccount(ctx echo.Context, userID string) error
GetDirectAccount(ctx echo.Context, userID string) error
GetAccounts(ctx echo.Context, params PaginationAccountsParams) error
}
@ -23,37 +23,37 @@ type Deps struct {
}
type API struct {
accountController AccountController
AccountController AccountController
}
func New(deps *Deps) *API {
return &API{
accountController: deps.AccountController,
AccountController: deps.AccountController,
}
}
func (receiver *API) DeleteAccount(ctx echo.Context) error {
return receiver.accountController.RemoveAccount(ctx)
return receiver.AccountController.RemoveAccount(ctx)
}
func (receiver *API) GetAccount(ctx echo.Context) error {
return receiver.accountController.GetAccount(ctx)
return receiver.AccountController.GetAccount(ctx)
}
func (receiver *API) AddAccount(ctx echo.Context) error {
return receiver.accountController.CreateAccount(ctx)
return receiver.AccountController.CreateAccount(ctx)
}
func (receiver *API) DeleteDirectAccount(ctx echo.Context, accountId string) error {
return receiver.accountController.RemoveDirectAccount(ctx, accountId)
func (receiver *API) DeleteDirectAccount(ctx echo.Context, userID string) error {
return receiver.AccountController.RemoveDirectAccount(ctx, userID)
}
func (receiver *API) GetDirectAccount(ctx echo.Context, accountId string) error {
return receiver.accountController.GetDirectAccount(ctx, accountId)
func (receiver *API) GetDirectAccount(ctx echo.Context, userID string) error {
return receiver.AccountController.GetDirectAccount(ctx, userID)
}
func (receiver *API) PaginationAccounts(ctx echo.Context, params PaginationAccountsParams) error {
return receiver.accountController.GetAccounts(ctx, params)
return receiver.AccountController.GetAccounts(ctx, params)
}
func (receiver *API) RemoveFromCart(ctx echo.Context, params RemoveFromCartParams) error {

@ -0,0 +1,20 @@
package swagger
import (
"github.com/deepmap/oapi-codegen/pkg/middleware"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/labstack/echo/v4"
)
func CreateMiddleware(swagger *openapi3.T, authenticationFunc openapi3filter.AuthenticationFunc) echo.MiddlewareFunc {
validator := middleware.OapiRequestValidatorWithOptions(swagger,
&middleware.Options{
Options: openapi3filter.Options{
AuthenticationFunc: authenticationFunc,
},
},
)
return validator
}

@ -16,19 +16,22 @@ const (
prefix = "Bearer "
)
func NewAuthenticator(jwtUtil *JWT[models.AuthJWTDecoded]) openapi3filter.AuthenticationFunc {
func NewAuthenticator(jwtUtil *JWT) openapi3filter.AuthenticationFunc {
return func(ctx context.Context, input *openapi3filter.AuthenticationInput) error {
if jwtUtil == nil {
return errors.New(fmt.Errorf("jwt util is nil"), errors.ErrInvalidArgs).Extract()
return errors.New(
fmt.Errorf("jwt util is nil: %w", errors.ErrInvalidArgs),
errors.ErrInvalidArgs,
).Extract()
}
return authenticate(jwtUtil, ctx, input)
return authenticate(ctx, jwtUtil, input)
}
}
func authenticate(jwtUtil *JWT[models.AuthJWTDecoded], ctx context.Context, input *openapi3filter.AuthenticationInput) error {
if input.SecuritySchemeName != "BearerAuth" {
return fmt.Errorf("security scheme %s != 'BearerAuth'", input.SecuritySchemeName)
func authenticate(ctx context.Context, jwtUtil *JWT, input *openapi3filter.AuthenticationInput) error {
if input.SecuritySchemeName != "Bearer" {
return fmt.Errorf("security scheme %s != 'Bearer'", input.SecuritySchemeName)
}
// Now, we need to get the JWS from the request, to match the request expectations
@ -48,12 +51,12 @@ func authenticate(jwtUtil *JWT[models.AuthJWTDecoded], ctx context.Context, inpu
// access the claims data we generate in here.
echoCtx := middleware.GetEchoContext(ctx)
echoCtx.Set(models.AuthJWTDecodedUserIDKey, token.UserID)
echoCtx.Set(models.AuthJWTDecodedUserIDKey, token)
return nil
}
// extracts a JWS string from an Authorization: Bearer <jws> header
// extracts a JWS string from an Authorization: Bearer <jws> header.
func parseJWSFromRequest(request *http.Request) (string, errors.Error) {
header := request.Header.Get("Authorization")

@ -14,6 +14,7 @@ var httpStatuses = map[errors.ErrorType]int{
errors.ErrNoAccess: http.StatusForbidden,
errors.ErrNotFound: http.StatusNotFound,
errors.ErrMethodNotImplemented: http.StatusNotImplemented,
errors.ErrConflict: http.StatusConflict,
}
func DetermineEchoErrorResponse(ctx echo.Context, err errors.Error) error {

@ -1,18 +1,15 @@
package utils
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
jsonUtil "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/json"
)
type JWT[T any] struct {
type JWT struct {
privateKey []byte
publicKey []byte
algorithm *jwt.SigningMethodRSA
@ -21,8 +18,8 @@ type JWT[T any] struct {
audience string
}
func NewJWT[T any](configuration *models.JWTConfiguration) *JWT[T] {
return &JWT[T]{
func NewJWT(configuration *models.JWTConfiguration) *JWT {
return &JWT{
privateKey: []byte(configuration.PrivateKey),
publicKey: []byte(configuration.PublicKey),
algorithm: &configuration.Algorithm,
@ -32,38 +29,10 @@ func NewJWT[T any](configuration *models.JWTConfiguration) *JWT[T] {
}
}
func (receiver *JWT[T]) Create(content *T) (string, error) {
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(receiver.privateKey)
if err != nil {
return "", fmt.Errorf("failed to parse private key on <Create> of <JWT>: %w", err)
}
encoded, err := json.Marshal(content)
if err != nil {
return "", fmt.Errorf("failed to encode content to json on <Create> of <JWT>: %w", err)
}
now := time.Now().UTC()
claims := jwt.MapClaims{
"dat": string(encoded), // Our custom data.
"exp": now.Add(receiver.expiresIn).Unix(), // The expiration time after which the token must be disregarded.
"aud": receiver.audience, // Audience
"iss": receiver.issuer, // Issuer
}
token, err := jwt.NewWithClaims(receiver.algorithm, claims).SignedString(privateKey)
if err != nil {
return "", fmt.Errorf("failed to sing on <Create> of <JWT>: %w", err)
}
return token, nil
}
func (receiver *JWT[T]) Validate(tokenString string) (*T, error) {
func (receiver *JWT) Validate(tokenString string) (string, error) {
key, err := jwt.ParseRSAPublicKeyFromPEM(receiver.publicKey)
if err != nil {
return nil, fmt.Errorf("failed to parse rsa public key on <Validate> of <JWT>: %w", err)
return "", fmt.Errorf("failed to parse rsa public key on <Validate> of <JWT>: %w", err)
}
parseCallback := func(token *jwt.Token) (any, error) {
@ -81,23 +50,18 @@ func (receiver *JWT[T]) Validate(tokenString string) (*T, error) {
jwt.WithIssuer(receiver.issuer),
)
if err != nil {
return nil, fmt.Errorf("failed to parse jwt token on <Validate> of <JWT>: %w", err)
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 nil, errors.New("token is invalid on <Validate> of <JWT>")
return "", errors.New("token is invalid on <Validate> of <JWT>")
}
data, ok := claims["dat"].(string)
data, ok := claims["id"].(string)
if !ok {
return nil, errors.New("data is empty or not a string on <Validate> of <JWT>")
return "", errors.New("data is empty or not a string on <Validate> of <JWT>")
}
parsedData, err := jsonUtil.Parse[T](strings.NewReader(data))
if err != nil {
return nil, fmt.Errorf("failed to parse data on <Validate> of <JWT>: %w", err)
}
return parsedData, nil
return data, nil
}

@ -1,129 +0,0 @@
package utils_test
import (
"strings"
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/utils"
)
func TestJWT(t *testing.T) {
type user struct {
Name string `json:"name"`
Age int `json:"age"`
}
testUser := user{
Name: "test",
Age: 80,
}
publicKey := `-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyt4XuLovUY7i12K2PIMb
QZOKn+wFFKUvxvKQDel049/+VMpHMx1FLolUKuyGp9zi6gOwjHsBPgc9oqr/eaXG
QSh7Ult7i9f+Ht563Y0er5UU9Zc5ZPSxf9O75KYD48ruGkqiFoncDqPENK4dtUa7
w0OqlN4bwVBbmIsP8B3EDC5Dof+vtiNTSHSXPx+zifKeZGyknp+nyOHVrRDhPjOh
zQzCom0MSZA/sJYmps8QZgiPA0k4Z6jTupDymPOIwYeD2C57zSxnAv0AfC3/pZYJ
bZYH/0TszRzmy052DME3zMnhMK0ikdN4nzYqU0dkkA5kb5GtKDymspHIJ9eWbUuw
gtg8Rq/LrVBj1I3UFgs0ibio40k6gqinLKslc5Y1I5mro7J3OSEP5eO/XeDLOLlO
JjEqkrx4fviI1cL3m5L6QV905xmcoNZG1+RmOg7D7cZQUf27TXqM381jkbNdktm1
JLTcMScxuo3vaRftnIVw70V8P8sIkaKY8S8HU1sQgE2LB9t04oog5u59htx2FHv4
B13NEm8tt8Tv1PexpB4UVh7PIualF6SxdFBrKbraYej72wgjXVPQ0eGXtGGD57j8
DUEzk7DK2OvIWhehlVqtiRnFdAvdBj2ynHT2/5FJ/Zpd4n5dKGJcQvy1U1qWMs+8
M7AHfWyt2+nZ04s48+bK3yMCAwEAAQ==
-----END PUBLIC KEY-----`
privateKey := `-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAyt4XuLovUY7i12K2PIMbQZOKn+wFFKUvxvKQDel049/+VMpH
Mx1FLolUKuyGp9zi6gOwjHsBPgc9oqr/eaXGQSh7Ult7i9f+Ht563Y0er5UU9Zc5
ZPSxf9O75KYD48ruGkqiFoncDqPENK4dtUa7w0OqlN4bwVBbmIsP8B3EDC5Dof+v
tiNTSHSXPx+zifKeZGyknp+nyOHVrRDhPjOhzQzCom0MSZA/sJYmps8QZgiPA0k4
Z6jTupDymPOIwYeD2C57zSxnAv0AfC3/pZYJbZYH/0TszRzmy052DME3zMnhMK0i
kdN4nzYqU0dkkA5kb5GtKDymspHIJ9eWbUuwgtg8Rq/LrVBj1I3UFgs0ibio40k6
gqinLKslc5Y1I5mro7J3OSEP5eO/XeDLOLlOJjEqkrx4fviI1cL3m5L6QV905xmc
oNZG1+RmOg7D7cZQUf27TXqM381jkbNdktm1JLTcMScxuo3vaRftnIVw70V8P8sI
kaKY8S8HU1sQgE2LB9t04oog5u59htx2FHv4B13NEm8tt8Tv1PexpB4UVh7PIual
F6SxdFBrKbraYej72wgjXVPQ0eGXtGGD57j8DUEzk7DK2OvIWhehlVqtiRnFdAvd
Bj2ynHT2/5FJ/Zpd4n5dKGJcQvy1U1qWMs+8M7AHfWyt2+nZ04s48+bK3yMCAwEA
AQKCAgEAhodpK7MsFeWvQC3Rs6ctt/rjftHBPMOeP0wzg0ZBoau0uP264YaTjhy7
mAtp8H9matEvjrkzRbL/iJPk/wKTyjnSLfdEoqQFfOsEh09B/iXa1FIIWY569s2u
WB5PjgvQgdbkThX1vC+VuWmNgdz6Pq7su/Pea/+h/jKZyx2yGHHFn/QyzZH3dKD8
e3vGT8B4kRgKwrYVSf2Y+T+sXtdWgOfpWlT+RPpHgg7QauX9dexPClrP8M3gOmRM
vGkjU1NOd1m7939ugGjOnYrTcTdh4S4Q95L5hbuYwVGyrxqiqkdl8iWeOx4Fa287
+iXp5i3lJKdyMLCnytsp5GHu+2OqFKyYQli23eMEEiTq/7PrzJas0BD3LfuT55Ht
UCwI/pRdgHvc/xEHqr7eF0C3f+PPG9/C85StDbm9WqhCVQ9nGt2ezkLeUSM/DBAh
DgI/LDFqRwLlIDrhkTT7BJGz6+2cmHwV80+eGPG2WzjpI619qhqgqB0fGBjLlVcZ
qoHy0K6NXuBqaoPOQq0TGkhl3SjurSe9EXeZHrrCT3LcSAIT7ZYoZDYuIvKBj7Sh
7r/wdYS9nzsBhU0xeGzfAs+5yxDCp1/GzLK0H8LlJcjJOxqArtEzf55v7ZBB8erR
sqmbpGoQAwzwyw1zosmhzQwZRlAMPNi0yfnjfi8yQu4kZchyJyECggEBAOStATj0
JNYWrPoHSgdW+NkzMRNIjjkHkUM/zs9F1bIlYwYDzmduXUoLChCHcjbOyE2tsPi8
eFbyJ0vpMa0ZgoQmAnqUhYOwceu/tmI2CE7jLB2luq9oFhQIblKR6Fi8TyvPzn4N
Q4iD1I2VjffSSQher+hNVdLmpRkP8s2UiY7OQOZMBWKNqfORddQWcXp3Wrg2Lkbd
7KcAtaMLYWg2W3mRdz6dnsqjMomRMi5arhroG3CtIpb62uiEdq2ZwyGF/Awon/kr
/XnfRLQeH0xVFPuVS/EbP6Ipq0TiieElTh4erhUIbmLZg7B5Fe9z1c528GUzTxhP
geQwN3bS5q71/f8CggEBAOMbosN7S+merANPzCOnRruLDPXukW+u20t/8CrOibJM
MO0embITOJfEdG4jBVRwnm5qacojuzFwfD7C18fJ1Hty010yQkjnB/zch3i8Fjx1
vtsWnYOfbViuIzuEi+9bPWRlMZh504zDjgqo8P24JU5qziw/ySLfMZAX7iNsohRB
R+bBdP933kPoCo5ehSj4QyVgRIWN751x5sZ0eyCUTZIw9OswuOmsmnlw4nMsqWIx
OXlARVkbA97+1pp21pAromekE/bzN8Qo4pn4inZTTy9yAeAvSp+vScCiaVJ4n+ag
WAgLeQBLxqRCU6BMvKiRjQ8dBMAn1DjKCrlV+5zFZt0CggEAd8TZEBBnPq4vuOCa
eE+oFHKIcJYez2XUQkmoMs1byGtmet8BexDF0aMIiXG3c1dId87SEuT7jmZUCKFB
gG0M+9PAlp01dKy0bgpCJxwvq8m18G094uL8NU/ZIGwFKnyuZr73YvPlfBm3+NPs
wHCmCbk2HtBqdASTUhYVUHFMvrvuJ/CHHYAfFFAKS6PZmY/rtvHBuSJA8ZMgjx3F
zcQykvCKaQQ7B90D+iNPChI6gCMzRAeaR0Np5kCCvBf9qJA5W9DnQKU2pF8457Gj
KOKjE8W1ObnQ0UlLx89y8bYNPR9Kg/+feSx9ma9BuuGLiRCohgiik5QI7xAF7Lk3
U0nJ1wKCAQAmkbjwre3UfSgFX/XxUCVJEHJhCeUVLIL9rXqiKnVkHGBqxLmhbnY8
ABct5TCwiHe/lL7mn27ZFJtlJT30Jii51mRi/XgYXXQT03gGXxr/pZeGKa8SfW7a
kqhVIUuKmNoyRKVJmdb9nvBuiwZycGWVjbn59dM44uLN7+J3jalw+y002UH/aOIM
cknop9DBhngQzuqUK+i3unJQ3dNTUxxhaYMOtjWRKckKOsuad8lEbcuu9eVRHq9n
navgi7IgxehM5aamV+PuomrpbzZEph1al2gOJLntqJ1D49EzOl0dk7mflCM2k6fm
mYUOQjn//sgP+wOlhp4aDuYHV7zlgPjZAoIBAQDXPUl6NeA2ZMWbSO+WRc8zzjQ9
qyxRA7g3ZSu+E5OqkxfwayXr/kAVKQNHJvn5wr9rLFhEF6CkBJ7XgOrHN0RjgXq2
z0DpwG5JEFMeqkQWI+rVJ+ZJ4g0SAa9k39+WDxQhpZM8/IlkuIYqRI0mlcHwxhkG
7JhkLtELhlxaGobAIinWiskKqX85tzZtCLe1wkErWOCueWviiuoCY2HWfELoA5+4
wAvKspBO6oa+R2JtjA0nE72jKWuIz4m0QaCE7yInyCG9ikrBHSh/85eMu37nqegU
ziOydfDNcQp17fBjy8NVeQBjdjxVYejl8pKAVcQP9iM4vIyRIx0Ersv1fySA
-----END RSA PRIVATE KEY-----`
publicKey = strings.Replace(publicKey, "\t", "", -1)
privateKey = strings.Replace(privateKey, "\t", "", -1)
jwt := utils.NewJWT[user](&models.JWTConfiguration{
PrivateKey: privateKey,
PublicKey: publicKey,
Algorithm: *jwt.SigningMethodRS256,
ExpiresIn: 15 * time.Minute,
Issuer: "issuer1",
Audience: "audience1",
})
t.Run("Успешная генерация токена", func(t *testing.T) {
assert.NotPanics(t, func() {
token, err := jwt.Create(&testUser)
assert.NoError(t, err)
assert.NotZero(t, token)
assert.NotEmpty(t, token)
})
})
t.Run("Успешная валидация токена", func(t *testing.T) {
assert.NotPanics(t, func() {
token, err := jwt.Create(&testUser)
isNoError := assert.NoError(t, err)
if isNoError {
parsedUser, err := jwt.Validate(token)
assert.NoError(t, err)
assert.NotNil(t, parsedUser)
assert.Equal(t, &testUser, parsedUser)
}
})
})
}

@ -24,7 +24,7 @@ func DeterminePagination(page, limit interface{}) *models.Pagination {
limitNumber, isLimitNumberOK := limit.(int64)
if !isLimitNumberOK || limitNumber > models.DefaultLimit {
if !isLimitNumberOK || limitNumber > models.DefaultLimit || limitNumber < 1 {
return models.DefaultLimit
}