generated from PenaSide/GolangTemplate
feat: account crud complete & errors wrapper & security
This commit is contained in:
parent
7060ec31f7
commit
a57d1db0ab
32
.env.test
32
.env.test
@ -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
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
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 {
|
||||
|
9
internal/initialize/api.go
Normal file
9
internal/initialize/api.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
49
internal/initialize/api_test.go
Normal file
49
internal/initialize/api_test.go
Normal file
@ -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 {
|
||||
|
20
internal/swagger/middleware.go
Normal file
20
internal/swagger/middleware.go
Normal file
@ -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-----
|
||||
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
|
||||
-----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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user