From dc408d604e636d458af37feb2af71977edc5aa53 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 11:36:14 +0200 Subject: [PATCH 01/90] update mockery version --- mocks/configuration/ConfigurationHolder.go | 2 +- mocks/configuration/DatabaseConfiguration.go | 2 +- .../configuration/HTTPServerConfiguration.go | 2 +- .../InitializationConfiguration.go | 2 +- mocks/configuration/LoggerConfiguration.go | 2 +- .../configuration/PaginationConfiguration.go | 2 +- mocks/configuration/SessionConfiguration.go | 2 +- mocks/controllers/InformationController.go | 7 +++-- mocks/httperrors/HTTPError.go | 2 +- mocks/persistence/models/Tabler.go | 2 +- mocks/persistence/pagination/Paginator.go | 2 +- .../persistence/repository/CRUDRepository.go | 27 ++++++++++++++----- mocks/persistence/repository/SortOption.go | 2 +- .../middlewares/AuthenticationMiddleware.go | 2 +- mocks/router/middlewares/JSONController.go | 2 +- mocks/router/middlewares/JSONHandler.go | 7 +++-- mocks/router/middlewares/MiddlewareLogger.go | 2 +- .../services/sessionservice/SessionService.go | 7 +++-- mocks/services/userservice/UserService.go | 12 ++++++--- 19 files changed, 59 insertions(+), 29 deletions(-) diff --git a/mocks/configuration/ConfigurationHolder.go b/mocks/configuration/ConfigurationHolder.go index f6475bb1..4a047153 100644 --- a/mocks/configuration/ConfigurationHolder.go +++ b/mocks/configuration/ConfigurationHolder.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/DatabaseConfiguration.go b/mocks/configuration/DatabaseConfiguration.go index a210f186..51f0f487 100644 --- a/mocks/configuration/DatabaseConfiguration.go +++ b/mocks/configuration/DatabaseConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/HTTPServerConfiguration.go b/mocks/configuration/HTTPServerConfiguration.go index 77cb41e5..f70cf767 100644 --- a/mocks/configuration/HTTPServerConfiguration.go +++ b/mocks/configuration/HTTPServerConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/InitializationConfiguration.go b/mocks/configuration/InitializationConfiguration.go index 58bdec3b..303c62b7 100644 --- a/mocks/configuration/InitializationConfiguration.go +++ b/mocks/configuration/InitializationConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/LoggerConfiguration.go b/mocks/configuration/LoggerConfiguration.go index 2115ec7e..88682c1c 100644 --- a/mocks/configuration/LoggerConfiguration.go +++ b/mocks/configuration/LoggerConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/PaginationConfiguration.go b/mocks/configuration/PaginationConfiguration.go index 9146b875..7db54091 100644 --- a/mocks/configuration/PaginationConfiguration.go +++ b/mocks/configuration/PaginationConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/configuration/SessionConfiguration.go b/mocks/configuration/SessionConfiguration.go index 2d5e9ee7..e107947d 100644 --- a/mocks/configuration/SessionConfiguration.go +++ b/mocks/configuration/SessionConfiguration.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/controllers/InformationController.go b/mocks/controllers/InformationController.go index 51eeffef..4a82f694 100644 --- a/mocks/controllers/InformationController.go +++ b/mocks/controllers/InformationController.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -19,6 +19,10 @@ func (_m *InformationController) Info(response http.ResponseWriter, r *http.Requ ret := _m.Called(response, r) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(response, r) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(response, r) } else { @@ -27,7 +31,6 @@ func (_m *InformationController) Info(response http.ResponseWriter, r *http.Requ } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(response, r) } else { diff --git a/mocks/httperrors/HTTPError.go b/mocks/httperrors/HTTPError.go index cbe71e52..3bae2ab3 100644 --- a/mocks/httperrors/HTTPError.go +++ b/mocks/httperrors/HTTPError.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/persistence/models/Tabler.go b/mocks/persistence/models/Tabler.go index cbc8e129..6a50e56e 100644 --- a/mocks/persistence/models/Tabler.go +++ b/mocks/persistence/models/Tabler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/persistence/pagination/Paginator.go b/mocks/persistence/pagination/Paginator.go index dabbbf23..ef2c6359 100644 --- a/mocks/persistence/pagination/Paginator.go +++ b/mocks/persistence/pagination/Paginator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/persistence/repository/CRUDRepository.go b/mocks/persistence/repository/CRUDRepository.go index 31f127b1..eeaecbbb 100644 --- a/mocks/persistence/repository/CRUDRepository.go +++ b/mocks/persistence/repository/CRUDRepository.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -25,13 +25,16 @@ func (_m *CRUDRepository[T, ID]) Count(_a0 squirrel.Sqlizer) (uint, httperrors.H ret := _m.Called(_a0) var r0 uint + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(squirrel.Sqlizer) (uint, httperrors.HTTPError)); ok { + return rf(_a0) + } if rf, ok := ret.Get(0).(func(squirrel.Sqlizer) uint); ok { r0 = rf(_a0) } else { r0 = ret.Get(0).(uint) } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(squirrel.Sqlizer) httperrors.HTTPError); ok { r1 = rf(_a0) } else { @@ -80,6 +83,10 @@ func (_m *CRUDRepository[T, ID]) Find(_a0 squirrel.Sqlizer, _a1 pagination.Pagin ret := _m.Called(_a0, _a1, _a2) var r0 *pagination.Page[T] + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) (*pagination.Page[T], httperrors.HTTPError)); ok { + return rf(_a0, _a1, _a2) + } if rf, ok := ret.Get(0).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) *pagination.Page[T]); ok { r0 = rf(_a0, _a1, _a2) } else { @@ -88,7 +95,6 @@ func (_m *CRUDRepository[T, ID]) Find(_a0 squirrel.Sqlizer, _a1 pagination.Pagin } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) httperrors.HTTPError); ok { r1 = rf(_a0, _a1, _a2) } else { @@ -105,6 +111,10 @@ func (_m *CRUDRepository[T, ID]) GetAll(_a0 repository.SortOption) ([]*T, httper ret := _m.Called(_a0) var r0 []*T + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(repository.SortOption) ([]*T, httperrors.HTTPError)); ok { + return rf(_a0) + } if rf, ok := ret.Get(0).(func(repository.SortOption) []*T); ok { r0 = rf(_a0) } else { @@ -113,7 +123,6 @@ func (_m *CRUDRepository[T, ID]) GetAll(_a0 repository.SortOption) ([]*T, httper } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(repository.SortOption) httperrors.HTTPError); ok { r1 = rf(_a0) } else { @@ -130,6 +139,10 @@ func (_m *CRUDRepository[T, ID]) GetByID(_a0 ID) (*T, httperrors.HTTPError) { ret := _m.Called(_a0) var r0 *T + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(ID) (*T, httperrors.HTTPError)); ok { + return rf(_a0) + } if rf, ok := ret.Get(0).(func(ID) *T); ok { r0 = rf(_a0) } else { @@ -138,7 +151,6 @@ func (_m *CRUDRepository[T, ID]) GetByID(_a0 ID) (*T, httperrors.HTTPError) { } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(ID) httperrors.HTTPError); ok { r1 = rf(_a0) } else { @@ -171,6 +183,10 @@ func (_m *CRUDRepository[T, ID]) Transaction(fn func(repository.CRUDRepository[T ret := _m.Called(fn) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) (interface{}, httperrors.HTTPError)); ok { + return rf(fn) + } if rf, ok := ret.Get(0).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) interface{}); ok { r0 = rf(fn) } else { @@ -179,7 +195,6 @@ func (_m *CRUDRepository[T, ID]) Transaction(fn func(repository.CRUDRepository[T } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) httperrors.HTTPError); ok { r1 = rf(fn) } else { diff --git a/mocks/persistence/repository/SortOption.go b/mocks/persistence/repository/SortOption.go index d2e10e83..807bd26c 100644 --- a/mocks/persistence/repository/SortOption.go +++ b/mocks/persistence/repository/SortOption.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/router/middlewares/AuthenticationMiddleware.go b/mocks/router/middlewares/AuthenticationMiddleware.go index a0a4053f..77b8fe2d 100644 --- a/mocks/router/middlewares/AuthenticationMiddleware.go +++ b/mocks/router/middlewares/AuthenticationMiddleware.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/router/middlewares/JSONController.go b/mocks/router/middlewares/JSONController.go index b1ad06cb..5fe7aa5a 100644 --- a/mocks/router/middlewares/JSONController.go +++ b/mocks/router/middlewares/JSONController.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/router/middlewares/JSONHandler.go b/mocks/router/middlewares/JSONHandler.go index 98dd567b..37a072cb 100644 --- a/mocks/router/middlewares/JSONHandler.go +++ b/mocks/router/middlewares/JSONHandler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -20,6 +20,10 @@ func (_m *JSONHandler) Execute(w http.ResponseWriter, r *http.Request) (interfac ret := _m.Called(w, r) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(w, r) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(w, r) } else { @@ -28,7 +32,6 @@ func (_m *JSONHandler) Execute(w http.ResponseWriter, r *http.Request) (interfac } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(w, r) } else { diff --git a/mocks/router/middlewares/MiddlewareLogger.go b/mocks/router/middlewares/MiddlewareLogger.go index 671b8ca8..f18920a8 100644 --- a/mocks/router/middlewares/MiddlewareLogger.go +++ b/mocks/router/middlewares/MiddlewareLogger.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks diff --git a/mocks/services/sessionservice/SessionService.go b/mocks/services/sessionservice/SessionService.go index 22b49694..101cfba9 100644 --- a/mocks/services/sessionservice/SessionService.go +++ b/mocks/services/sessionservice/SessionService.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -25,13 +25,16 @@ func (_m *SessionService) IsValid(sessionUUID uuid.UUID) (bool, *sessionservice. ret := _m.Called(sessionUUID) var r0 bool + var r1 *sessionservice.SessionClaims + if rf, ok := ret.Get(0).(func(uuid.UUID) (bool, *sessionservice.SessionClaims)); ok { + return rf(sessionUUID) + } if rf, ok := ret.Get(0).(func(uuid.UUID) bool); ok { r0 = rf(sessionUUID) } else { r0 = ret.Get(0).(bool) } - var r1 *sessionservice.SessionClaims if rf, ok := ret.Get(1).(func(uuid.UUID) *sessionservice.SessionClaims); ok { r1 = rf(sessionUUID) } else { diff --git a/mocks/services/userservice/UserService.go b/mocks/services/userservice/UserService.go index 6ce9b7fa..bd6bb257 100644 --- a/mocks/services/userservice/UserService.go +++ b/mocks/services/userservice/UserService.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -21,6 +21,10 @@ func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, httperrors.H ret := _m.Called(_a0) var r0 *models.User + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(dto.UserLoginDTO) (*models.User, httperrors.HTTPError)); ok { + return rf(_a0) + } if rf, ok := ret.Get(0).(func(dto.UserLoginDTO) *models.User); ok { r0 = rf(_a0) } else { @@ -29,7 +33,6 @@ func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, httperrors.H } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(dto.UserLoginDTO) httperrors.HTTPError); ok { r1 = rf(_a0) } else { @@ -46,6 +49,10 @@ func (_m *UserService) NewUser(username string, email string, password string) ( ret := _m.Called(username, email, password) var r0 *models.User + var r1 error + if rf, ok := ret.Get(0).(func(string, string, string) (*models.User, error)); ok { + return rf(username, email, password) + } if rf, ok := ret.Get(0).(func(string, string, string) *models.User); ok { r0 = rf(username, email, password) } else { @@ -54,7 +61,6 @@ func (_m *UserService) NewUser(username string, email string, password string) ( } } - var r1 error if rf, ok := ret.Get(1).(func(string, string, string) error); ok { r1 = rf(username, email, password) } else { From e2c07761f3b952dcfd7fd2d7d001289e0a746a5e Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 16:21:24 +0200 Subject: [PATCH 02/90] fix typos in Authentication, resource and constructor --- CONTRIBUTING.md | 2 +- README.md | 2 +- changelog.md | 2 +- controllers/ModuleFx.go | 2 +- controllers/basicAuth.go | 26 ++++++++--------- controllers/basicAuth_test.go | 8 +++--- features/basic_auth.feature | 4 +-- httperrors/httperrors.go | 10 +++---- ...er.go => BasicAuthenticationController.go} | 28 +++++++++++-------- persistence/repository/CRUDRepositoryImpl.go | 10 +++---- ...ication.go => middlewareAuthentication.go} | 2 +- router/router.go | 11 ++++---- router/router_test.go | 7 +++-- services/sessionservice/session.go | 2 +- 14 files changed, 61 insertions(+), 55 deletions(-) rename mocks/controllers/{BasicAuthentificationController.go => BasicAuthenticationController.go} (53%) rename router/middlewares/{middlewareAuthentification.go => middlewareAuthentication.go} (94%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d252da0..de552170 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,7 +68,7 @@ This is the directory structure we use for the project: - `/db/` : Contains the Dockerfile to build a developpement version of CockroachDB. - `services/` *(Go code)*: Contains the Dockerfile to build a developpement version of CockroachDB. - `/auth/protocols/`: Contains the implementations of authentication clients for differents protocols. - - `/basicauth/` *(Go code)*: Handle the authentification using email/password. + - `/basicauth/` *(Go code)*: Handle the authentication using email/password. - `/oidc/` *(Go code)*: Handle the authentication via Open-ID Connect. - `/sessionservice/` *(Go code)*: Handle sessions and their lifecycle. - `/userservice/` *(Go code)*: Handle users. diff --git a/README.md b/README.md index 15ade58d..ce6d3c8f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Badaas enables the effortless construction of ***distributed, resilient, highly Badaas provides several key features: -- **Authentification**: Badaas can authentify users using its internal authentification scheme or externally by using protocols such as OIDC, SAML, Oauth2... +- **Authentication**: Badaas can authenticate users using its internal authentication scheme or externally by using protocols such as OIDC, SAML, Oauth2... - **Habilitation**: On a resource access, Badaas will check if the user is authorized using a RBAC model. - **Distribution**: Badaas is built to run in clusters by default. Communications between nodes are TLS encrypted using [shoset](https://github.com/ditrit/shoset). - **Persistence**: Applicative objects are persisted as well as user files. Those resources are shared accross the clusters to increase resiliency. diff --git a/changelog.md b/changelog.md index 866b2573..e62f794b 100644 --- a/changelog.md +++ b/changelog.md @@ -26,7 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `init` flag to migrate database and create admin user. - Add a CONTRIBUTING.md and a documentation file for configuration (configuration.md) - Add a session services. -- Add a basic authentification controller. +- Add a basic authentication controller. - Now config keys are only declared once with constants in the `configuration/` package. - Add a dto that is returned on a successful login. - Update verdeter to version v0.4.0 diff --git a/controllers/ModuleFx.go b/controllers/ModuleFx.go index ccab54eb..adfff9ec 100644 --- a/controllers/ModuleFx.go +++ b/controllers/ModuleFx.go @@ -6,5 +6,5 @@ import "go.uber.org/fx" var ControllerModule = fx.Module( "controllers", fx.Provide(NewInfoController), - fx.Provide(NewBasicAuthentificationController), + fx.Provide(NewBasicAuthenticationController), ) diff --git a/controllers/basicAuth.go b/controllers/basicAuth.go index d98c4de5..3a65cfb5 100644 --- a/controllers/basicAuth.go +++ b/controllers/basicAuth.go @@ -18,32 +18,31 @@ var ( "Request malformed", "The schema of the received data is not correct", nil, - false) + false, + ) ) -// Basic Authentification Controller -type BasicAuthentificationController interface { +type BasicAuthenticationController interface { BasicLoginHandler(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) Logout(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) } // Check interface compliance -var _ BasicAuthentificationController = (*basicAuthentificationController)(nil) +var _ BasicAuthenticationController = (*basicAuthenticationController)(nil) -// BasicAuthentificationController implementation -type basicAuthentificationController struct { +type basicAuthenticationController struct { logger *zap.Logger userService userservice.UserService sessionService sessionservice.SessionService } -// BasicAuthentificationController contructor -func NewBasicAuthentificationController( +// BasicAuthenticationController constructor +func NewBasicAuthenticationController( logger *zap.Logger, userService userservice.UserService, sessionService sessionservice.SessionService, -) BasicAuthentificationController { - return &basicAuthentificationController{ +) BasicAuthenticationController { + return &basicAuthenticationController{ logger: logger, userService: userService, sessionService: sessionService, @@ -51,7 +50,7 @@ func NewBasicAuthentificationController( } // Log In with username and password -func (basicAuthController *basicAuthentificationController) BasicLoginHandler(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { +func (basicAuthController *basicAuthenticationController) BasicLoginHandler(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { var loginJSONStruct dto.UserLoginDTO err := json.NewDecoder(r.Body).Decode(&loginJSONStruct) if err != nil { @@ -77,7 +76,6 @@ func (basicAuthController *basicAuthentificationController) BasicLoginHandler(w } // Log Out the user -func (basicAuthController *basicAuthentificationController) Logout(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { - basicAuthController.sessionService.LogUserOut(sessionservice.GetSessionClaimsFromContext(r.Context()), w) - return nil, nil +func (basicAuthController *basicAuthenticationController) Logout(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + return nil, basicAuthController.sessionService.LogUserOut(sessionservice.GetSessionClaimsFromContext(r.Context()), w) } diff --git a/controllers/basicAuth_test.go b/controllers/basicAuth_test.go index 2fecf795..6e337939 100644 --- a/controllers/basicAuth_test.go +++ b/controllers/basicAuth_test.go @@ -24,7 +24,7 @@ func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { userService := mocksUserService.NewUserService(t) sessionService := mocksSessionService.NewSessionService(t) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, @@ -55,7 +55,7 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { Return(nil, httperrors.AnError) sessionService := mocksSessionService.NewSessionService(t) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, @@ -107,7 +107,7 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { On("LogUserIn", user, response). Return(httperrors.AnError) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, @@ -152,7 +152,7 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { On("LogUserIn", user, response). Return(nil) - controller := controllers.NewBasicAuthentificationController( + controller := controllers.NewBasicAuthenticationController( logger, userService, sessionService, diff --git a/features/basic_auth.feature b/features/basic_auth.feature index ab06d86a..d1371744 100644 --- a/features/basic_auth.feature +++ b/features/basic_auth.feature @@ -1,4 +1,4 @@ -Feature: Login as superadmin using the basic authentification +Feature: Login as superadmin using the basic authentication Scenario: Should be a success on valid credentials Given I request "/login" with method "POST" with json @@ -29,6 +29,6 @@ Feature: Login as superadmin using the basic authentification Scenario: Should be an error if we try to logout without login first When I request "/logout" Then I expect status code is "401" - And I expect response field "err" is "Authentification Error" + And I expect response field "err" is "Authentication Error" And I expect response field "msg" is "not authenticated" And I expect response field "status" is "Unauthorized" diff --git a/httperrors/httperrors.go b/httperrors/httperrors.go index c5a02ecf..69c03c6f 100644 --- a/httperrors/httperrors.go +++ b/httperrors/httperrors.go @@ -93,18 +93,18 @@ func NewHTTPError(status int, err string, message string, golangError error, toL } } -// A contructor for an HttpError "Not Found" -func NewErrorNotFound(ressourceName string, msg string) HTTPError { +// A constructor for an HttpError "Not Found" +func NewErrorNotFound(resourceName string, msg string) HTTPError { return NewHTTPError( http.StatusNotFound, - fmt.Sprintf("%s not found", ressourceName), + fmt.Sprintf("%s not found", resourceName), msg, nil, false, ) } -// A contructor for an HttpError "Internal Server Error" +// A constructor for an HttpError "Internal Server Error" func NewInternalServerError(errorName string, msg string, err error) HTTPError { return NewHTTPError( http.StatusInternalServerError, @@ -115,7 +115,7 @@ func NewInternalServerError(errorName string, msg string, err error) HTTPError { ) } -// A contructor for an HttpError "Unauthorized Error" +// A constructor for an HttpError "Unauthorized Error" func NewUnauthorizedError(errorName string, msg string) HTTPError { return NewHTTPError( http.StatusUnauthorized, diff --git a/mocks/controllers/BasicAuthentificationController.go b/mocks/controllers/BasicAuthenticationController.go similarity index 53% rename from mocks/controllers/BasicAuthentificationController.go rename to mocks/controllers/BasicAuthenticationController.go index 19092bfa..5bf3c0b9 100644 --- a/mocks/controllers/BasicAuthentificationController.go +++ b/mocks/controllers/BasicAuthenticationController.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.20.0. DO NOT EDIT. package mocks @@ -9,16 +9,20 @@ import ( mock "github.com/stretchr/testify/mock" ) -// BasicAuthentificationController is an autogenerated mock type for the BasicAuthentificationController type -type BasicAuthentificationController struct { +// BasicAuthenticationController is an autogenerated mock type for the BasicAuthenticationController type +type BasicAuthenticationController struct { mock.Mock } // BasicLoginHandler provides a mock function with given fields: _a0, _a1 -func (_m *BasicAuthentificationController) BasicLoginHandler(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { +func (_m *BasicAuthenticationController) BasicLoginHandler(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { ret := _m.Called(_a0, _a1) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(_a0, _a1) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(_a0, _a1) } else { @@ -27,7 +31,6 @@ func (_m *BasicAuthentificationController) BasicLoginHandler(_a0 http.ResponseWr } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(_a0, _a1) } else { @@ -40,10 +43,14 @@ func (_m *BasicAuthentificationController) BasicLoginHandler(_a0 http.ResponseWr } // Logout provides a mock function with given fields: _a0, _a1 -func (_m *BasicAuthentificationController) Logout(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { +func (_m *BasicAuthenticationController) Logout(_a0 http.ResponseWriter, _a1 *http.Request) (interface{}, httperrors.HTTPError) { ret := _m.Called(_a0, _a1) var r0 interface{} + var r1 httperrors.HTTPError + if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) (interface{}, httperrors.HTTPError)); ok { + return rf(_a0, _a1) + } if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) interface{}); ok { r0 = rf(_a0, _a1) } else { @@ -52,7 +59,6 @@ func (_m *BasicAuthentificationController) Logout(_a0 http.ResponseWriter, _a1 * } } - var r1 httperrors.HTTPError if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) httperrors.HTTPError); ok { r1 = rf(_a0, _a1) } else { @@ -64,14 +70,14 @@ func (_m *BasicAuthentificationController) Logout(_a0 http.ResponseWriter, _a1 * return r0, r1 } -type mockConstructorTestingTNewBasicAuthentificationController interface { +type mockConstructorTestingTNewBasicAuthenticationController interface { mock.TestingT Cleanup(func()) } -// NewBasicAuthentificationController creates a new instance of BasicAuthentificationController. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewBasicAuthentificationController(t mockConstructorTestingTNewBasicAuthentificationController) *BasicAuthentificationController { - mock := &BasicAuthentificationController{} +// NewBasicAuthenticationController creates a new instance of BasicAuthenticationController. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewBasicAuthenticationController(t mockConstructorTestingTNewBasicAuthenticationController) *BasicAuthenticationController { + mock := &BasicAuthenticationController{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/persistence/repository/CRUDRepositoryImpl.go b/persistence/repository/CRUDRepositoryImpl.go index eca2365f..3aa84d1b 100644 --- a/persistence/repository/CRUDRepositoryImpl.go +++ b/persistence/repository/CRUDRepositoryImpl.go @@ -5,14 +5,15 @@ import ( "net/http" "github.com/Masterminds/squirrel" + "go.uber.org/zap" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "github.com/ditrit/badaas/configuration" "github.com/ditrit/badaas/httperrors" "github.com/ditrit/badaas/persistence/gormdatabase" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/pagination" - "go.uber.org/zap" - "gorm.io/gorm" - "gorm.io/gorm/clause" ) // Return a database error @@ -32,7 +33,7 @@ type CRUDRepositoryImpl[T models.Tabler, ID any] struct { paginationConfiguration configuration.PaginationConfiguration } -// Contructor of the Generic CRUD Repository +// Constructor of the Generic CRUD Repository func NewCRUDRepository[T models.Tabler, ID any]( database *gorm.DB, logger *zap.Logger, @@ -180,7 +181,6 @@ func (repository *CRUDRepositoryImpl[T, ID]) Find( defer func() { if recoveredError := recover(); recoveredError != nil { transaction.Rollback() - } }() var instances []*T diff --git a/router/middlewares/middlewareAuthentification.go b/router/middlewares/middlewareAuthentication.go similarity index 94% rename from router/middlewares/middlewareAuthentification.go rename to router/middlewares/middlewareAuthentication.go index aaf8d4b1..b1560dd5 100644 --- a/router/middlewares/middlewareAuthentification.go +++ b/router/middlewares/middlewareAuthentication.go @@ -10,7 +10,7 @@ import ( ) var ( - NotAuthenticated = httperrors.NewUnauthorizedError("Authentification Error", "not authenticated") + NotAuthenticated = httperrors.NewUnauthorizedError("Authentication Error", "not authenticated") ) // The authentication middleware diff --git a/router/router.go b/router/router.go index 8a3088cc..1a194074 100644 --- a/router/router.go +++ b/router/router.go @@ -3,20 +3,21 @@ package router import ( "net/http" + "github.com/gorilla/mux" + "github.com/ditrit/badaas/controllers" "github.com/ditrit/badaas/router/middlewares" - "github.com/gorilla/mux" ) // Default router of badaas, initialize all routes. func SetupRouter( - //middlewares + // middlewares jsonController middlewares.JSONController, middlewareLogger middlewares.MiddlewareLogger, authenticationMiddleware middlewares.AuthenticationMiddleware, // controllers - basicAuthentificationController controllers.BasicAuthentificationController, + basicAuthenticationController controllers.BasicAuthenticationController, informationController controllers.InformationController, ) http.Handler { router := mux.NewRouter() @@ -29,14 +30,14 @@ func SetupRouter( router.HandleFunc( "/login", jsonController.Wrap( - basicAuthentificationController.BasicLoginHandler, + basicAuthenticationController.BasicLoginHandler, ), ).Methods("POST") protected := router.PathPrefix("").Subrouter() protected.Use(authenticationMiddleware.Handle) - protected.HandleFunc("/logout", jsonController.Wrap(basicAuthentificationController.Logout)).Methods("GET") + protected.HandleFunc("/logout", jsonController.Wrap(basicAuthenticationController.Logout)).Methods("GET") return router } diff --git a/router/router_test.go b/router/router_test.go index da2f247a..6685708e 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -4,10 +4,11 @@ import ( "net/http" "testing" - controllersMocks "github.com/ditrit/badaas/mocks/controllers" - middlewaresMocks "github.com/ditrit/badaas/mocks/router/middlewares" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + + controllersMocks "github.com/ditrit/badaas/mocks/controllers" + middlewaresMocks "github.com/ditrit/badaas/mocks/router/middlewares" ) func TestSetupRouter(t *testing.T) { @@ -15,7 +16,7 @@ func TestSetupRouter(t *testing.T) { middlewareLogger := middlewaresMocks.NewMiddlewareLogger(t) authenticationMiddleware := middlewaresMocks.NewAuthenticationMiddleware(t) - basicController := controllersMocks.NewBasicAuthentificationController(t) + basicController := controllersMocks.NewBasicAuthenticationController(t) informationController := controllersMocks.NewInformationController(t) jsonController.On("Wrap", mock.Anything).Return(func(response http.ResponseWriter, request *http.Request) {}) router := SetupRouter(jsonController, middlewareLogger, authenticationMiddleware, basicController, informationController) diff --git a/services/sessionservice/session.go b/services/sessionservice/session.go index d4368319..b07839f6 100644 --- a/services/sessionservice/session.go +++ b/services/sessionservice/session.go @@ -228,7 +228,7 @@ func (sessionService *sessionServiceImpl) LogUserIn(user *models.User, response func (sessionService *sessionServiceImpl) LogUserOut(sessionClaims *SessionClaims, response http.ResponseWriter) httperrors.HTTPError { session := sessionService.get(sessionClaims.SessionUUID) if session == nil { - return httperrors.NewUnauthorizedError("Authentification Error", "not authenticated") + return httperrors.NewUnauthorizedError("Authentication Error", "not authenticated") } err := sessionService.delete(session) if err != nil { From 83e14467c04e80f946f91c7128ac60df4f4eba52 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 16:21:55 +0200 Subject: [PATCH 03/90] fix route for auth unit tests --- controllers/basicAuth_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/controllers/basicAuth_test.go b/controllers/basicAuth_test.go index 6e337939..d388a421 100644 --- a/controllers/basicAuth_test.go +++ b/controllers/basicAuth_test.go @@ -32,7 +32,7 @@ func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( "POST", - "/v1/auth/basic/login", + "/login", strings.NewReader("qsdqsdqsd"), ) @@ -63,7 +63,7 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( "POST", - "/v1/auth/basic/login", + "/login", strings.NewReader(`{ "email": "bob@email.com", "password":"1234" @@ -86,7 +86,7 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( "POST", - "/v1/auth/basic/login", + "/login", strings.NewReader(`{ "email": "bob@email.com", "password":"1234" @@ -129,7 +129,7 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( "POST", - "/v1/auth/basic/login", + "/login", strings.NewReader(`{ "email": "bob@email.com", "password":"1234" From 33e371c844155ebf55d9db3750ae438ea17ad2ae Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 16:25:03 +0200 Subject: [PATCH 04/90] remove error that is always nil --- services/sessionservice/session.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/sessionservice/session.go b/services/sessionservice/session.go index b07839f6..40a4fbb6 100644 --- a/services/sessionservice/session.go +++ b/services/sessionservice/session.go @@ -110,7 +110,7 @@ func (sessionService *sessionServiceImpl) add(session *models.Session) httperror } // Initialize the session service -func (sessionService *sessionServiceImpl) init() error { +func (sessionService *sessionServiceImpl) init() { sessionService.cache = make(map[uuid.UUID]*models.Session) go func() { for { @@ -121,7 +121,6 @@ func (sessionService *sessionServiceImpl) init() error { ) } }() - return nil } // Get all sessions and save them in cache From 03c876c6566eb22322065fd4bc2880eced498860 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 16:25:44 +0200 Subject: [PATCH 05/90] add TODO to the changes to be done in the session service --- services/sessionservice/session.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/sessionservice/session.go b/services/sessionservice/session.go index 40a4fbb6..9c41818f 100644 --- a/services/sessionservice/session.go +++ b/services/sessionservice/session.go @@ -244,8 +244,8 @@ func CreateAndSetAccessTokenCookie(w http.ResponseWriter, sessionUUID string) { Path: "/", Value: sessionUUID, HttpOnly: true, - SameSite: http.SameSiteNoneMode, // change to http.SameSiteStrictMode in prod - Secure: false, // change to true in prod + SameSite: http.SameSiteNoneMode, // TODO change to http.SameSiteStrictMode in prod + Secure: false, // TODO change to true in prod Expires: time.Now().Add(48 * time.Hour), } err := accessToken.Valid() From 960f598a5c68c4a95ddaa1b041cc674e41994a96 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 16:28:51 +0200 Subject: [PATCH 06/90] add error management to middlewareJSON --- router/middlewares/middlewareJson.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/router/middlewares/middlewareJson.go b/router/middlewares/middlewareJson.go index 1abe0343..c503f68e 100644 --- a/router/middlewares/middlewareJson.go +++ b/router/middlewares/middlewareJson.go @@ -4,8 +4,9 @@ import ( "encoding/json" "net/http" - "github.com/ditrit/badaas/httperrors" "go.uber.org/zap" + + "github.com/ditrit/badaas/httperrors" ) // transform a JSON handler into a standard [http.HandlerFunc] @@ -27,7 +28,8 @@ func NewJSONController(logger *zap.Logger) JSONController { return &jsonControllerImpl{logger} } -// Marshall the response from the JSONHandler and handle HTTPError if needed +// Transforms a JSONHandler into a standard [http.HandlerFunc] +// It marshalls the response from the JSONHandler and handles HTTPError if needed func (controller *jsonControllerImpl) Wrap(handler JSONHandler) func(response http.ResponseWriter, request *http.Request) { return func(response http.ResponseWriter, request *http.Request) { object, herr := handler(response, request) @@ -48,6 +50,12 @@ func (controller *jsonControllerImpl) Wrap(handler JSONHandler) func(response ht return } response.Header().Set("Content-Type", "application/json") - response.Write(payload) + _, err = response.Write(payload) + if err != nil { + controller.logger.Error( + "Error while writing http response", + zap.String("error", err.Error()), + ) + } } } From 1f7761ff28d87678516e57c9226a58c40bba3a4f Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 20:22:54 +0200 Subject: [PATCH 07/90] add CommandInitializer to init configuration keys --- badaas.go | 54 ++++++++++++++++- commands/rootCmd.go | 70 ----------------------- configuration/CommandInitializer.go | 46 +++++++++++++++ configuration/CommandInitializer_test.go | 39 +++++++++++++ configuration/KeySetter.go | 47 +++++++++++++++ mocks/configuration/CommandInitializer.go | 42 ++++++++++++++ mocks/configuration/KeySetter.go | 44 ++++++++++++++ commands/server.go => server.go | 7 ++- commands/server_test.go => server_test.go | 9 +-- 9 files changed, 278 insertions(+), 80 deletions(-) delete mode 100644 commands/rootCmd.go create mode 100644 configuration/CommandInitializer.go create mode 100644 configuration/CommandInitializer_test.go create mode 100644 configuration/KeySetter.go create mode 100644 mocks/configuration/CommandInitializer.go create mode 100644 mocks/configuration/KeySetter.go rename commands/server.go => server.go (91%) rename commands/server_test.go => server_test.go (89%) diff --git a/badaas.go b/badaas.go index 05513376..0183594d 100644 --- a/badaas.go +++ b/badaas.go @@ -1,11 +1,59 @@ -// Package main : package main import ( - "github.com/ditrit/badaas/commands" + "net/http" + + "github.com/spf13/cobra" + "go.uber.org/fx" + "go.uber.org/fx/fxevent" + "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/controllers" + "github.com/ditrit/badaas/logger" + "github.com/ditrit/badaas/persistence" + "github.com/ditrit/badaas/router" + "github.com/ditrit/badaas/services/sessionservice" + "github.com/ditrit/badaas/services/userservice" + "github.com/ditrit/verdeter" ) // Badaas application, run a http-server on 8000. func main() { - commands.Execute() + rootCommand := verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "badaas", + Short: "BaDaaS", + Run: runHTTPServer, + }) + + err := configuration.NewCommandInitializer(configuration.NewKeySetter()).Init(rootCommand) + if err != nil { + panic(err) + } + + rootCommand.Execute() +} + +// Run the http server for badaas +func runHTTPServer(cmd *cobra.Command, args []string) { + fx.New( + // Modules + configuration.ConfigurationModule, + router.RouterModule, + controllers.ControllerModule, + logger.LoggerModule, + persistence.PersistanceModule, + + fx.Provide(userservice.NewUserService), + fx.Provide(sessionservice.NewSessionService), + // logger for fx + fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { + return &fxevent.ZapLogger{Logger: logger} + }), + + fx.Provide(newHTTPServer), + + // Finally: we invoke the newly created server + fx.Invoke(func(*http.Server) { /* we need this function to be empty*/ }), + ).Run() } diff --git a/commands/rootCmd.go b/commands/rootCmd.go deleted file mode 100644 index 605b3bec..00000000 --- a/commands/rootCmd.go +++ /dev/null @@ -1,70 +0,0 @@ -package commands - -import ( - "net/http" - - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/controllers" - "github.com/ditrit/badaas/logger" - "github.com/ditrit/badaas/persistence" - "github.com/ditrit/badaas/resources" - "github.com/ditrit/badaas/router" - "github.com/ditrit/badaas/services/sessionservice" - "github.com/ditrit/badaas/services/userservice" - "github.com/ditrit/verdeter" - "github.com/spf13/cobra" - "go.uber.org/fx" - "go.uber.org/fx/fxevent" - "go.uber.org/zap" -) - -// Run the http server for badaas -func runHTTPServer(cmd *cobra.Command, args []string) { - fx.New( - // Modules - configuration.ConfigurationModule, - router.RouterModule, - controllers.ControllerModule, - logger.LoggerModule, - persistence.PersistanceModule, - - fx.Provide(userservice.NewUserService), - fx.Provide(sessionservice.NewSessionService), - // logger for fx - fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { - return &fxevent.ZapLogger{Logger: logger} - }), - - fx.Provide(NewHTTPServer), - - // Finally: we invoke the newly created server - fx.Invoke(func(*http.Server) { /* we need this function to be empty*/ }), - fx.Invoke(createSuperUser), - ).Run() -} - -// The command badaas -var rootCfg = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ - Use: "badaas", - Short: "Backend and Distribution as a Service", - Long: "Badaas stands for Backend and Distribution as a Service.", - Version: resources.Version, - Run: runHTTPServer, -}) - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - rootCfg.Execute() -} - -func init() { - rootCfg.GKey("config_path", verdeter.IsStr, "", "Path to the config file/directory") - rootCfg.SetDefault("config_path", ".") - - initServerCommands(rootCfg) - initLoggerCommands(rootCfg) - initDatabaseCommands(rootCfg) - initInitialisationCommands(rootCfg) - initSessionCommands(rootCfg) -} diff --git a/configuration/CommandInitializer.go b/configuration/CommandInitializer.go new file mode 100644 index 00000000..841ea99c --- /dev/null +++ b/configuration/CommandInitializer.go @@ -0,0 +1,46 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +type CommandInitializer interface { + // Inits VerdeterCommand "command" with the all the keys that are configurable in badaas + Init(command *verdeter.VerdeterCommand) error +} + +// Implementation of the CommandInitializer +type commandInitializerImpl struct { + KeySetter KeySetter + Keys []KeyDefinition +} + +// Constructor of CommandInitializer with the keys for badaas +// it uses the keySetter to set the configuration keys in the VerdeterCommand +func NewCommandInitializer(keySetter KeySetter) CommandInitializer { + keys := []KeyDefinition{ + { + Name: "config_path", + ValType: verdeter.IsStr, + Usage: "Path to the config file/directory", + DefaultV: ".", + }, + } + + return commandInitializerImpl{ + KeySetter: keySetter, + Keys: keys, + } +} + +// Inits VerdeterCommand "cmd" with the all the keys in the Keys of the initializer +func (initializer commandInitializerImpl) Init(command *verdeter.VerdeterCommand) error { + for _, key := range initializer.Keys { + err := initializer.KeySetter.Set(command, key) + if err != nil { + return err + } + } + + return nil +} diff --git a/configuration/CommandInitializer_test.go b/configuration/CommandInitializer_test.go new file mode 100644 index 00000000..bbe3e1b4 --- /dev/null +++ b/configuration/CommandInitializer_test.go @@ -0,0 +1,39 @@ +package configuration_test + +import ( + "errors" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/ditrit/badaas/configuration" + configurationMocks "github.com/ditrit/badaas/mocks/configuration" + "github.com/ditrit/verdeter" +) + +var rootCommand = verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ + Use: "badaas", + Short: "Backend and Distribution as a Service", + Run: doNothing, +}) + +func doNothing(_ *cobra.Command, _ []string) {} + +func TestInitCommandsInitializerSetsAllKeysWithoutError(t *testing.T) { + err := configuration.NewCommandInitializer( + configuration.NewKeySetter(), + ).Init(rootCommand) + assert.Nil(t, err) +} + +func TestInitCommandsInitializerReturnsErrorWhenErrorOnKeySet(t *testing.T) { + mockKeySetter := configurationMocks.NewKeySetter(t) + mockKeySetter.On("Set", mock.Anything, mock.Anything).Return(errors.New("error setting key")) + + commandInitializer := configuration.NewCommandInitializer(mockKeySetter) + + err := commandInitializer.Init(rootCommand) + assert.ErrorContains(t, err, "error setting key") +} diff --git a/configuration/KeySetter.go b/configuration/KeySetter.go new file mode 100644 index 00000000..d549a014 --- /dev/null +++ b/configuration/KeySetter.go @@ -0,0 +1,47 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/models" +) + +type KeySetter interface { + // Configures the VerdeterCommand "command" with the information contained in "key" + Set(command *verdeter.VerdeterCommand, key KeyDefinition) error +} + +type KeyDefinition struct { + Name string + ValType models.ConfigType + Usage string + Required bool + DefaultV any + Validator *models.Validator +} + +type keySetterImpl struct{} + +func NewKeySetter() KeySetter { + return keySetterImpl{} +} + +// Configures the VerdeterCommand "command" with the information contained in "key" +func (ks keySetterImpl) Set(command *verdeter.VerdeterCommand, key KeyDefinition) error { + if err := command.GKey(key.Name, key.ValType, "", key.Usage); err != nil { + return err + } + + if key.Required { + command.SetRequired(key.Name) + } + + if key.DefaultV != nil { + command.SetDefault(key.Name, key.DefaultV) + } + + if key.Validator != nil { + command.AddValidator(key.Name, *key.Validator) + } + + return nil +} diff --git a/mocks/configuration/CommandInitializer.go b/mocks/configuration/CommandInitializer.go new file mode 100644 index 00000000..14b1686b --- /dev/null +++ b/mocks/configuration/CommandInitializer.go @@ -0,0 +1,42 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + verdeter "github.com/ditrit/verdeter" + mock "github.com/stretchr/testify/mock" +) + +// CommandInitializer is an autogenerated mock type for the CommandInitializer type +type CommandInitializer struct { + mock.Mock +} + +// Init provides a mock function with given fields: command +func (_m *CommandInitializer) Init(command *verdeter.VerdeterCommand) error { + ret := _m.Called(command) + + var r0 error + if rf, ok := ret.Get(0).(func(*verdeter.VerdeterCommand) error); ok { + r0 = rf(command) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewCommandInitializer interface { + mock.TestingT + Cleanup(func()) +} + +// NewCommandInitializer creates a new instance of CommandInitializer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCommandInitializer(t mockConstructorTestingTNewCommandInitializer) *CommandInitializer { + mock := &CommandInitializer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/configuration/KeySetter.go b/mocks/configuration/KeySetter.go new file mode 100644 index 00000000..05885cc0 --- /dev/null +++ b/mocks/configuration/KeySetter.go @@ -0,0 +1,44 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + configuration "github.com/ditrit/badaas/configuration" + mock "github.com/stretchr/testify/mock" + + verdeter "github.com/ditrit/verdeter" +) + +// KeySetter is an autogenerated mock type for the KeySetter type +type KeySetter struct { + mock.Mock +} + +// Set provides a mock function with given fields: command, key +func (_m *KeySetter) Set(command *verdeter.VerdeterCommand, key configuration.KeyDefinition) error { + ret := _m.Called(command, key) + + var r0 error + if rf, ok := ret.Get(0).(func(*verdeter.VerdeterCommand, configuration.KeyDefinition) error); ok { + r0 = rf(command, key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewKeySetter interface { + mock.TestingT + Cleanup(func()) +} + +// NewKeySetter creates a new instance of KeySetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewKeySetter(t mockConstructorTestingTNewKeySetter) *KeySetter { + mock := &KeySetter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/commands/server.go b/server.go similarity index 91% rename from commands/server.go rename to server.go index 1d023b42..a6f63b72 100644 --- a/commands/server.go +++ b/server.go @@ -1,6 +1,7 @@ -package commands +package main -// This file holds functions needed by the badaas rootCommand, thoses functions help in creating the http.Server. +// This file holds functions needed by the badaas rootCommand, +// those functions help in creating the http.Server. import ( "context" @@ -44,7 +45,7 @@ func addrFromConf(host string, port int) string { return address } -func NewHTTPServer( +func newHTTPServer( lc fx.Lifecycle, logger *zap.Logger, router http.Handler, diff --git a/commands/server_test.go b/server_test.go similarity index 89% rename from commands/server_test.go rename to server_test.go index aa949b71..302e5d31 100644 --- a/commands/server_test.go +++ b/server_test.go @@ -1,14 +1,15 @@ -package commands +package main -// This files holds the tests for the commands/server.go file. +// This files holds the tests for the server.go file. import ( "net/http" "testing" "time" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/configuration" ) func Test_addrFromConf(t *testing.T) { @@ -16,6 +17,7 @@ func Test_addrFromConf(t *testing.T) { addr := addrFromConf("192.168.236.222", 25100) assert.Equal(t, expected, addr) } + func Test_createServer(t *testing.T) { handl := http.NewServeMux() timeout := time.Duration(time.Second) @@ -32,5 +34,4 @@ func TestCreateServerFromConfigurationHolder(t *testing.T) { srv := createServerFromConfigurationHolder(handl, configuration.NewHTTPServerConfiguration()) assert.NotNil(t, srv) - } From 28e3ecb7abb6c88bbb2622209735bc1d0b1371bd Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 20:26:35 +0200 Subject: [PATCH 08/90] init database configuration keys --- badaas.example.yml | 3 ++ commands/initDatabaseCommands.go | 32 ------------ configuration.md | 3 ++ configuration/CommandInitializer.go | 1 + configuration/DatabaseConfigurationKeys.go | 58 ++++++++++++++++++++++ 5 files changed, 65 insertions(+), 32 deletions(-) delete mode 100644 commands/initDatabaseCommands.go create mode 100644 configuration/DatabaseConfigurationKeys.go diff --git a/badaas.example.yml b/badaas.example.yml index 44313081..c00e461c 100644 --- a/badaas.example.yml +++ b/badaas.example.yml @@ -8,6 +8,9 @@ database: # (mandatory) port: 26257 + # The name of the database to use. + name: badaas_db + # The sslmode of the connection to the database server. # (mandatory) sslmode: disable diff --git a/commands/initDatabaseCommands.go b/commands/initDatabaseCommands.go deleted file mode 100644 index 022cfb98..00000000 --- a/commands/initDatabaseCommands.go +++ /dev/null @@ -1,32 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" -) - -func initDatabaseCommands(cfg *verdeter.VerdeterCommand) { - cfg.GKey(configuration.DatabasePortKey, verdeter.IsInt, "", "The port of the database server") - cfg.SetRequired(configuration.DatabasePortKey) - - cfg.GKey(configuration.DatabaseHostKey, verdeter.IsStr, "", "The host of the database server") - cfg.SetRequired(configuration.DatabaseHostKey) - - cfg.GKey(configuration.DatabaseNameKey, verdeter.IsStr, "", "The name of the database to use") - cfg.SetRequired(configuration.DatabaseNameKey) - - cfg.GKey(configuration.DatabaseUsernameKey, verdeter.IsStr, "", "The username of the account on the database server") - cfg.SetRequired(configuration.DatabaseUsernameKey) - - cfg.GKey(configuration.DatabasePasswordKey, verdeter.IsStr, "", "The password of the account one the database server") - cfg.SetRequired(configuration.DatabasePasswordKey) - - cfg.GKey(configuration.DatabaseSslmodeKey, verdeter.IsStr, "", "The sslmode to use when connecting to the database server") - cfg.SetRequired(configuration.DatabaseSslmodeKey) - - cfg.GKey(configuration.DatabaseRetryKey, verdeter.IsUint, "", "The number of times badaas tries to establish a connection with the database") - cfg.SetDefault(configuration.DatabaseRetryKey, uint(10)) - - cfg.GKey(configuration.DatabaseRetryDurationKey, verdeter.IsUint, "", "The duration in seconds badaas wait between connection attempts") - cfg.SetDefault(configuration.DatabaseRetryDurationKey, uint(5)) -} diff --git a/configuration.md b/configuration.md index 06383628..cd2e9486 100644 --- a/configuration.md +++ b/configuration.md @@ -33,6 +33,9 @@ database: # (mandatory) port: 26257 + # The name of the database to use. + name: badaas_db + # The sslmode of the connection to the database server. # (mandatory) sslmode: disable diff --git a/configuration/CommandInitializer.go b/configuration/CommandInitializer.go index 841ea99c..489503da 100644 --- a/configuration/CommandInitializer.go +++ b/configuration/CommandInitializer.go @@ -26,6 +26,7 @@ func NewCommandInitializer(keySetter KeySetter) CommandInitializer { DefaultV: ".", }, } + keys = append(keys, getDatabaseConfigurationKeys()...) return commandInitializerImpl{ KeySetter: keySetter, diff --git a/configuration/DatabaseConfigurationKeys.go b/configuration/DatabaseConfigurationKeys.go new file mode 100644 index 00000000..36c3ed37 --- /dev/null +++ b/configuration/DatabaseConfigurationKeys.go @@ -0,0 +1,58 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +// Definition of database configuration keys +func getDatabaseConfigurationKeys() []KeyDefinition { + return []KeyDefinition{ + { + Name: DatabasePortKey, + ValType: verdeter.IsInt, + Usage: "The port of the database server", + Required: true, + }, + { + Name: DatabaseHostKey, + ValType: verdeter.IsStr, + Usage: "The host of the database server", + Required: true, + }, + { + Name: DatabaseNameKey, + ValType: verdeter.IsStr, + Usage: "The name of the database to use", + }, + { + Name: DatabaseSslmodeKey, + ValType: verdeter.IsStr, + Usage: "The sslmode to use when connecting to the database server", + Required: true, + }, + { + Name: DatabaseUsernameKey, + ValType: verdeter.IsStr, + Usage: "The username of the account on the database server", + Required: true, + }, + { + Name: DatabasePasswordKey, + ValType: verdeter.IsStr, + Usage: "The password of the account one the database server", + Required: true, + }, + { + Name: DatabaseRetryKey, + ValType: verdeter.IsUint, + Usage: "The number of times badaas tries to establish a connection with the database", + DefaultV: uint(10), + }, + { + Name: DatabaseRetryDurationKey, + ValType: verdeter.IsUint, + Usage: "The duration in seconds badaas wait between connection attempts", + DefaultV: uint(5), + }, + } +} From 63de335e3cdb84c3fe3e25869b568f229c414ae7 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 20:28:58 +0200 Subject: [PATCH 09/90] init session configuration keys --- commands/initSessionCommands.go | 19 --------------- configuration/CommandInitializer.go | 1 + configuration/SessionConfigurationKeys.go | 29 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 19 deletions(-) delete mode 100644 commands/initSessionCommands.go create mode 100644 configuration/SessionConfigurationKeys.go diff --git a/commands/initSessionCommands.go b/commands/initSessionCommands.go deleted file mode 100644 index 13f4ad74..00000000 --- a/commands/initSessionCommands.go +++ /dev/null @@ -1,19 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" -) - -// initialize session related config keys -func initSessionCommands(cfg *verdeter.VerdeterCommand) { - cfg.LKey(configuration.SessionDurationKey, verdeter.IsUint, "", "The duration of a user session in seconds.") - cfg.SetDefault(configuration.SessionDurationKey, uint(3600*4)) // 4 hours by default - - cfg.LKey(configuration.SessionPullIntervalKey, - verdeter.IsUint, "", "The refresh interval in seconds. Badaas refresh it's internal session cache periodically.") - cfg.SetDefault(configuration.SessionPullIntervalKey, uint(30)) // 30 seconds by default - - cfg.LKey(configuration.SessionRollIntervalKey, verdeter.IsUint, "", "The interval in which the user can renew it's session by making a request.") - cfg.SetDefault(configuration.SessionRollIntervalKey, uint(3600)) // 1 hour by default -} diff --git a/configuration/CommandInitializer.go b/configuration/CommandInitializer.go index 489503da..00ad4267 100644 --- a/configuration/CommandInitializer.go +++ b/configuration/CommandInitializer.go @@ -27,6 +27,7 @@ func NewCommandInitializer(keySetter KeySetter) CommandInitializer { }, } keys = append(keys, getDatabaseConfigurationKeys()...) + keys = append(keys, getSessionConfigurationKeys()...) return commandInitializerImpl{ KeySetter: keySetter, diff --git a/configuration/SessionConfigurationKeys.go b/configuration/SessionConfigurationKeys.go new file mode 100644 index 00000000..4a0960f7 --- /dev/null +++ b/configuration/SessionConfigurationKeys.go @@ -0,0 +1,29 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +// Definition of session configuration keys +func getSessionConfigurationKeys() []KeyDefinition { + return []KeyDefinition{ + { + Name: SessionDurationKey, + ValType: verdeter.IsUint, + Usage: "The duration of a user session in seconds", + DefaultV: uint(3600 * 4), // 4 hours by default + }, + { + Name: SessionPullIntervalKey, + ValType: verdeter.IsUint, + Usage: "The refresh interval in seconds. Badaas refresh it's internal session cache periodically", + DefaultV: uint(30), // 30 seconds by default + }, + { + Name: SessionRollIntervalKey, + ValType: verdeter.IsUint, + Usage: "The interval in which the user can renew it's session by making a request", + DefaultV: uint(3600), // 1 hour by default + }, + } +} From 807e598b16a635f46825da11399cf8d4fdd1de78 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 20:31:36 +0200 Subject: [PATCH 10/90] init initialization configuration keys --- commands/initInitialisationCommands.go | 13 ------------- configuration/CommandInitializer.go | 1 + .../InitializationConfigurationKeys.go | 17 +++++++++++++++++ 3 files changed, 18 insertions(+), 13 deletions(-) delete mode 100644 commands/initInitialisationCommands.go create mode 100644 configuration/InitializationConfigurationKeys.go diff --git a/commands/initInitialisationCommands.go b/commands/initInitialisationCommands.go deleted file mode 100644 index 130e9778..00000000 --- a/commands/initInitialisationCommands.go +++ /dev/null @@ -1,13 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" -) - -func initInitialisationCommands(cfg *verdeter.VerdeterCommand) { - - cfg.GKey(configuration.InitializationDefaultAdminPasswordKey, verdeter.IsStr, "", - "Set the default admin password is the admin user is not created yet.") - cfg.SetDefault(configuration.InitializationDefaultAdminPasswordKey, "admin") -} diff --git a/configuration/CommandInitializer.go b/configuration/CommandInitializer.go index 00ad4267..6d1871be 100644 --- a/configuration/CommandInitializer.go +++ b/configuration/CommandInitializer.go @@ -28,6 +28,7 @@ func NewCommandInitializer(keySetter KeySetter) CommandInitializer { } keys = append(keys, getDatabaseConfigurationKeys()...) keys = append(keys, getSessionConfigurationKeys()...) + keys = append(keys, getInitializationConfigurationKeys()...) return commandInitializerImpl{ KeySetter: keySetter, diff --git a/configuration/InitializationConfigurationKeys.go b/configuration/InitializationConfigurationKeys.go new file mode 100644 index 00000000..7eb896ad --- /dev/null +++ b/configuration/InitializationConfigurationKeys.go @@ -0,0 +1,17 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" +) + +// Definition of initialization configuration keys +func getInitializationConfigurationKeys() []KeyDefinition { + return []KeyDefinition{ + { + Name: InitializationDefaultAdminPasswordKey, + ValType: verdeter.IsStr, + Usage: "Set the default admin password is the admin user is not created yet.", + DefaultV: "admin", + }, + } +} From 2de14ef48880dc35ec5bdd151be7b507b593a18d Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 20:33:52 +0200 Subject: [PATCH 11/90] init server configuration keys --- commands/initServerCommands.go | 23 --------------- configuration/CommandInitializer.go | 1 + configuration/ServerConfigurationKeys.go | 37 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 23 deletions(-) delete mode 100644 commands/initServerCommands.go create mode 100644 configuration/ServerConfigurationKeys.go diff --git a/commands/initServerCommands.go b/commands/initServerCommands.go deleted file mode 100644 index 7323524b..00000000 --- a/commands/initServerCommands.go +++ /dev/null @@ -1,23 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" - "github.com/ditrit/verdeter/validators" -) - -func initServerCommands(cfg *verdeter.VerdeterCommand) { - cfg.GKey(configuration.ServerTimeoutKey, verdeter.IsInt, "", "Maximum timeout of the http server in second (default is 15s)") - cfg.SetDefault(configuration.ServerTimeoutKey, 15) - - cfg.GKey(configuration.ServerHostKey, verdeter.IsStr, "", "Address to bind (default is 0.0.0.0)") - cfg.SetDefault(configuration.ServerHostKey, "0.0.0.0") - - cfg.GKey(configuration.ServerPortKey, verdeter.IsInt, "p", "Port to bind (default is 8000)") - cfg.AddValidator(configuration.ServerPortKey, validators.CheckTCPHighPort) - cfg.SetDefault(configuration.ServerPortKey, 8000) - - cfg.GKey(configuration.ServerPaginationMaxElemPerPage, verdeter.IsUint, "", "The max number of records returned per page") - cfg.SetDefault(configuration.ServerPaginationMaxElemPerPage, 100) - -} diff --git a/configuration/CommandInitializer.go b/configuration/CommandInitializer.go index 6d1871be..85014f64 100644 --- a/configuration/CommandInitializer.go +++ b/configuration/CommandInitializer.go @@ -29,6 +29,7 @@ func NewCommandInitializer(keySetter KeySetter) CommandInitializer { keys = append(keys, getDatabaseConfigurationKeys()...) keys = append(keys, getSessionConfigurationKeys()...) keys = append(keys, getInitializationConfigurationKeys()...) + keys = append(keys, getServerConfigurationKeys()...) return commandInitializerImpl{ KeySetter: keySetter, diff --git a/configuration/ServerConfigurationKeys.go b/configuration/ServerConfigurationKeys.go new file mode 100644 index 00000000..7582984d --- /dev/null +++ b/configuration/ServerConfigurationKeys.go @@ -0,0 +1,37 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/validators" +) + +// Definition of server configuration keys +func getServerConfigurationKeys() []KeyDefinition { + return []KeyDefinition{ + { + Name: ServerTimeoutKey, + ValType: verdeter.IsInt, + Usage: "Maximum timeout of the http server in second (default is 15s)", + DefaultV: 15, + }, + { + Name: ServerHostKey, + ValType: verdeter.IsStr, + Usage: "Address to bind (default is 0.0.0.0)", + DefaultV: "0.0.0.0", + }, + { + Name: ServerPortKey, + ValType: verdeter.IsInt, + Usage: "Port to bind (default is 8000)", + DefaultV: 8000, + Validator: &validators.CheckTCPHighPort, + }, + { + Name: ServerPaginationMaxElemPerPage, + ValType: verdeter.IsUint, + Usage: "The max number of records returned per page", + DefaultV: uint(100), + }, + } +} From 2dc590720fd26abd35c8137e4ef3b426f20f8d2a Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 20:35:18 +0200 Subject: [PATCH 12/90] init logger configuration keys --- commands/initLoggerCommands.go | 16 --------------- configuration/CommandInitializer.go | 1 + configuration/LoggerConfigurationKeys.go | 26 ++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 16 deletions(-) delete mode 100644 commands/initLoggerCommands.go create mode 100644 configuration/LoggerConfigurationKeys.go diff --git a/commands/initLoggerCommands.go b/commands/initLoggerCommands.go deleted file mode 100644 index 097ac1d3..00000000 --- a/commands/initLoggerCommands.go +++ /dev/null @@ -1,16 +0,0 @@ -package commands - -import ( - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/verdeter" - "github.com/ditrit/verdeter/validators" -) - -func initLoggerCommands(cfg *verdeter.VerdeterCommand) { - cfg.GKey(configuration.LoggerModeKey, verdeter.IsStr, "", "The logger mode (default to \"prod\")") - cfg.SetDefault(configuration.LoggerModeKey, "prod") - cfg.AddValidator(configuration.LoggerModeKey, validators.AuthorizedValues("prod", "dev")) - - cfg.GKey(configuration.LoggerRequestTemplateKey, verdeter.IsStr, "", "Template message for all request logs") - cfg.SetDefault(configuration.LoggerRequestTemplateKey, "Receive {{method}} request on {{url}}") -} diff --git a/configuration/CommandInitializer.go b/configuration/CommandInitializer.go index 85014f64..51569748 100644 --- a/configuration/CommandInitializer.go +++ b/configuration/CommandInitializer.go @@ -30,6 +30,7 @@ func NewCommandInitializer(keySetter KeySetter) CommandInitializer { keys = append(keys, getSessionConfigurationKeys()...) keys = append(keys, getInitializationConfigurationKeys()...) keys = append(keys, getServerConfigurationKeys()...) + keys = append(keys, getLoggerConfigurationKeys()...) return commandInitializerImpl{ KeySetter: keySetter, diff --git a/configuration/LoggerConfigurationKeys.go b/configuration/LoggerConfigurationKeys.go new file mode 100644 index 00000000..b5686730 --- /dev/null +++ b/configuration/LoggerConfigurationKeys.go @@ -0,0 +1,26 @@ +package configuration + +import ( + "github.com/ditrit/verdeter" + "github.com/ditrit/verdeter/validators" +) + +// Definition of logger configuration keys +func getLoggerConfigurationKeys() []KeyDefinition { + modeValidator := validators.AuthorizedValues("prod", "dev") + return []KeyDefinition{ + { + Name: LoggerRequestTemplateKey, + ValType: verdeter.IsStr, + Usage: "Template message for all request logs", + DefaultV: "Receive {{method}} request on {{url}}", + }, + { + Name: LoggerModeKey, + ValType: verdeter.IsStr, + Usage: "The logger mode (default to \"prod\")", + DefaultV: "prod", + Validator: &modeValidator, + }, + } +} From cbb6f5c4356fe365710824d5e1a1a2c799b77960 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 20:40:45 +0200 Subject: [PATCH 13/90] remove unused r.md file in commands --- commands/r.md | 86 --------------------------------------------------- 1 file changed, 86 deletions(-) delete mode 100644 commands/r.md diff --git a/commands/r.md b/commands/r.md deleted file mode 100644 index c32746b4..00000000 --- a/commands/r.md +++ /dev/null @@ -1,86 +0,0 @@ -package commands - -import ( - "log" - - "github.com/ditrit/badaas/logger" - "github.com/ditrit/badaas/persistence/registry" - "github.com/ditrit/badaas/persistence/repository" - "github.com/ditrit/badaas/router" - "github.com/ditrit/badaas/services/session" - "github.com/ditrit/badaas/services/userservice" - "github.com/ditrit/verdeter" - "go.uber.org/zap" -) - -// Create a super admin user and exit with code 1 on error -func createSuperAdminUser() { - logg := zap.L().Sugar() - _, err := userservice.NewUser("superadmin", "superadmin@badaas.test", "1234") - if err != nil { - if repository.ErrAlreadyExists == err { - logg.Debugf("The superadmin user already exists in database") - } else { - logg.Fatalf("failed to save the super admin %w", err) - } - } - -} - -// Run the http server for badaas -func runHTTPServer(cfg *verdeter.VerdeterCommand, args []string) error { - err := logger.InitLoggerFromConf() - if err != nil { - log.Fatalf("An error happened while initializing logger (ERROR=%s)", err.Error()) - } - - zap.L().Info("The logger is initialiazed") - - // create router - router := router.SetupRouter() - - registryInstance, err := registry.FactoryRegistry(registry.GormDataStore) - if err != nil { - zap.L().Sugar().Fatalf("An error happened while initializing datastorage layer (ERROR=%s)", err.Error()) - } - registry.ReplaceGlobals(registryInstance) - zap.L().Info("The datastorage layer is initialized") - - createSuperAdminUser() - - err = session.Init() - if err != nil { - zap.L().Sugar().Fatalf("An error happened while initializing the session service (ERROR=%s)", err.Error()) - } - zap.L().Info("The session service is initialized") - - // create server - srv := createServerFromConfiguration(router) - - zap.L().Sugar().Infof("Ready to serve at %s\n", srv.Addr) - return srv.ListenAndServe() -} - -var rootCfg = verdeter.NewVerdeterCommand( - "badaas", - "Backend and Distribution as a Service", - `Badaas stands for Backend and Distribution as a Service.`, - runHTTPServer, -) - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - rootCfg.Execute() -} - -func init() { - rootCfg.Initialize() - - rootCfg.GKey("config_path", verdeter.IsStr, "", "Path to the config file/directory") - rootCfg.SetDefault("config_path", ".") - - initServerCommands(rootCfg) - initLoggerCommands(rootCfg) - initDatabaseCommands(rootCfg) -} From d165eec23b36499f32ef18debf6f980aded25922 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 20:43:13 +0200 Subject: [PATCH 14/90] move time to utils --- configuration/DatabaseConfiguration.go | 3 ++- configuration/HttpServerConfiguration.go | 4 +++- configuration/SessionConfiguration.go | 7 ++++--- {configuration => utils}/time.go | 4 ++-- {configuration => utils}/time_test.go | 8 ++++---- 5 files changed, 15 insertions(+), 11 deletions(-) rename {configuration => utils}/time.go (60%) rename {configuration => utils}/time_test.go (82%) diff --git a/configuration/DatabaseConfiguration.go b/configuration/DatabaseConfiguration.go index 293874a1..6a950473 100644 --- a/configuration/DatabaseConfiguration.go +++ b/configuration/DatabaseConfiguration.go @@ -3,6 +3,7 @@ package configuration import ( "time" + "github.com/ditrit/badaas/utils" "github.com/spf13/viper" "go.uber.org/zap" ) @@ -100,7 +101,7 @@ func (databaseConfiguration *databaseConfigurationImpl) GetRetry() uint { // Return the waiting time between the database connections in seconds func (databaseConfiguration *databaseConfigurationImpl) GetRetryTime() time.Duration { - return intToSecond(int(databaseConfiguration.retryTime)) + return utils.IntToSecond(int(databaseConfiguration.retryTime)) } // Log the values provided by the configuration holder diff --git a/configuration/HttpServerConfiguration.go b/configuration/HttpServerConfiguration.go index 0a00f5f3..3f864687 100644 --- a/configuration/HttpServerConfiguration.go +++ b/configuration/HttpServerConfiguration.go @@ -5,6 +5,8 @@ import ( "github.com/spf13/viper" "go.uber.org/zap" + + "github.com/ditrit/badaas/utils" ) // The config keys regarding the http server settings @@ -41,7 +43,7 @@ func NewHTTPServerConfiguration() HTTPServerConfiguration { func (httpServerConfiguration *hTTPServerConfigurationImpl) Reload() { httpServerConfiguration.host = viper.GetString(ServerHostKey) httpServerConfiguration.port = viper.GetInt(ServerPortKey) - httpServerConfiguration.timeout = intToSecond(viper.GetInt(ServerTimeoutKey)) + httpServerConfiguration.timeout = utils.IntToSecond(viper.GetInt(ServerTimeoutKey)) } // Return the host addr diff --git a/configuration/SessionConfiguration.go b/configuration/SessionConfiguration.go index 3a76ef6f..1d330a8c 100644 --- a/configuration/SessionConfiguration.go +++ b/configuration/SessionConfiguration.go @@ -3,6 +3,7 @@ package configuration import ( "time" + "github.com/ditrit/badaas/utils" "github.com/spf13/viper" "go.uber.org/zap" ) @@ -53,9 +54,9 @@ func (sessionConfiguration *sessionConfigurationImpl) GetRollDuration() time.Dur // Reload session configuration func (sessionConfiguration *sessionConfigurationImpl) Reload() { - sessionConfiguration.sessionDuration = intToSecond(int(viper.GetUint(SessionDurationKey))) - sessionConfiguration.pullInterval = intToSecond(int(viper.GetUint(SessionPullIntervalKey))) - sessionConfiguration.rollDuration = intToSecond(int(viper.GetUint(SessionRollIntervalKey))) + sessionConfiguration.sessionDuration = utils.IntToSecond(int(viper.GetUint(SessionDurationKey))) + sessionConfiguration.pullInterval = utils.IntToSecond(int(viper.GetUint(SessionPullIntervalKey))) + sessionConfiguration.rollDuration = utils.IntToSecond(int(viper.GetUint(SessionRollIntervalKey))) } // Log the values provided by the configuration holder diff --git a/configuration/time.go b/utils/time.go similarity index 60% rename from configuration/time.go rename to utils/time.go index f71ef280..b075cbb1 100644 --- a/configuration/time.go +++ b/utils/time.go @@ -1,8 +1,8 @@ -package configuration +package utils import "time" // Convert int (seconds) to [time.Duration] -func intToSecond(numberOfSeconds int) time.Duration { +func IntToSecond(numberOfSeconds int) time.Duration { return time.Duration(numberOfSeconds) * time.Second } diff --git a/configuration/time_test.go b/utils/time_test.go similarity index 82% rename from configuration/time_test.go rename to utils/time_test.go index 56134aab..578290ce 100644 --- a/configuration/time_test.go +++ b/utils/time_test.go @@ -1,4 +1,4 @@ -package configuration +package utils import ( "testing" @@ -10,19 +10,19 @@ import ( func TestIntToSecond(t *testing.T) { assert.Equal( t, - intToSecond(20), + IntToSecond(20), time.Duration(20*time.Second), "the duration should be equals", ) assert.Equal( t, - intToSecond(-5), + IntToSecond(-5), time.Duration(-5*time.Second), "the duration should be equals", ) assert.Equal( t, - intToSecond(3600), + IntToSecond(3600), time.Duration(time.Hour), "the duration should be equals", ) From b80f2e47f399ce480435f49093f5a3816c62b3f1 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 20:44:06 +0200 Subject: [PATCH 15/90] create super user when adding the auth controller --- commands/init.go | 27 ----------------- controllers/ModuleFx.go | 29 ++++++++++++++++++- .../createSuperUser_test.go | 13 +++++---- 3 files changed, 35 insertions(+), 34 deletions(-) delete mode 100644 commands/init.go rename commands/init_test.go => controllers/createSuperUser_test.go (84%) diff --git a/commands/init.go b/commands/init.go deleted file mode 100644 index 1d9b4f20..00000000 --- a/commands/init.go +++ /dev/null @@ -1,27 +0,0 @@ -package commands - -import ( - "strings" - - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/services/userservice" - "go.uber.org/zap" -) - -// Create a super user -func createSuperUser( - config configuration.InitializationConfiguration, - logger *zap.Logger, - userService userservice.UserService, -) error { - // Create a super admin user and exit with code 1 on error - _, err := userService.NewUser("admin", "admin-no-reply@badaas.com", config.GetAdminPassword()) - if err != nil { - if !strings.Contains(err.Error(), "already exist in database") { - logger.Sugar().Errorf("failed to save the super admin %w", err) - return err - } - logger.Sugar().Infof("The superadmin user already exists in database") - } - return nil -} diff --git a/controllers/ModuleFx.go b/controllers/ModuleFx.go index adfff9ec..dcebb545 100644 --- a/controllers/ModuleFx.go +++ b/controllers/ModuleFx.go @@ -1,10 +1,37 @@ package controllers -import "go.uber.org/fx" +import ( + "strings" + + "go.uber.org/fx" + "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/services/userservice" +) // ControllerModule for fx var ControllerModule = fx.Module( "controllers", fx.Provide(NewInfoController), fx.Provide(NewBasicAuthenticationController), + fx.Invoke(createSuperUser), ) + +// Create a super user +func createSuperUser( + config configuration.InitializationConfiguration, + logger *zap.Logger, + userService userservice.UserService, +) error { + // Create a super admin user and exit with code 1 on error + _, err := userService.NewUser("admin", "admin-no-reply@badaas.com", config.GetAdminPassword()) + if err != nil { + if !strings.Contains(err.Error(), "already exist in database") { + logger.Sugar().Errorf("failed to save the super admin %w", err) + return err + } + logger.Sugar().Infof("The superadmin user already exists in database") + } + return nil +} diff --git a/commands/init_test.go b/controllers/createSuperUser_test.go similarity index 84% rename from commands/init_test.go rename to controllers/createSuperUser_test.go index 1966506d..6e160b76 100644 --- a/commands/init_test.go +++ b/controllers/createSuperUser_test.go @@ -1,21 +1,22 @@ -package commands +package controllers import ( "errors" "testing" - mocks "github.com/ditrit/badaas/mocks/configuration" - mockUserServices "github.com/ditrit/badaas/mocks/services/userservice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" + + mocksConfiguration "github.com/ditrit/badaas/mocks/configuration" + mockUserServices "github.com/ditrit/badaas/mocks/services/userservice" ) func TestCreateSuperUser(t *testing.T) { core, _ := observer.New(zap.DebugLevel) logger := zap.New(core) - initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig := mocksConfiguration.NewInitializationConfiguration(t) initializationConfig.On("GetAdminPassword").Return("adminpassword") userService := mockUserServices.NewUserService(t) userService. @@ -32,7 +33,7 @@ func TestCreateSuperUser(t *testing.T) { func TestCreateSuperUser_UserExists(t *testing.T) { core, logs := observer.New(zap.DebugLevel) logger := zap.New(core) - initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig := mocksConfiguration.NewInitializationConfiguration(t) initializationConfig.On("GetAdminPassword").Return("adminpassword") userService := mockUserServices.NewUserService(t) userService. @@ -51,7 +52,7 @@ func TestCreateSuperUser_UserExists(t *testing.T) { func TestCreateSuperUser_UserServiceError(t *testing.T) { core, logs := observer.New(zap.DebugLevel) logger := zap.New(core) - initializationConfig := mocks.NewInitializationConfiguration(t) + initializationConfig := mocksConfiguration.NewInitializationConfiguration(t) initializationConfig.On("GetAdminPassword").Return("adminpassword") userService := mockUserServices.NewUserService(t) userService. From 88db618f7ca4fbb2a7e6e5bf9ba0d23e3e76fa4d Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 11:44:16 +0200 Subject: [PATCH 16/90] update info controller and routes creation --- badaas.go | 2 - controllers/ModuleFx.go | 11 ++-- controllers/info.go | 36 +++++++------ controllers/routes.go | 42 +++++++++++++++ controllers/routes_test.go | 72 ++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 - persistence/models/ProductInfo.go | 7 --- persistence/models/dto/ProductInfo.go | 9 ---- resources/api.go | 4 -- router/ModuleFx.go | 6 +-- router/middlewares/middlewareLogger.go | 8 ++- router/router.go | 39 ++------------ router/router_test.go | 24 --------- 14 files changed, 156 insertions(+), 107 deletions(-) create mode 100644 controllers/routes.go create mode 100644 controllers/routes_test.go delete mode 100644 persistence/models/ProductInfo.go delete mode 100644 persistence/models/dto/ProductInfo.go delete mode 100644 resources/api.go delete mode 100644 router/router_test.go diff --git a/badaas.go b/badaas.go index 0183594d..ee167cb6 100644 --- a/badaas.go +++ b/badaas.go @@ -9,7 +9,6 @@ import ( "go.uber.org/zap" "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/controllers" "github.com/ditrit/badaas/logger" "github.com/ditrit/badaas/persistence" "github.com/ditrit/badaas/router" @@ -40,7 +39,6 @@ func runHTTPServer(cmd *cobra.Command, args []string) { // Modules configuration.ConfigurationModule, router.RouterModule, - controllers.ControllerModule, logger.LoggerModule, persistence.PersistanceModule, diff --git a/controllers/ModuleFx.go b/controllers/ModuleFx.go index dcebb545..175cbc81 100644 --- a/controllers/ModuleFx.go +++ b/controllers/ModuleFx.go @@ -10,11 +10,16 @@ import ( "github.com/ditrit/badaas/services/userservice" ) -// ControllerModule for fx -var ControllerModule = fx.Module( - "controllers", +var InfoControllerModule = fx.Module( + "infoController", fx.Provide(NewInfoController), + fx.Invoke(AddInfoRoutes), +) + +var AuthControllerModule = fx.Module( + "authController", fx.Provide(NewBasicAuthenticationController), + fx.Invoke(AddAuthRoutes), fx.Invoke(createSuperUser), ) diff --git a/controllers/info.go b/controllers/info.go index f39f30a2..50e58ad3 100644 --- a/controllers/info.go +++ b/controllers/info.go @@ -3,34 +3,40 @@ package controllers import ( "net/http" + "github.com/Masterminds/semver/v3" "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/models/dto" - "github.com/ditrit/badaas/resources" ) // The information controller type InformationController interface { - // Return the badaas server informations + // Return the badaas server information Info(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) } // check interface compliance var _ InformationController = (*infoControllerImpl)(nil) -// The InformationController constructor -func NewInfoController() InformationController { - return &infoControllerImpl{} -} - // The concrete implementation of the InformationController -type infoControllerImpl struct{} +type infoControllerImpl struct { + Version *semver.Version +} -// Return the badaas server informations -func (*infoControllerImpl) Info(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { +// The InformationController constructor +func NewInfoController(version *semver.Version) InformationController { + return &infoControllerImpl{ + Version: version, + } +} - infos := &dto.DTOBadaasServerInfo{ +// Return the badaas server information +func (c *infoControllerImpl) Info(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + return &BadaasServerInfo{ Status: "OK", - Version: resources.Version, - } - return infos, nil + Version: c.Version.String(), + }, nil +} + +type BadaasServerInfo struct { + Status string `json:"status"` + Version string `json:"version"` } diff --git a/controllers/routes.go b/controllers/routes.go new file mode 100644 index 00000000..0b71536b --- /dev/null +++ b/controllers/routes.go @@ -0,0 +1,42 @@ +package controllers + +import ( + "github.com/gorilla/mux" + + "github.com/ditrit/badaas/router/middlewares" +) + +func AddInfoRoutes( + router *mux.Router, + jsonController middlewares.JSONController, + infoController InformationController, +) { + router.HandleFunc( + "/info", + jsonController.Wrap(infoController.Info), + ).Methods("GET") +} + +// Adds to the "router" the routes for handling authentication: +// /login +// /logout +// And creates a very first user +func AddAuthRoutes( + router *mux.Router, + authenticationMiddleware middlewares.AuthenticationMiddleware, + basicAuthenticationController BasicAuthenticationController, + jsonController middlewares.JSONController, +) { + router.HandleFunc( + "/login", + jsonController.Wrap(basicAuthenticationController.BasicLoginHandler), + ).Methods("POST") + + protected := router.PathPrefix("").Subrouter() + protected.Use(authenticationMiddleware.Handle) + + protected.HandleFunc( + "/logout", + jsonController.Wrap(basicAuthenticationController.Logout), + ).Methods("GET") +} diff --git a/controllers/routes_test.go b/controllers/routes_test.go new file mode 100644 index 00000000..44840b05 --- /dev/null +++ b/controllers/routes_test.go @@ -0,0 +1,72 @@ +package controllers + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "go.uber.org/zap" + + mockControllers "github.com/ditrit/badaas/mocks/controllers" + mockMiddlewares "github.com/ditrit/badaas/mocks/router/middlewares" + "github.com/ditrit/badaas/router" + "github.com/ditrit/badaas/router/middlewares" +) + +var logger, _ = zap.NewDevelopment() + +func TestAddInfoRoutes(t *testing.T) { + jsonController := middlewares.NewJSONController(logger) + informationController := NewInfoController(semver.MustParse("1.0.1")) + + router := router.NewRouter() + AddInfoRoutes( + router, + jsonController, + informationController, + ) + + response := httptest.NewRecorder() + request := httptest.NewRequest( + "GET", + "/info", + nil, + ) + + router.ServeHTTP(response, request) + assert.Equal(t, response.Code, http.StatusOK) + assert.Equal(t, response.Body.String(), "{\"status\":\"OK\",\"version\":\"1.0.1\"}") +} + +func TestAddLoginRoutes(t *testing.T) { + jsonController := middlewares.NewJSONController(logger) + + basicAuthenticationController := mockControllers.NewBasicAuthenticationController(t) + basicAuthenticationController. + On("BasicLoginHandler", mock.Anything, mock.Anything). + Return(map[string]string{"login": "called"}, nil) + + authenticationMiddleware := mockMiddlewares.NewAuthenticationMiddleware(t) + + router := router.NewRouter() + AddAuthRoutes( + router, + authenticationMiddleware, + basicAuthenticationController, + jsonController, + ) + + response := httptest.NewRecorder() + request := httptest.NewRequest( + "POST", + "/login", + nil, + ) + + router.ServeHTTP(response, request) + assert.Equal(t, response.Code, http.StatusOK) + assert.Equal(t, response.Body.String(), "{\"login\":\"called\"}") +} diff --git a/go.mod b/go.mod index 788ae80b..65c6b5d4 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ditrit/badaas go 1.18 require ( + github.com/Masterminds/semver/v3 v3.1.1 github.com/Masterminds/squirrel v1.5.3 github.com/cucumber/godog v0.12.5 github.com/ditrit/verdeter v0.4.0 diff --git a/go.sum b/go.sum index a7b58dea..f21aad14 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,6 @@ 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= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/ditrit/verdeter v0.3.2-0.20230118160022-0caba70148cd h1:r2NABj0IHkzEtp4ZGaKpWNxsWAhjQU4ezxyELvXObrk= -github.com/ditrit/verdeter v0.3.2-0.20230118160022-0caba70148cd/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/persistence/models/ProductInfo.go b/persistence/models/ProductInfo.go deleted file mode 100644 index 42040ce5..00000000 --- a/persistence/models/ProductInfo.go +++ /dev/null @@ -1,7 +0,0 @@ -package models - -// Describe the current BADAAS instance -type BadaasServerInfo struct { - Status string `json:"status"` - Version string `json:"version"` -} diff --git a/persistence/models/dto/ProductInfo.go b/persistence/models/dto/ProductInfo.go deleted file mode 100644 index 988650ea..00000000 --- a/persistence/models/dto/ProductInfo.go +++ /dev/null @@ -1,9 +0,0 @@ -package dto - -// Data Transfert Object Package - -// Describe the Server Info payload -type DTOBadaasServerInfo struct { - Status string `json:"status"` - Version string `json:"version"` -} diff --git a/resources/api.go b/resources/api.go deleted file mode 100644 index ddea375f..00000000 --- a/resources/api.go +++ /dev/null @@ -1,4 +0,0 @@ -package resources - -// Version of Badaas -const Version = "UNRELEASED" diff --git a/router/ModuleFx.go b/router/ModuleFx.go index a9f9d763..e5561dc2 100644 --- a/router/ModuleFx.go +++ b/router/ModuleFx.go @@ -8,12 +8,10 @@ import ( // RouterModule for fx var RouterModule = fx.Module( "router", + fx.Provide(NewRouter), // middlewares fx.Provide(middlewares.NewJSONController), fx.Provide(middlewares.NewMiddlewareLogger), - fx.Provide(middlewares.NewAuthenticationMiddleware), - - // create router - fx.Provide(SetupRouter), + fx.Invoke(middlewares.AddLoggerMiddleware), ) diff --git a/router/middlewares/middlewareLogger.go b/router/middlewares/middlewareLogger.go index 00f436ad..2d792e4b 100644 --- a/router/middlewares/middlewareLogger.go +++ b/router/middlewares/middlewareLogger.go @@ -4,12 +4,18 @@ import ( "fmt" "net/http" - "github.com/ditrit/badaas/configuration" + "github.com/gorilla/mux" "github.com/noirbizarre/gonja" "github.com/noirbizarre/gonja/exec" "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" ) +func AddLoggerMiddleware(router *mux.Router, middlewareLogger MiddlewareLogger) { + router.Use(middlewareLogger.Handle) +} + // Log the requests data type MiddlewareLogger interface { // [github.com/gorilla/mux] compatible middleware function diff --git a/router/router.go b/router/router.go index 1a194074..24039d72 100644 --- a/router/router.go +++ b/router/router.go @@ -1,43 +1,10 @@ package router import ( - "net/http" - "github.com/gorilla/mux" - - "github.com/ditrit/badaas/controllers" - "github.com/ditrit/badaas/router/middlewares" ) -// Default router of badaas, initialize all routes. -func SetupRouter( - // middlewares - jsonController middlewares.JSONController, - middlewareLogger middlewares.MiddlewareLogger, - authenticationMiddleware middlewares.AuthenticationMiddleware, - - // controllers - basicAuthenticationController controllers.BasicAuthenticationController, - informationController controllers.InformationController, -) http.Handler { - router := mux.NewRouter() - router.Use(middlewareLogger.Handle) - - router.HandleFunc( - "/info", - jsonController.Wrap(informationController.Info), - ).Methods("GET") - router.HandleFunc( - "/login", - jsonController.Wrap( - basicAuthenticationController.BasicLoginHandler, - ), - ).Methods("POST") - - protected := router.PathPrefix("").Subrouter() - protected.Use(authenticationMiddleware.Handle) - - protected.HandleFunc("/logout", jsonController.Wrap(basicAuthenticationController.Logout)).Methods("GET") - - return router +// Router to use in Badaas server +func NewRouter() *mux.Router { + return mux.NewRouter() } diff --git a/router/router_test.go b/router/router_test.go deleted file mode 100644 index 6685708e..00000000 --- a/router/router_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package router - -import ( - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - controllersMocks "github.com/ditrit/badaas/mocks/controllers" - middlewaresMocks "github.com/ditrit/badaas/mocks/router/middlewares" -) - -func TestSetupRouter(t *testing.T) { - jsonController := middlewaresMocks.NewJSONController(t) - middlewareLogger := middlewaresMocks.NewMiddlewareLogger(t) - authenticationMiddleware := middlewaresMocks.NewAuthenticationMiddleware(t) - - basicController := controllersMocks.NewBasicAuthenticationController(t) - informationController := controllersMocks.NewInformationController(t) - jsonController.On("Wrap", mock.Anything).Return(func(response http.ResponseWriter, request *http.Request) {}) - router := SetupRouter(jsonController, middlewareLogger, authenticationMiddleware, basicController, informationController) - assert.NotNil(t, router) -} From 1730a3335eb03bb4d313ec5511d85e121483c4a0 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 11:45:08 +0200 Subject: [PATCH 17/90] change the way badaas server is created --- badaas.go | 56 +++++++++++---- badaas_test.go | 68 +++++++++++++++++++ configuration/HttpServerConfiguration.go | 12 +++- configuration/HttpServerConfiguration_test.go | 10 ++- go.mod | 2 + go.sum | 4 ++ .../configuration/HTTPServerConfiguration.go | 14 ++++ server.go | 55 ++++++++------- server_test.go | 24 +------ services/ModuleFx.go | 14 ++++ 10 files changed, 196 insertions(+), 63 deletions(-) create mode 100644 badaas_test.go create mode 100644 services/ModuleFx.go diff --git a/badaas.go b/badaas.go index ee167cb6..c3196ac8 100644 --- a/badaas.go +++ b/badaas.go @@ -1,4 +1,4 @@ -package main +package badaas import ( "net/http" @@ -12,17 +12,45 @@ import ( "github.com/ditrit/badaas/logger" "github.com/ditrit/badaas/persistence" "github.com/ditrit/badaas/router" - "github.com/ditrit/badaas/services/sessionservice" - "github.com/ditrit/badaas/services/userservice" + "github.com/ditrit/badaas/services" "github.com/ditrit/verdeter" ) -// Badaas application, run a http-server on 8000. -func main() { +var BaDaaS = BaDaaSInitializer{} + +type BaDaaSInitializer struct { + modules []fx.Option +} + +// Allows to select which modules provided by badaas must be added to the application +func (badaas *BaDaaSInitializer) AddModules(modules ...fx.Option) *BaDaaSInitializer { + badaas.modules = append(badaas.modules, modules...) + + return badaas +} + +// Allows to provide constructors to the application +// so that the constructed objects will be available via dependency injection +func (badaas *BaDaaSInitializer) Provide(constructors ...any) *BaDaaSInitializer { + badaas.modules = append(badaas.modules, fx.Provide(constructors...)) + + return badaas +} + +// Allows to invoke functions when the application starts. +// They can take advantage of dependency injection +func (badaas *BaDaaSInitializer) Invoke(funcs ...any) *BaDaaSInitializer { + badaas.modules = append(badaas.modules, fx.Invoke(funcs...)) + + return badaas +} + +// Start the application +func (badaas BaDaaSInitializer) Start() { rootCommand := verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ Use: "badaas", Short: "BaDaaS", - Run: runHTTPServer, + Run: badaas.runHTTPServer, }) err := configuration.NewCommandInitializer(configuration.NewKeySetter()).Init(rootCommand) @@ -34,24 +62,28 @@ func main() { } // Run the http server for badaas -func runHTTPServer(cmd *cobra.Command, args []string) { - fx.New( - // Modules +func (badaas BaDaaSInitializer) runHTTPServer(cmd *cobra.Command, args []string) { + modules := []fx.Option{ + // internal modules configuration.ConfigurationModule, router.RouterModule, logger.LoggerModule, persistence.PersistanceModule, + services.ServicesModule, - fx.Provide(userservice.NewUserService), - fx.Provide(sessionservice.NewSessionService), // logger for fx fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { return &fxevent.ZapLogger{Logger: logger} }), + // create httpServer fx.Provide(newHTTPServer), - // Finally: we invoke the newly created server fx.Invoke(func(*http.Server) { /* we need this function to be empty*/ }), + } + + fx.New( + // add modules selected by user + append(modules, badaas.modules...)..., ).Run() } diff --git a/badaas_test.go b/badaas_test.go new file mode 100644 index 00000000..cae48d52 --- /dev/null +++ b/badaas_test.go @@ -0,0 +1,68 @@ +package badaas + +import ( + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/mock" + "go.uber.org/fx" + + "github.com/ditrit/badaas/configuration" +) + +func TestInvokeFunctionsWithProvidedValues(t *testing.T) { + mockObject := mockObject{} + + mockObject.On("Function", 1).Return(1) + + viper.Set(configuration.DatabasePortKey, 5000) + viper.Set(configuration.DatabaseHostKey, "localhost") + viper.Set(configuration.DatabaseUsernameKey, "badaas") + viper.Set(configuration.DatabasePasswordKey, "badaas") + viper.Set(configuration.DatabaseSslmodeKey, "disable") + viper.Set(configuration.DatabaseRetryKey, 0) + + badaas := BaDaaSInitializer{} + badaas.Provide( + newIntValue, + ).Invoke( + mockObject.Function, + shutdown, + ).Start() +} + +func TestAddModulesAreExecuted(t *testing.T) { + mockObject := mockObject{} + + mockObject.On("Function", 1).Return(1) + + badaas := BaDaaSInitializer{} + badaas.AddModules( + fx.Module( + "test module", + fx.Provide(newIntValue), + fx.Invoke(mockObject.Function), + ), + ).Invoke( + shutdown, + ).Start() +} + +func newIntValue() int { + return 1 +} + +type mockObject struct { + mock.Mock +} + +func (o *mockObject) Function(intValue int) int { + args := o.Called(intValue) + return args.Int(0) +} + +func shutdown( + shutdowner fx.Shutdowner, +) { + shutdowner.Shutdown() +} diff --git a/configuration/HttpServerConfiguration.go b/configuration/HttpServerConfiguration.go index 3f864687..a81346de 100644 --- a/configuration/HttpServerConfiguration.go +++ b/configuration/HttpServerConfiguration.go @@ -1,6 +1,7 @@ package configuration import ( + "fmt" "time" "github.com/spf13/viper" @@ -20,6 +21,7 @@ const ( // Hold the configuration values for the http server type HTTPServerConfiguration interface { ConfigurationHolder + GetAddr() string GetHost() string GetPort() int GetMaxTimeout() time.Duration @@ -56,7 +58,7 @@ func (httpServerConfiguration *hTTPServerConfigurationImpl) GetPort() int { return httpServerConfiguration.port } -// Return the maximum timout for read and write +// Return the maximum timeout for read and write func (httpServerConfiguration *hTTPServerConfigurationImpl) GetMaxTimeout() time.Duration { return httpServerConfiguration.timeout } @@ -69,3 +71,11 @@ func (httpServerConfiguration *hTTPServerConfigurationImpl) Log(logger *zap.Logg zap.Duration("timeout", httpServerConfiguration.timeout), ) } + +// Create the addr string in format: ":" +func (httpServerConfiguration *hTTPServerConfigurationImpl) GetAddr() string { + return fmt.Sprintf("%s:%d", + httpServerConfiguration.GetHost(), + httpServerConfiguration.GetPort(), + ) +} diff --git a/configuration/HttpServerConfiguration_test.go b/configuration/HttpServerConfiguration_test.go index d50119c5..899e91fe 100644 --- a/configuration/HttpServerConfiguration_test.go +++ b/configuration/HttpServerConfiguration_test.go @@ -4,12 +4,13 @@ import ( "testing" "time" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var HTTPServerConfigurationString = `server: @@ -27,12 +28,19 @@ func TestHTTPServerConfigurationGetPort(t *testing.T) { HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() assert.Equal(t, 8000, HTTPServerConfiguration.GetPort()) } + func TestHTTPServerConfigurationGetHost(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() assert.Equal(t, "0.0.0.0", HTTPServerConfiguration.GetHost()) } +func TestHTTPServerConfigurationGetAddr(t *testing.T) { + setupViperEnvironment(HTTPServerConfigurationString) + HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, "0.0.0.0:8000", HTTPServerConfiguration.GetAddr()) +} + func TestHTTPServerConfigurationGetMaxTimeout(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() diff --git a/go.mod b/go.mod index 65c6b5d4..26c990ac 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/cucumber/godog v0.12.5 github.com/ditrit/verdeter v0.4.0 github.com/google/uuid v1.3.0 + github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/jackc/pgconn v1.13.0 github.com/magiconair/properties v1.8.6 @@ -27,6 +28,7 @@ require ( github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect github.com/cucumber/messages-go/v16 v16.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/goph/emperror v0.17.2 // indirect diff --git a/go.sum b/go.sum index f21aad14..f839922d 100644 --- a/go.sum +++ b/go.sum @@ -102,6 +102,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -187,6 +189,8 @@ github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/mocks/configuration/HTTPServerConfiguration.go b/mocks/configuration/HTTPServerConfiguration.go index f70cf767..ffb21116 100644 --- a/mocks/configuration/HTTPServerConfiguration.go +++ b/mocks/configuration/HTTPServerConfiguration.go @@ -15,6 +15,20 @@ type HTTPServerConfiguration struct { mock.Mock } +// GetAddr provides a mock function with given fields: +func (_m *HTTPServerConfiguration) GetAddr() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + // GetHost provides a mock function with given fields: func (_m *HTTPServerConfiguration) GetHost() string { ret := _m.Called() diff --git a/server.go b/server.go index a6f63b72..e27b767d 100644 --- a/server.go +++ b/server.go @@ -1,15 +1,15 @@ -package main +package badaas // This file holds functions needed by the badaas rootCommand, // those functions help in creating the http.Server. import ( "context" - "fmt" "net" "net/http" - "time" + "github.com/gorilla/handlers" + "github.com/gorilla/mux" "go.uber.org/fx" "go.uber.org/zap" @@ -17,41 +17,39 @@ import ( ) // Create the server from the configuration holder and the http handler -func createServerFromConfigurationHolder(router http.Handler, httpServerConfig configuration.HTTPServerConfiguration) *http.Server { - address := addrFromConf(httpServerConfig.GetHost(), httpServerConfig.GetPort()) +func createServer(handler http.Handler, httpServerConfig configuration.HTTPServerConfiguration) *http.Server { timeout := httpServerConfig.GetMaxTimeout() - return createServer(router, address, timeout, timeout) -} -// Create an http server -func createServer(router http.Handler, address string, writeTimeout, readTimeout time.Duration) *http.Server { - srv := &http.Server{ - Handler: router, - Addr: address, + return &http.Server{ + Handler: handler, + Addr: httpServerConfig.GetAddr(), - WriteTimeout: writeTimeout, - ReadTimeout: readTimeout, + WriteTimeout: timeout, + ReadTimeout: timeout, } - return srv -} - -// Create the addr string for the http.Server -// returns ":" -func addrFromConf(host string, port int) string { - address := fmt.Sprintf("%s:%d", - host, - port, - ) - return address } func newHTTPServer( lc fx.Lifecycle, logger *zap.Logger, - router http.Handler, + router *mux.Router, httpServerConfig configuration.HTTPServerConfiguration, ) *http.Server { - srv := createServerFromConfigurationHolder(router, httpServerConfig) + handler := handlers.CORS( + handlers.AllowedMethods([]string{"GET", "POST", "DELETE", "PUT", "OPTIONS"}), + handlers.AllowedHeaders([]string{ + "Accept", "Content-Type", "Content-Length", + "Accept-Encoding", "X-CSRF-Token", "Authorization", + "Access-Control-Request-Headers", "Access-Control-Request-Method", + "Connection", "Host", "Origin", "User-Agent", "Referer", + "Cache-Control", "X-header", + }), + handlers.AllowedOrigins([]string{"*"}), + handlers.AllowCredentials(), + handlers.MaxAge(0), + )(router) + + srv := createServer(handler, httpServerConfig) lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { ln, err := net.Listen("tcp", srv.Addr) @@ -64,9 +62,10 @@ func newHTTPServer( }, OnStop: func(ctx context.Context) error { // Flush the logger - logger.Sync() + _ = logger.Sync() return srv.Shutdown(ctx) }, }) + return srv } diff --git a/server_test.go b/server_test.go index 302e5d31..cb231be3 100644 --- a/server_test.go +++ b/server_test.go @@ -1,37 +1,19 @@ -package main +package badaas // This files holds the tests for the server.go file. import ( "net/http" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/ditrit/badaas/configuration" ) -func Test_addrFromConf(t *testing.T) { - expected := "192.168.236.222:25100" - addr := addrFromConf("192.168.236.222", 25100) - assert.Equal(t, expected, addr) -} - -func Test_createServer(t *testing.T) { - handl := http.NewServeMux() - timeout := time.Duration(time.Second) - srv := createServer( - handl, - "localhost:8000", - timeout, timeout, - ) - assert.NotNil(t, srv) -} - -func TestCreateServerFromConfigurationHolder(t *testing.T) { +func TestCreateServer(t *testing.T) { handl := http.NewServeMux() - srv := createServerFromConfigurationHolder(handl, configuration.NewHTTPServerConfiguration()) + srv := createServer(handl, configuration.NewHTTPServerConfiguration()) assert.NotNil(t, srv) } diff --git a/services/ModuleFx.go b/services/ModuleFx.go new file mode 100644 index 00000000..8b03a9b6 --- /dev/null +++ b/services/ModuleFx.go @@ -0,0 +1,14 @@ +package services + +import ( + "go.uber.org/fx" + + "github.com/ditrit/badaas/services/sessionservice" + "github.com/ditrit/badaas/services/userservice" +) + +var ServicesModule = fx.Module( + "services", + fx.Provide(userservice.NewUserService), + fx.Provide(sessionservice.NewSessionService), +) From 25f8e5747af0620f537dd80cc4d091fc2d31327e Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Fri, 28 Jul 2023 10:35:58 +0200 Subject: [PATCH 18/90] move test e2e to test_e2e/ and docker to docker/ --- .gitignore | 2 +- badaas_e2e_test.go | 56 -- docker-compose.yml | 19 - docker/test_api/Dockerfile | 10 + .../e2e/api => docker/test_api}/badaas.yml | 7 +- docker/test_api/docker-compose.yml | 18 + docker/test_db/docker-compose.yml | 17 + {scripts/e2e/db => docker/test_db}/init.sh | 23 +- {scripts/e2e/db => docker/test_db}/logs.yaml | 0 docker/wait_for_api.sh | 6 + features/api_info.feature | 7 - go.mod | 69 +- go.sum | 310 ++----- go.work | 6 + go.work.sum | 407 +++++++++ scripts/e2e/api/Dockerfile | 12 - scripts/e2e/db/Dockerfile | 15 - scripts/e2e/docker-compose.yml | 25 - test_e2e/badaas_e2e_test.go | 105 +++ test_e2e/features/api_info.feature | 7 + .../features}/basic_auth.feature | 29 +- test_e2e/go.mod | 69 ++ test_e2e/go.sum | 847 ++++++++++++++++++ .../http_support_test.go | 151 ++-- test_e2e/setup.go | 24 + test_e2e/test_api.go | 21 + 26 files changed, 1770 insertions(+), 492 deletions(-) delete mode 100644 badaas_e2e_test.go delete mode 100644 docker-compose.yml create mode 100644 docker/test_api/Dockerfile rename {scripts/e2e/api => docker/test_api}/badaas.yml (85%) create mode 100644 docker/test_api/docker-compose.yml create mode 100644 docker/test_db/docker-compose.yml rename {scripts/e2e/db => docker/test_db}/init.sh (55%) mode change 100644 => 100755 rename {scripts/e2e/db => docker/test_db}/logs.yaml (100%) create mode 100755 docker/wait_for_api.sh delete mode 100644 features/api_info.feature create mode 100644 go.work create mode 100644 go.work.sum delete mode 100644 scripts/e2e/api/Dockerfile delete mode 100644 scripts/e2e/db/Dockerfile delete mode 100644 scripts/e2e/docker-compose.yml create mode 100644 test_e2e/badaas_e2e_test.go create mode 100644 test_e2e/features/api_info.feature rename {features => test_e2e/features}/basic_auth.feature (53%) create mode 100644 test_e2e/go.mod create mode 100644 test_e2e/go.sum rename http_support_test.go => test_e2e/http_support_test.go (59%) create mode 100644 test_e2e/setup.go create mode 100644 test_e2e/test_api.go diff --git a/.gitignore b/.gitignore index d477eb47..25cd84cf 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ # vendor/ # Go workspace file -go.work +# go.work # cockroach files node* diff --git a/badaas_e2e_test.go b/badaas_e2e_test.go deleted file mode 100644 index 8b5e2072..00000000 --- a/badaas_e2e_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "net/http" - "net/http/cookiejar" - "os" - "testing" - "time" - - "github.com/cucumber/godog" - "github.com/cucumber/godog/colors" - "github.com/spf13/pflag" -) - -type TestContext struct { - statusCode int - json map[string]interface{} - httpClient *http.Client -} - -var opts = godog.Options{Output: colors.Colored(os.Stdout)} - -func init() { - godog.BindCommandLineFlags("godog.", &opts) -} - -func TestMain(m *testing.M) { - pflag.Parse() - opts.Paths = pflag.Args() - - status := godog.TestSuite{ - Name: "godogs", - ScenarioInitializer: InitializeScenario, - Options: &opts, - }.Run() - - os.Exit(status) -} - -func InitializeScenario(ctx *godog.ScenarioContext) { - t := &TestContext{} - jar, err := cookiejar.New(nil) - if err != nil { - panic(err) - } - t.httpClient = &http.Client{ - Transport: http.DefaultTransport, - Timeout: time.Duration(5 * time.Second), - Jar: jar, - } - - ctx.Step(`^I request "(.+)"$`, t.requestGET) - ctx.Step(`^I expect status code is "(\d+)"$`, t.assertStatusCode) - ctx.Step(`^I expect response field "(.+)" is "(.+)"$`, t.assertResponseFieldIsEquals) - ctx.Step(`^I request "(.+)" with method "(.+)" with json$`, t.requestWithJson) -} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index e616ca88..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3.5' - -services: - db: - image: cockroachdb/cockroach:latest - ports: - - "26257:26257" - - "8080:8080" # Web based dashboard - command: start-single-node --insecure - volumes: - - "${PWD}/_temp/cockroach-data/crdb:/cockroach/cockroach-data" - - - api: - build: ditrit/badaas:latest # local image - ports: - - "8000:8000" - depends_on: - - db diff --git a/docker/test_api/Dockerfile b/docker/test_api/Dockerfile new file mode 100644 index 00000000..fbeba3a2 --- /dev/null +++ b/docker/test_api/Dockerfile @@ -0,0 +1,10 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +FROM golang:1.19-alpine +RUN addgroup -S badaas \ + && adduser -S badaas -G badaas +USER badaas +WORKDIR /badaas +ENV CGO_ENABLED=0 +ENV GOOS=linux +ENV GOARCH=amd64 +ENV GOFLAGS=-buildvcs=false \ No newline at end of file diff --git a/scripts/e2e/api/badaas.yml b/docker/test_api/badaas.yml similarity index 85% rename from scripts/e2e/api/badaas.yml rename to docker/test_api/badaas.yml index 831b12d8..71c3ab7e 100644 --- a/scripts/e2e/api/badaas.yml +++ b/docker/test_api/badaas.yml @@ -3,16 +3,15 @@ server: host: "0.0.0.0" # listening on all interfaces timeout: 15 # in seconds pagination: - page: + page: max: 10 - database: - host: e2e-db-1 + host: badaas-test-db port: 26257 sslmode: disable username: root - password: postres + password: postgres name: badaas_db init: retry: 10 diff --git a/docker/test_api/docker-compose.yml b/docker/test_api/docker-compose.yml new file mode 100644 index 00000000..2a6e0173 --- /dev/null +++ b/docker/test_api/docker-compose.yml @@ -0,0 +1,18 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +version: '3.5' + +services: + api: + container_name: "badaas-test-api" + build: + context: ../.. + dockerfile: ./docker/test_api/Dockerfile + image: badaas-test-api + volumes: + - ../..:/badaas:ro + entrypoint: go run /badaas/test_e2e/test_api.go --config_path /badaas/docker/test_api/badaas.yml + ports: + - "8000:8000" + restart: always + depends_on: + - db diff --git a/docker/test_db/docker-compose.yml b/docker/test_db/docker-compose.yml new file mode 100644 index 00000000..b937793b --- /dev/null +++ b/docker/test_db/docker-compose.yml @@ -0,0 +1,17 @@ +# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION +version: '3.5' + +services: + db: + container_name: "badaas-test-db" + image: cockroachdb/cockroach:latest + volumes: + - .:/cockroach/files + working_dir: /cockroach + entrypoint: ./files/init.sh + ports: + - "26257:26257" + - "8080:8080" # Web based dashboard + environment: + - COCKROACH_USER=root + - COCKROACH_DATABASE=badaas_db diff --git a/scripts/e2e/db/init.sh b/docker/test_db/init.sh old mode 100644 new mode 100755 similarity index 55% rename from scripts/e2e/db/init.sh rename to docker/test_db/init.sh index 7231f8bf..3f65962b --- a/scripts/e2e/db/init.sh +++ b/docker/test_db/init.sh @@ -1,33 +1,32 @@ #!/bin/sh -echo "******************************* Listing Env Variables..." -printenv -echo "******************************* starting single cockroach node..." -./cockroach start-single-node --insecure --log-config-file=logs.yaml --background +set -e +echo "******************************* Listing Env Variables..." +printenv +echo "******************************* Starting single cockroach node..." -echo "******************************* Creating user" -# cockroach user set ${COCKROACH_USER} --password 1234 --echo-sql -# cockroach user ls +./cockroach start-single-node --insecure --log-config-file=files/logs.yaml --background echo "******************************* Init database" echo "******************************* |=> Creating init.sql" cat > init.sql < Applying init.sql" ./cockroach sql --insecure --file init.sql echo "******************************* To the moon" -cd /cockroach/cockroach-data/logs -tail -f cockroach.log \ No newline at end of file +# tail logs to make them accesible with docker logs +tail -f cockroach-data/logs/cockroach.log \ No newline at end of file diff --git a/scripts/e2e/db/logs.yaml b/docker/test_db/logs.yaml similarity index 100% rename from scripts/e2e/db/logs.yaml rename to docker/test_db/logs.yaml diff --git a/docker/wait_for_api.sh b/docker/wait_for_api.sh new file mode 100755 index 00000000..9c586dde --- /dev/null +++ b/docker/wait_for_api.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +until $(curl --output /dev/null --silent --fail http://localhost:$1); do + printf '.' + sleep 5 +done \ No newline at end of file diff --git a/features/api_info.feature b/features/api_info.feature deleted file mode 100644 index f7b6790f..00000000 --- a/features/api_info.feature +++ /dev/null @@ -1,7 +0,0 @@ -Feature: Test info controller - -Scenario: Server should return ok and current project version - When I request "/info" - Then I expect status code is "200" - And I expect response field "status" is "OK" - And I expect response field "version" is "UNRELEASED" diff --git a/go.mod b/go.mod index 26c990ac..fe0924be 100644 --- a/go.mod +++ b/go.mod @@ -3,69 +3,60 @@ module github.com/ditrit/badaas go 1.18 require ( - github.com/Masterminds/semver/v3 v3.1.1 - github.com/Masterminds/squirrel v1.5.3 - github.com/cucumber/godog v0.12.5 + github.com/Masterminds/squirrel v1.5.4 github.com/ditrit/verdeter v0.4.0 github.com/google/uuid v1.3.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 - github.com/jackc/pgconn v1.13.0 - github.com/magiconair/properties v1.8.6 + github.com/jackc/pgconn v1.14.1 + github.com/magiconair/properties v1.8.7 github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 - github.com/spf13/cobra v1.5.0 - github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.13.0 - github.com/stretchr/testify v1.8.1 - go.uber.org/fx v1.18.2 - go.uber.org/zap v1.23.0 - golang.org/x/crypto v0.1.0 - gorm.io/driver/postgres v1.4.5 - gorm.io/gorm v1.24.1 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.16.0 + github.com/stretchr/testify v1.8.4 + go.uber.org/fx v1.20.0 + go.uber.org/zap v1.24.0 + golang.org/x/crypto v0.11.0 + gorm.io/driver/postgres v1.5.2 + gorm.io/gorm v1.25.2 ) +require github.com/felixge/httpsnoop v1.0.1 // indirect + require ( - github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect - github.com/cucumber/messages-go/v16 v16.0.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 github.com/davecgh/go-spew v1.1.1 // indirect - github.com/felixge/httpsnoop v1.0.1 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/goph/emperror v0.17.2 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-memdb v1.3.3 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.1 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.12.0 // indirect - github.com/jackc/pgx/v4 v4.17.2 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.4.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/spf13/afero v1.9.2 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/dig v1.15.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f839922d..68dd6625 100644 --- a/go.sum +++ b/go.sum @@ -25,7 +25,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -41,28 +40,17 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= -github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmuller/arrow v0.0.0-20180318014521-b14bfde8dff2/go.mod h1:+voQMVaya0tr8p3W33Qxj/dKOjZNCepW+k8JJvt91gk= github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -70,29 +58,14 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= -github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= -github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs= -github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc= -github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= -github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY= -github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= 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= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -101,33 +74,21 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -163,7 +124,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -187,53 +148,19 @@ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 github.com/goph/emperror v0.17.1/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= -github.com/hashicorp/go-memdb v1.3.3 h1:oGfEWrFuxtIUF3W2q/Jzt6G85TrMk9ey6XfYLvVe1Wo= -github.com/hashicorp/go-memdb v1.3.3/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -243,9 +170,8 @@ github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= -github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -261,48 +187,34 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= -github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= -github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= -github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= +github.com/jackc/pgx/v5 v5.4.2 h1:u1gmGDwbdRUZiwisBm/Ky2M14uQyUP65bG8+20nnyrg= +github.com/jackc/pgx/v5 v5.4.2/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -314,52 +226,28 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 h1:8HaKr2WO2B5XKEFbJE9Z7W8mWC6+dL3jZCw53Dbl0oI= github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61/go.mod h1:WboHq+I9Ck8PwKsVFJNrpiRyngXhquRSTWBGwuSWOrg= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -367,63 +255,34 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= -github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -437,21 +296,19 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -460,29 +317,21 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= -go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= -go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= -go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= +go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -494,10 +343,11 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -531,13 +381,10 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -569,6 +416,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -588,13 +437,10 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -612,7 +458,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -635,13 +480,18 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -650,19 +500,18 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -674,9 +523,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -684,7 +530,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -710,6 +555,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -804,36 +650,24 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc= -gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg= -gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs= -gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= +gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go.work b/go.work new file mode 100644 index 00000000..241c23dd --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.18 + +use ( + . + ./test_e2e +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 00000000..fcf390bc --- /dev/null +++ b/go.work.sum @@ -0,0 +1,407 @@ +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= +github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/pgx/v5 v5.4.2 h1:u1gmGDwbdRUZiwisBm/Ky2M14uQyUP65bG8+20nnyrg= +github.com/jackc/pgx/v5 v5.4.2/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY= +github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/crypt v0.10.0/go.mod h1:gwTNHQVoOS3xp9Xvz5LLR+1AauC5M6880z5NWzdhOyQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= +go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= +gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/scripts/e2e/api/Dockerfile b/scripts/e2e/api/Dockerfile deleted file mode 100644 index 49bdc272..00000000 --- a/scripts/e2e/api/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -# builder image -FROM golang:1.19-alpine as builder -RUN apk add build-base -WORKDIR /app -COPY . . -RUN CGO_ENABLED=1 go build --race -a -o badaas . - -FROM alpine:3.16.2 -ENV BADAAS_PORT=8000 -COPY --from=builder /app/badaas . -COPY ./scripts/e2e/api/badaas.yml . -ENTRYPOINT [ "./badaas" ] diff --git a/scripts/e2e/db/Dockerfile b/scripts/e2e/db/Dockerfile deleted file mode 100644 index 6f892635..00000000 --- a/scripts/e2e/db/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM cockroachdb/cockroach:latest - -LABEL maintainer="tjveil@gmail.com" - -ADD init.sh /cockroach/ -RUN chmod a+x /cockroach/init.sh - -ADD logs.yaml /cockroach/ - -WORKDIR /cockroach/ - -EXPOSE 8080 -EXPOSE 26257 - -ENTRYPOINT ["/cockroach/init.sh"] \ No newline at end of file diff --git a/scripts/e2e/docker-compose.yml b/scripts/e2e/docker-compose.yml deleted file mode 100644 index a6b936a3..00000000 --- a/scripts/e2e/docker-compose.yml +++ /dev/null @@ -1,25 +0,0 @@ -# DEVELOPMENT ONLY, DO NOT USE FOR PRODUCTION -version: '3.5' - -services: - db: - build: db/. - ports: - - "26257:26257" - - "8080:8080" # Web based dashboard - environment: - - COCKROACH_USER=root - - COCKROACH_DB=badaas_db - - api: - build: - context: ./../.. - dockerfile: ./scripts/e2e/api/Dockerfile - ports: - - "8000:8000" - restart: always - # environment: - # - BADAAS_PORT=8000 - # - BADAAS_MAX_TIMOUT= 15 # in seconds - depends_on: - - db diff --git a/test_e2e/badaas_e2e_test.go b/test_e2e/badaas_e2e_test.go new file mode 100644 index 00000000..b31a2e5e --- /dev/null +++ b/test_e2e/badaas_e2e_test.go @@ -0,0 +1,105 @@ +package main + +import ( + "context" + "log" + "net/http" + "net/http/cookiejar" + "os" + "testing" + "time" + + "github.com/cucumber/godog" + "github.com/cucumber/godog/colors" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "go.uber.org/zap" + "gorm.io/gorm" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/persistence/gormdatabase" + "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/services/auth/protocols/basicauth" +) + +type TestContext struct { + statusCode int + json any + httpClient *http.Client +} + +var ( + opts = godog.Options{Output: colors.Colored(os.Stdout)} + db *gorm.DB +) + +func init() { + godog.BindCommandLineFlags("godog.", &opts) +} + +func TestMain(_ *testing.M) { + pflag.Parse() + opts.Paths = pflag.Args() + + logger, _ := zap.NewDevelopment() + var err error + + viper.Set(configuration.DatabasePortKey, 26257) + viper.Set(configuration.DatabaseHostKey, "localhost") + viper.Set(configuration.DatabaseNameKey, "badaas_db") + viper.Set(configuration.DatabaseUsernameKey, "root") + viper.Set(configuration.DatabasePasswordKey, "postgres") + viper.Set(configuration.DatabaseSslmodeKey, "disable") + viper.Set(configuration.DatabaseRetryKey, 10) + viper.Set(configuration.DatabaseRetryDurationKey, 5) + db, err = gormdatabase.CreateDatabaseConnectionFromConfiguration( + logger, + configuration.NewDatabaseConfiguration(), + ) + if err != nil { + log.Fatalln("Unable to connect to database : ", err) + } + + status := godog.TestSuite{ + Name: "godogs", + ScenarioInitializer: InitializeScenario, + Options: &opts, + }.Run() + + os.Exit(status) +} + +func InitializeScenario(ctx *godog.ScenarioContext) { + t := &TestContext{} + jar, err := cookiejar.New(nil) + if err != nil { + panic(err) + } + t.httpClient = &http.Client{ + Transport: http.DefaultTransport, + Timeout: time.Duration(5 * time.Second), + Jar: jar, + } + + ctx.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + // clean db before each scenario + CleanDB(db) + + adminUser := &models.User{ + Username: "admin", + Email: "admin-no-reply@badaas.com", + Password: basicauth.SaltAndHashPassword("admin"), + } + err = db.Create(&adminUser).Error + if err != nil { + log.Fatalln(err) + } + + return ctx, nil + }) + + ctx.Step(`^I request "(.+)"$`, t.requestGet) + ctx.Step(`^status code is "(\d+)"$`, t.assertStatusCode) + ctx.Step(`^response field "(.+)" is "(.+)"$`, t.assertResponseFieldIsEquals) + ctx.Step(`^I request "(.+)" with method "(.+)" with json$`, t.requestWithJson) +} diff --git a/test_e2e/features/api_info.feature b/test_e2e/features/api_info.feature new file mode 100644 index 00000000..b14da5b7 --- /dev/null +++ b/test_e2e/features/api_info.feature @@ -0,0 +1,7 @@ +Feature: Test info controller + +Scenario: Server should return ok and current project version + When I request "/info" + Then status code is "200" + And response field "status" is "OK" + And response field "version" is "0.0.0-unreleased" diff --git a/features/basic_auth.feature b/test_e2e/features/basic_auth.feature similarity index 53% rename from features/basic_auth.feature rename to test_e2e/features/basic_auth.feature index d1371744..d2d89dc9 100644 --- a/features/basic_auth.feature +++ b/test_e2e/features/basic_auth.feature @@ -1,34 +1,35 @@ Feature: Login as superadmin using the basic authentication Scenario: Should be a success on valid credentials - Given I request "/login" with method "POST" with json + When I request "/login" with method "POST" with json | key | value | type | | email | admin-no-reply@badaas.com | string | | password | admin | string | - Then I expect status code is "200" + Then status code is "200" + And response field "username" is "admin" + And response field "email" is "admin-no-reply@badaas.com" Scenario: Should be an error on invalid credentials - Given I request "/login" with method "POST" with json + When I request "/login" with method "POST" with json | key | value | type | | email | admin-no-reply@badaas.com | string | | password | wrongpassword | string | - Then I expect status code is "401" - And I expect response field "err" is "wrong password" - And I expect response field "msg" is "the provided password is incorrect" - And I expect response field "status" is "Unauthorized" + Then status code is "401" + And response field "err" is "wrong password" + And response field "msg" is "the provided password is incorrect" + And response field "status" is "Unauthorized" Scenario: Should be a success if we logout after a successful login Given I request "/login" with method "POST" with json | key | value | type | | email | admin-no-reply@badaas.com | string | | password | admin | string | - Then I expect status code is "200" - And I expect response field "username" is "admin" - And I expect response field "email" is "admin-no-reply@badaas.com" + When I request "/logout" + Then status code is "200" Scenario: Should be an error if we try to logout without login first When I request "/logout" - Then I expect status code is "401" - And I expect response field "err" is "Authentication Error" - And I expect response field "msg" is "not authenticated" - And I expect response field "status" is "Unauthorized" + Then status code is "401" + And response field "err" is "Authentication Error" + And response field "msg" is "not authenticated" + And response field "status" is "Unauthorized" diff --git a/test_e2e/go.mod b/test_e2e/go.mod new file mode 100644 index 00000000..ecdb6df1 --- /dev/null +++ b/test_e2e/go.mod @@ -0,0 +1,69 @@ +module github.com/ditrit/badaas/test_e2e + +go 1.18 + +require ( + github.com/Masterminds/semver/v3 v3.1.1 + github.com/cucumber/godog v0.12.5 + github.com/ditrit/badaas v0.0.0-20230727191545-7a2596b72797 + github.com/elliotchance/pie/v2 v2.5.2 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.13.0 + go.uber.org/zap v1.23.0 + gorm.io/gorm v1.24.1 +) + +require ( + github.com/Masterminds/squirrel v1.5.3 // indirect + github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect + github.com/cucumber/messages-go/v16 v16.0.1 // indirect + github.com/ditrit/verdeter v0.4.0 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/goph/emperror v0.17.2 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-memdb v1.3.3 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.13.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.12.0 // indirect + github.com/jackc/pgx/v4 v4.17.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/spf13/afero v1.9.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cobra v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/dig v1.15.0 // indirect + go.uber.org/fx v1.18.2 // indirect + go.uber.org/multierr v1.8.0 // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect + golang.org/x/sys v0.1.0 // indirect + golang.org/x/text v0.4.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.4.5 // indirect +) diff --git a/test_e2e/go.sum b/test_e2e/go.sum new file mode 100644 index 00000000..0912407a --- /dev/null +++ b/test_e2e/go.sum @@ -0,0 +1,847 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= +github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bmuller/arrow v0.0.0-20180318014521-b14bfde8dff2/go.mod h1:+voQMVaya0tr8p3W33Qxj/dKOjZNCepW+k8JJvt91gk= +github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= +github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= +github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs= +github.com/cucumber/godog v0.12.5/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc= +github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= +github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY= +github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= +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= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/ditrit/badaas v0.0.0-20230727191545-7a2596b72797 h1:Y/dcxQT0l+m+vjspbhLSBvq6dWy6ijt3SErmegRqLiM= +github.com/ditrit/badaas v0.0.0-20230727191545-7a2596b72797/go.mod h1:ZfxtnvFsdevSvx3qKf//Js1MVqDqtysPSMbvUfn4Dvk= +github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= +github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= +github.com/elliotchance/pie/v2 v2.5.2 h1:jRENMmysCljhUmyT8ITKV0Atp6Lukm3XpeqaI87POsM= +github.com/elliotchance/pie/v2 v2.5.2/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/goph/emperror v0.17.1/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= +github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= +github.com/hashicorp/go-memdb v1.3.3 h1:oGfEWrFuxtIUF3W2q/Jzt6G85TrMk9ey6XfYLvVe1Wo= +github.com/hashicorp/go-memdb v1.3.3/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= +github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= +github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= +github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= +github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +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/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 h1:8HaKr2WO2B5XKEFbJE9Z7W8mWC6+dL3jZCw53Dbl0oI= +github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61/go.mod h1:WboHq+I9Ck8PwKsVFJNrpiRyngXhquRSTWBGwuSWOrg= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= +go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= +go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= +go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 h1:ba9YlqfDGTTQ5aZ2fwOoQ1hf32QySyQkR6ODGDzHlnE= +golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc= +gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg= +gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs= +gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/http_support_test.go b/test_e2e/http_support_test.go similarity index 59% rename from http_support_test.go rename to test_e2e/http_support_test.go index b1f5ec7c..a53ab405 100644 --- a/http_support_test.go +++ b/test_e2e/http_support_test.go @@ -1,26 +1,56 @@ package main import ( - "context" "encoding/json" "fmt" "io" - "log" "net/http" "strconv" "strings" "github.com/cucumber/godog" + "github.com/elliotchance/pie/v2" ) const BaseUrl = "http://localhost:8000" -func (t *TestContext) requestGET(url string) error { - response, err := t.httpClient.Get(fmt.Sprintf("%s%s", BaseUrl, url)) +func (t *TestContext) requestGet(url string) error { + return t.request(url, http.MethodGet, nil, nil) +} + +func (t *TestContext) requestWithJson(url, method string, jsonTable *godog.Table) error { + return t.request(url, method, nil, jsonTable) +} + +func (t *TestContext) request(url, method string, query map[string]string, jsonTable *godog.Table) error { + var payload io.Reader + var err error + if jsonTable != nil { + payload, err = buildJSONFromTable(jsonTable) + if err != nil { + return err + } + } + + method, err = checkMethod(method) if err != nil { return err } + request, err := http.NewRequest(method, BaseUrl+url, payload) + if err != nil { + return fmt.Errorf("failed to build request ERROR=%s", err.Error()) + } + q := request.URL.Query() + for k, v := range query { + q.Add(k, v) + } + request.URL.RawQuery = q.Encode() + + response, err := t.httpClient.Do(request) + if err != nil { + return fmt.Errorf("failed to run request ERROR=%s", err.Error()) + } t.storeResponseInContext(response) return nil } @@ -28,56 +58,66 @@ func (t *TestContext) requestGET(url string) error { func (t *TestContext) storeResponseInContext(response *http.Response) { t.statusCode = response.StatusCode - buffer, err := io.ReadAll(response.Body) + err := json.NewDecoder(response.Body).Decode(&t.json) if err != nil { - log.Panic(err) + t.json = map[string]any{} } - response.Body.Close() - json.Unmarshal(buffer, &t.json) } -func (t *TestContext) assertStatusCode(_ context.Context, expectedStatusCode int) error { +func (t *TestContext) assertStatusCode(expectedStatusCode int) error { if t.statusCode != expectedStatusCode { return fmt.Errorf("expect status code %d but is %d", expectedStatusCode, t.statusCode) } return nil } -func (t *TestContext) assertResponseFieldIsEquals(field string, expectedValue string) error { - value := t.json[field].(string) - if !assertValue(value, expectedValue) { - return fmt.Errorf("expect response field %s is %s but is %s", field, expectedValue, value) - } - return nil -} -func assertValue(value string, expectedValue string) bool { - return expectedValue == value -} +func (t *TestContext) assertResponseFieldIsEquals(field string, expectedValue string) error { + fields := strings.Split(field, ".") + jsonMap := t.json.(map[string]any) -func (t *TestContext) requestWithJson(url, method string, jsonTable *godog.Table) error { - payload, err := buildJSONFromTable(jsonTable) - if err != nil { - return err + for _, field := range fields[:len(fields)-1] { + intValue, present := jsonMap[field] + if !present { + return fmt.Errorf("expected response field %s to be %s but it is not present", field, expectedValue) + } + jsonMap = intValue.(map[string]any) } - method, err = checkMethod(method) - if err != nil { - return err + lastValue, present := jsonMap[pie.Last(fields)] + if !present { + return fmt.Errorf("expected response field %s to be %s but it is not present", field, expectedValue) } - request, err := http.NewRequest(method, BaseUrl+url, payload) - if err != nil { - return fmt.Errorf("failed to build request ERROR=%s", err.Error()) - } - response, err := t.httpClient.Do(request) - if err != nil { - return fmt.Errorf("failed to run request ERROR=%s", err.Error()) + + if !assertValue(lastValue, expectedValue) { + return fmt.Errorf("expected response field %s to be %s but is %v", field, expectedValue, lastValue) } - t.storeResponseInContext(response) + return nil } -// build a json payload in the form of a reader from a godog.Table -func buildJSONFromTable(table *godog.Table) (io.Reader, error) { +func assertValue(value any, expectedValue string) bool { + switch value.(type) { + case string: + return expectedValue == value + case int: + expectedValueInt, err := strconv.Atoi(expectedValue) + if err != nil { + panic(err) + } + return expectedValueInt == value + case float64: + expectedValueFloat, err := strconv.ParseFloat(expectedValue, 64) + if err != nil { + panic(err) + } + return expectedValueFloat == value + default: + panic("unsupported format") + } +} + +// build a map from a godog.Table +func buildMapFromTable(table *godog.Table) (map[string]any, error) { data := make(map[string]any, 0) for indexRow, row := range table.Rows { if indexRow == 0 { @@ -112,6 +152,13 @@ func buildJSONFromTable(table *godog.Table) (io.Reader, error) { return nil, fmt.Errorf("can't parse %q as float for key %q", valueAsString, key) } data[key] = floatingNumber + case jsonValueType: + jsonMap := map[string]string{} + err := json.Unmarshal([]byte(valueAsString), &jsonMap) + if err != nil { + return nil, fmt.Errorf("can't parse %q as json for key %q", valueAsString, key) + } + data[key] = jsonMap case nullValueType: data[key] = nil default: @@ -120,6 +167,17 @@ func buildJSONFromTable(table *godog.Table) (io.Reader, error) { } } + + return data, nil +} + +// build a json payload in the form of a reader from a godog.Table +func buildJSONFromTable(table *godog.Table) (io.Reader, error) { + data, err := buildMapFromTable(table) + if err != nil { + panic("should not return an error") + } + bytes, err := json.Marshal(data) if err != nil { panic("should not return an error") @@ -133,11 +191,13 @@ const ( integerValueType = "integer" floatValueType = "float" nullValueType = "null" + jsonValueType = "json" ) // check if the method is allowed and sanitize the string func checkMethod(method string) (string, error) { - allowedMethods := []string{http.MethodGet, + allowedMethods := []string{ + http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, @@ -145,24 +205,15 @@ func checkMethod(method string) (string, error) { http.MethodDelete, http.MethodConnect, http.MethodOptions, - http.MethodTrace} + http.MethodTrace, + } sanitizedMethod := strings.TrimSpace(strings.ToUpper(method)) - if !contains( + if !pie.Contains( allowedMethods, sanitizedMethod, ) { return "", fmt.Errorf("%q is not a valid HTTP method (please choose between %v)", method, allowedMethods) } - return sanitizedMethod, nil - -} -// return true if the set contains the target -func contains[T comparable](set []T, target T) bool { - for _, elem := range set { - if target == elem { - return true - } - } - return false + return sanitizedMethod, nil } diff --git a/test_e2e/setup.go b/test_e2e/setup.go new file mode 100644 index 00000000..d554670b --- /dev/null +++ b/test_e2e/setup.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + + "gorm.io/gorm" + + "github.com/ditrit/badaas/persistence/models" +) + +var ListOfTables = []any{ + models.Session{}, + models.User{}, +} + +func CleanDB(db *gorm.DB) { + // clean database to ensure independency between tests + for _, table := range ListOfTables { + err := db.Unscoped().Where("1 = 1").Delete(table).Error + if err != nil { + log.Fatalln("could not clean database: ", err) + } + } +} diff --git a/test_e2e/test_api.go b/test_e2e/test_api.go new file mode 100644 index 00000000..8100b731 --- /dev/null +++ b/test_e2e/test_api.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/Masterminds/semver/v3" + + "github.com/ditrit/badaas" + "github.com/ditrit/badaas/controllers" +) + +func main() { + badaas.BaDaaS.AddModules( + controllers.InfoControllerModule, + controllers.AuthControllerModule, + ).Provide( + NewAPIVersion, + ).Start() +} + +func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") +} From f1bed03f2e6532e8c867b80311975bf75303d6e0 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Fri, 28 Jul 2023 10:36:48 +0200 Subject: [PATCH 19/90] do not run auto migration on test e2e execution + automigration refactor --- persistence/ModuleFx.go | 10 ++++--- persistence/gormdatabase/db.go | 48 ++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/persistence/ModuleFx.go b/persistence/ModuleFx.go index d99d1c02..708030fd 100644 --- a/persistence/ModuleFx.go +++ b/persistence/ModuleFx.go @@ -1,11 +1,13 @@ package persistence import ( + "github.com/google/uuid" + + "go.uber.org/fx" + "github.com/ditrit/badaas/persistence/gormdatabase" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/repository" - "github.com/google/uuid" - "go.uber.org/fx" ) // PersistanceModule for fx @@ -18,9 +20,9 @@ import ( var PersistanceModule = fx.Module( "persistence", // Database connection - fx.Provide(gormdatabase.CreateDatabaseConnectionFromConfiguration), + fx.Provide(gormdatabase.SetupDatabaseConnection), - //repositories + // repositories fx.Provide(repository.NewCRUDRepository[models.Session, uuid.UUID]), fx.Provide(repository.NewCRUDRepository[models.User, uuid.UUID]), ) diff --git a/persistence/gormdatabase/db.go b/persistence/gormdatabase/db.go index ddd5a22b..e62e1c81 100644 --- a/persistence/gormdatabase/db.go +++ b/persistence/gormdatabase/db.go @@ -4,12 +4,13 @@ import ( "fmt" "time" - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/persistence/gormdatabase/gormzap" - "github.com/ditrit/badaas/persistence/models" "go.uber.org/zap" "gorm.io/driver/postgres" "gorm.io/gorm" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/persistence/gormdatabase/gormzap" + "github.com/ditrit/badaas/persistence/models" ) // Create the dsn string from the configuration @@ -32,7 +33,25 @@ func createDsn(host, username, password, sslmode, dbname string, port int) strin ) } -// Initialize the database with using the database configuration +// Creates the database object with using the database configuration +// and then executes the auto-migration +func SetupDatabaseConnection(logger *zap.Logger, databaseConfiguration configuration.DatabaseConfiguration) (*gorm.DB, error) { + db, err := CreateDatabaseConnectionFromConfiguration(logger, databaseConfiguration) + if err != nil { + return nil, err + } + + err = AutoMigrate(logger, db) + if err != nil { + logger.Error("migration failed") + return nil, err + } + logger.Info("AutoMigration was executed successfully") + + return db, nil +} + +// Creates the database object with using the database configuration func CreateDatabaseConnectionFromConfiguration(logger *zap.Logger, databaseConfiguration configuration.DatabaseConfiguration) (*gorm.DB, error) { dsn := createDsnFromConf(databaseConfiguration) var err error @@ -41,12 +60,6 @@ func CreateDatabaseConnectionFromConfiguration(logger *zap.Logger, databaseConfi database, err = initializeDBFromDsn(dsn, logger) if err == nil { logger.Sugar().Debugf("Database connection is active") - err = AutoMigrate(logger, database) - if err != nil { - logger.Error("migration failed") - return nil, err - } - logger.Info("AutoMigration was executed successfully") return database, err } logger.Sugar().Debugf("Database connection failed with error %q", err.Error()) @@ -54,6 +67,7 @@ func CreateDatabaseConnectionFromConfiguration(logger *zap.Logger, databaseConfi numberRetry+1, databaseConfiguration.GetRetry(), databaseConfiguration.GetRetryTime().String()) time.Sleep(databaseConfiguration.GetRetryTime()) } + return nil, err } @@ -62,7 +76,6 @@ func initializeDBFromDsn(dsn string, logger *zap.Logger) (*gorm.DB, error) { database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ Logger: gormzap.New(logger), }) - if err != nil { return nil, err } @@ -79,18 +92,9 @@ func initializeDBFromDsn(dsn string, logger *zap.Logger) (*gorm.DB, error) { return database, nil } -// Migrate the database using gorm [https://gorm.io/docs/migration.html#Auto-Migration] -func autoMigrate(database *gorm.DB, listOfDatabaseTables []any) error { - err := database.AutoMigrate(listOfDatabaseTables...) - if err != nil { - return err - } - return nil -} - -// Run the automigration +// Run the auto-migration func AutoMigrate(logger *zap.Logger, database *gorm.DB) error { - err := autoMigrate(database, models.ListOfTables) + err := database.AutoMigrate(models.ListOfTables...) if err != nil { return err } From f523952bc94f95e7942db0dad009cf0e3b99b47b Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 27 Jul 2023 16:03:36 +0200 Subject: [PATCH 20/90] update docs and developers utils --- .dockerignore | 21 ++-- .github/ISSUE_TEMPLATE/bug_report.md | 15 ++- .github/ISSUE_TEMPLATE/user_story.md | 3 +- .github/pull_request_template.md | 4 +- .github/workflows/CI.yml | 53 +++++---- .gitignore | 9 +- CONTRIBUTING.md | 73 +++++------- Dockerfile | 13 --- Makefile | 16 +++ README.md | 162 ++++++++++++++++++++------- badaas.example.yml | 9 +- changelog.md | 24 ++-- configuration.md | 17 ++- go.mod | 18 +-- go.sum | 39 +++---- go.work.sum | 86 +------------- sonar-project.properties | 4 +- 17 files changed, 276 insertions(+), 290 deletions(-) delete mode 100644 Dockerfile create mode 100644 Makefile diff --git a/.dockerignore b/.dockerignore index 65959251..3eb84618 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,21 +1,24 @@ .editorconfig .git +.gitignore .github sonar-project.properties -AUTHORS.md -CONTRIBUTING.md +*.md LICENSE Makefile NOTICE -README.md arm/ powerpc/ mips/ .golangci.yml -_temp .vscode -node1 -node2 -node3 -.gitignore -changelog.md +go.work +go.work.sum +tools/ +test_e2e/ +!test_e2e/test_api.go +!test_e2e/go.mod +!test_e2e/go.sum +mocks/ +docker/ +**/*_test.go \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 31914924..9cc652d2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -5,30 +5,33 @@ title: '' labels: Bug --- -**Describe the bug** +## Describe the bug A clear and concise description of what the bug is. -**To Reproduce** +## To Reproduce Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error -**Expected behavior** +## Expected behavior A clear and concise description of what you expected to happen. -**Screenshots** +## Screenshots If applicable, add screenshots to help explain your problem. -**Application (please complete the following information):** +## Application + +Please complete the following information: - badaas version [X.X.X] or commit hash -**Additional context** +## Additional context Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/user_story.md b/.github/ISSUE_TEMPLATE/user_story.md index ec61755b..1112fab2 100644 --- a/.github/ISSUE_TEMPLATE/user_story.md +++ b/.github/ISSUE_TEMPLATE/user_story.md @@ -26,7 +26,7 @@ labels: User Story, To be verify `[Put all others constraints here, like list of acceptances values or other]` -## Resources: +## Resources `[Put all your resources here, like mockups, diagrams or other here]` @@ -37,4 +37,3 @@ labels: User Story, To be verify ## Links `[Only use by the team, to link this feature with epic, technical tasks or bugs]` - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c2d7558b..210601a1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,5 @@ :information_source: Don't forget to modify the changelog.md before merging this branch. :information_source: Don't forget to modify config files: -- `badaas.example.yml`: the example file. -- `/scripts/e2e/api/ci-conf.yml`: otherwise you will probably break the CI. (*For local testing please use [act](https://github.com/nektos/act)*). \ No newline at end of file + +- `badaas.example.yml`: the example file. diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e382df70..315d232a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,6 +5,7 @@ on: - main pull_request: types: [opened, synchronize, reopened] + jobs: branch-naming-rules: name: Check branch name @@ -18,8 +19,8 @@ jobs: min_length: 5 max_length: 50 - unit-tests: - name: Unit tests + check-style: + name: Code style needs: [branch-naming-rules] runs-on: ubuntu-latest steps: @@ -30,15 +31,16 @@ jobs: with: go-version: '^1.18' cache: true - - name: Run test - run: go test $(go list ./... | sed 1d) -coverprofile=coverage.out -v - - uses: actions/upload-artifact@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 with: - name: coverage - path: coverage.out + version: v1.52.2 + skip-cache: true + skip-pkg-cache: true + skip-build-cache: true - check-style: - name: Code style + unit-tests: + name: Unit tests needs: [branch-naming-rules] runs-on: ubuntu-latest steps: @@ -49,14 +51,12 @@ jobs: with: go-version: '^1.18' cache: true - - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + - name: Run unit tests + run: go test ./... -coverprofile=coverage_unit.out -v + - uses: actions/upload-artifact@v3 with: - version: latest - skip-cache: true - skip-pkg-cache: true - skip-build-cache: true + name: coverage_unit + path: coverage_unit.out e2e-tests: name: E2E Tests @@ -71,25 +71,25 @@ jobs: go-version: '^1.18' cache: true - name: Start containers - run: docker compose -f "scripts/e2e/docker-compose.yml" up -d --build + run: docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" up -d --build - name: Wait for API server to be up uses: mydea/action-wait-for-api@v1 with: url: "http://localhost:8000/info" - timeout: 20 + timeout: 60 - name: Run test - run: go test -v + run: go test ./test_e2e -v - name: Get logs if: always() - run: docker compose -f "scripts/e2e/docker-compose.yml" logs --no-color 2>&1 | tee app.log & + run: docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" logs --no-color 2>&1 | tee app.log & - name: Stop containers if: always() - run: docker compose -f "scripts/e2e/docker-compose.yml" down + run: docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" down - uses: actions/upload-artifact@v3 with: name: docker-compose-e2e-logs path: app.log - + sonarcloud: name: SonarCloud needs: [unit-tests, check-style] @@ -98,14 +98,13 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Download line coverage report + - name: Download unit tests line coverage report uses: actions/download-artifact@v3 with: - name: coverage - path: coverage.out + name: coverage_unit + path: coverage_unit.out - name: SonarCloud Scan uses: sonarsource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 25cd84cf..e333f3ae 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ *.out # Dependency directories (remove the comment below to include it) -# vendor/ +vendor/ # Go workspace file # go.work @@ -23,11 +23,8 @@ # cockroach files node* -#Vscode conf +# vscode conf .vscode # binary output -badaas - -# temporary directories -_temp \ No newline at end of file +badaas \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de552170..201ba296 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,12 +14,12 @@ ### Unit tests -We use the standard test suite in combination with [github.com/stretchr/testify](https://github.com/stretchr/testify) to do our unit testing. Mocks are generated using [mockery](https://github.com/vektra/mockery) a mock generator using this command `mockery --all --keeptree`. +We use the standard test suite in combination with [github.com/stretchr/testify](https://github.com/stretchr/testify) to do our unit testing. Mocks are generated using [mockery](https://github.com/vektra/mockery) a mock generator using this command `make test_generate_mocks`. To run them, please run: ```sh -go test $(go list ./... | sed 1d) -v +make test_unit ``` ### Feature tests (of end to end tests) @@ -29,57 +29,47 @@ We use docker to run a Badaas instance in combination with one node of Cockroach Run: ```sh -docker compose -f "scripts/e2e/docker-compose.yml" up -d --build +make test_e2e ``` -Then in an another shell: - -```sh -go test -v -``` - -The feature files can be found in the `feature` folder. +The feature files can be found in the `test_e2e/features` folder. ## Logger -We use ubber's [zap](https://pkg.go.dev/go.uber.org/zap) to log stuff, please take `zap.Logger` as an argument for your services constructors. [fx](https://github.com/uber-go/fx) will provide your service with an instance. +We use uber's [zap](https://pkg.go.dev/go.uber.org/zap) to log stuff, please take `zap.Logger` as an argument for your services constructors. [fx](https://github.com/uber-go/fx) will provide your service with an instance. ## Directory structure This is the directory structure we use for the project: -- `commands/` *(Go code)*: Contains all the CLI commands. This package relies heavily on github.com/ditrit/verdeter. -- `configuration/` *(Go code)*: Contains all the configuration holders. Please only use the interfaces, they are all mocked for easy testing +- `configuration/` *(Go code)*: Contains all the configuration keys and holders. Please only use the interfaces, they are all mocked for easy testing. - `controllers/` *(Go code)*: Contains all the http controllers, they handle http requests and consume services. -- `docs/`: Contains the documentation. -- `features/`: Contains all the feature tests (or end to end tests). +- `docker/` : Contains the docker, docker-compose file and configuration files for different environments. + - `test_db/` : Contains the Dockerfile to build a development/test version of CockroachDB. + - `test_api/` : Contains files to build a development/test version of the api. +- `test_e2e/`: Contains all the feature and steps for e2e tests. - `logger/` *(Go code)*: Contains the logger creation logic. Please don't call it from your own services and code, use the dependency injection system. -- `persistance/` *(Go code)*: - - `/gormdatabase/` *(Go code)*: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. - - `/models/` *(Go code)*: Contains the models. (For a structure to me considered a valid model, it has to embed `models.BaseModel` and satisfy the `models.Tabler` interface. This interface returns the name of the sql table.) - - `/dto/` *(Go code)*: Contains the Data Transfert Objects. They are used mainly to decode json payloads. - - `/pagination/` *(Go code)*: Contains the pagination logic. - - `/repository/` *(Go code)*: Contains the repository interface and implementation. Use uint as ID when using gorm models. -- `resources/` *(Go code)*: Contains the resources shared with the rest of the codebase (ex: API version). +- `persistance/` *(Go code)*: + - `gormdatabase/` *(Go code)*: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. + - `models/` *(Go code)*: Contains the models. (For a structure to me considered a valid model, it has to embed `models.BaseModel` and satisfy the `models.Tabler` interface. This interface returns the name of the sql table.). + - `dto/` *(Go code)*: Contains the Data Transfer Objects. They are used mainly to decode json payloads. + - `pagination/` *(Go code)*: Contains the pagination logic. + - `repository/` *(Go code)*: Contains the repository interfaces and implementations to manage queries to the database. - `router/` *(Go code)*: Contains http router of badaas. - - `/middlewares/` *(Go code)*: Contains the various http middlewares that we use. -- `scripts/e2e/` : Contains the docker-compose file for end-to-end test. - - `/api/` : Contains the Dockerfile to build badaas with a dedicated config file. - - `/db/` : Contains the Dockerfile to build a developpement version of CockroachDB. -- `services/` *(Go code)*: Contains the Dockerfile to build a developpement version of CockroachDB. - - `/auth/protocols/`: Contains the implementations of authentication clients for differents protocols. - - `/basicauth/` *(Go code)*: Handle the authentication using email/password. - - `/oidc/` *(Go code)*: Handle the authentication via Open-ID Connect. - - `/sessionservice/` *(Go code)*: Handle sessions and their lifecycle. - - `/userservice/` *(Go code)*: Handle users. - - `validators/` *(Go code)*: Contains validators such as an email validator. + - `middlewares/` *(Go code)*: Contains the various http middlewares that we use. +- `services/` *(Go code)*: Contains services. + - `auth/protocols/`: Contains the implementations of authentication clients for different protocols. + - `basicauth/` *(Go code)*: Handle the authentication using email/password. + - `sessionservice/` *(Go code)*: Handle sessions and their lifecycle. + - `userservice/` *(Go code)*: Handle users. +- `utils/` *(Go code)*: Contains utility functions that can be used all around the project. +- `validators/` *(Go code)*: Contains validators such as an email validator. At the root of the project, you will find: - The README. - The changelog. -- The files for the E2E test http support. -- The LICENCE file. +- The LICENSE file. ## Git @@ -87,17 +77,16 @@ At the root of the project, you will find: `[BRANCH_TYPE]/[BRANCH_NAME]` -- `BRANCH_TYPE` is a prefix to describe the purpose of the branch. - +- `BRANCH_TYPE` is a prefix to describe the purpose of the branch. Accepted prefixes are: - `feature`, used for feature development - `bugfix`, used for bug fix - - `improvement`, used for refacto + - `improvement`, used for refactor - `library`, used for updating library - `prerelease`, used for preparing the branch for the release - `release`, used for releasing project - `hotfix`, used for applying a hotfix on main - - `poc`, used for proof of concept + - `poc`, used for proof of concept - `BRANCH_NAME` is managed by this regex: `[a-z0-9._-]` (`_` is used as space character). ### Default branch @@ -113,9 +102,9 @@ We use [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as guideline f Steps to release: - Create a new branch labeled `release/vX.Y.Z` from the latest `main`. -- Improve the version number in `changelog.md` and `resources/api.go`. +- Improve the version number in `changelog.md`. - Verify the content of the `changelog.md`. - Commit the modifications with the label `Release version X.Y.Z`. - Create a pull request on github for this branch into `main`. -- Once the pull request validated and merged, tag the `main` branch with `vX.Y.Z` -- After the tag is pushed, make the release on the tag in GitHub +- Once the pull request validated and merged, tag the `main` branch with `vX.Y.Z`. +- After the tag is pushed, make the release on the tag in GitHub. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 96dc2afe..00000000 --- a/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -# builder image -FROM golang:1.19-alpine as builder -WORKDIR /app -COPY . . -RUN apk add build-base -RUN CGO_ENABLED=1 go build -a -o badaas . - - -# final image for end users -FROM alpine:3.16.2 -COPY --from=builder /app/badaas . -EXPOSE 8000 -ENTRYPOINT [ "./badaas" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..f53517d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +lint: + golangci-lint run + +test_unit: + go test ./... -v + +test_e2e: + docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" up -d + ./docker/wait_for_api.sh 8000/info + go test ./test_e2e -v + +test_generate_mocks: + mockery --all --keeptree + +.PHONY: test_unit test_e2e + diff --git a/README.md b/README.md index ce6d3c8f..9346cfd2 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,156 @@ # BADAAS: Backend And Distribution As A Service -Badaas enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). +Badaas enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). Badaas provides several key features: - **Authentication**: Badaas can authenticate users using its internal authentication scheme or externally by using protocols such as OIDC, SAML, Oauth2... -- **Habilitation**: On a resource access, Badaas will check if the user is authorized using a RBAC model. +- **Authorization**: On resource access, Badaas will check if the user is authorized using a RBAC model. - **Distribution**: Badaas is built to run in clusters by default. Communications between nodes are TLS encrypted using [shoset](https://github.com/ditrit/shoset). -- **Persistence**: Applicative objects are persisted as well as user files. Those resources are shared accross the clusters to increase resiliency. +- **Persistence**: Applicative objects are persisted as well as user files. Those resources are shared across the clusters to increase resiliency. - **Querying Resources**: Resources are accessible via a REST API. -- **Posix complient**: Badaas strives towards being a good unix citizen and respecting commonly accepted norms. (see [Configuration](#configuration)) -- **Advanced logs management**: Badaas provides an interface to interact with the logs produced by the clusters. Logs are formated in json by default. - -To quickly get badaas up and running, please head to the [miniblog tutorial]() +- **Posix compliant**: Badaas strives towards being a good unix citizen and respecting commonly accepted norms. (see [Configuration](#configuration)) +- **Advanced logs management**: Badaas provides an interface to interact with the logs produced by the clusters. Logs are formatted in json by default. - [Quickstart](#quickstart) -- [Docker install](#docker-install) -- [Install from sources](#install-from-sources) - - [Prerequisites](#prerequisites) - - [Configuration](#configuration) + - [Example](#example) + - [Step-by-step instructions](#step-by-step-instructions) +- [Configuration](#configuration) - [Contributing](#contributing) -- [Licence](#licence) +- [License](#license) ## Quickstart -You can either use the [Docker Install](#docker-install) or build it from source . +### Example + +To quickly get badaas up and running, you can head to the [example](https://github.com/ditrit/badaas-example). This example will help you to see how to use badaas and as a template to start your own project -## Docker install +### Step-by-step instructions -You can build the image using `docker build -t badaas .` since we don't have an official docker image yet. +Once you have started your project with `go init`, you must add the dependency to badaas: -## Install from sources +```bash +go get -u github.com/ditrit/badaas +``` -### Prerequisites +Then, you can use the following structure to configure and start your application + +```go +func main() { + badaas.BaDaaS.AddModules( + // add badaas modules + ).Provide( + // provide constructors + ).Invoke( + // invoke functions + ).Start() +} +``` -Get the sources of the project, either by visiting the [releases](https://github.com/ditrit/badaas/releases) page and downloading an archive or clone the main branch (please be aware that is it not a stable version). +#### Config badaas functionalities -To build the project: +You are free to choose which badaas functionalities you wish to use. To add them, you must add the corresponding module, for example: -- [Install go](https://go.dev/dl/#go1.18.4) v1.18 -- Install project dependencies +```go +func main() { + badaas.BaDaaS.AddModules( + controllers.InfoControllerModule, + controllers.AuthControllerModule, + ).Provide( + NewAPIVersion, + ).Start() +} -```bash -go get +func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") +} ``` -- Run build command +#### Add your own functionalities + +With the "Provide" and "Invoke" functions you will be able to add your own functionalities to the application. For example, to add a route you must first provide the constructor of the controller and then invoke the function that adds this route: + +```go +func main() { + badaas.BaDaaS.Provide( + NewHelloController, + ).Invoke( + AddExampleRoutes, + ).Start() +} + +type HelloController interface { + SayHello(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) +} + +type helloControllerImpl struct{} + +func NewHelloController() HelloController { + return &helloControllerImpl{} +} + +func (*helloControllerImpl) SayHello(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + return "hello world", nil +} + +func AddExampleRoutes( + router *mux.Router, + jsonController middlewares.JSONController, + helloController HelloController, +) { + router.HandleFunc( + "/hello", + jsonController.Wrap(helloController.SayHello), + ).Methods("GET") +} +``` -```bash -go build . +#### Run it + +Once you have defined the functionalities of your project (the `/hello` route in this case), you can run the application using the steps described in the example README.md + +### Provided functionalities + +#### InfoControllerModule + +`InfoControllerModule` adds the path `/info`, where the api version will be answered. To set the version you want to be responded you must provide a function that returns it: + +```go +func main() { + badaas.BaDaaS.AddModules( + controllers.InfoControllerModule, + ).Provide( + NewAPIVersion, + ).Start() +} + +func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") +} ``` -Well done, you have a binary `badaas` at the root of the project. +#### AuthControllerModule -Then you can launch Badaas directly with: +`AuthControllerModule` adds `/login` and `/logout`, which allow us to add authentication to our application in a simple way: -```bash -export BADAAS_DATABASE_PORT= -export BADAAS_DATABASE_HOST= -export BADAAS_DATABASE_DBNAME= -export BADAAS_DATABASE_SSLMODE= -export BADAAS_DATABASE_USERNAME= -export BADAAS_DATABASE_PASSWORD= -./badaas +```go +func main() { + badaas.BaDaaS.AddModules( + controllers.AuthControllerModule, + ).Start() +} ``` ### Configuration -Badaas use [verdeter](https://github.com/ditrit/verdeter) to manage it's configuration. So Badaas is POSIX complient by default. +Badaas use [verdeter](https://github.com/ditrit/verdeter) to manage it's configuration, so Badaas is POSIX compliant by default. + +Badgen automatically generates a default configuration in `badaas/config/badaas.yml`, but you are free to modify it if you need to. -Badaas can be configured using environment variables, configuration files or CLI flags. +This can be done using environment variables, configuration files or CLI flags. CLI flags take priority on the environment variables and the environment variables take priority on the content of the configuration file. -As an exemple we will define the `database.port` configuration key using the 3 methods: +As an example we will define the `database.port` configuration key using the 3 methods: - Using a CLI flag: `--database.port=1222` - Using an environment variable: `export BADAAS_DATABASE_PORT=1222` (*dots are replaced by underscores*) @@ -81,7 +159,7 @@ As an exemple we will define the `database.port` configuration key using the 3 m ```yml # /etc/badaas/badaas.yml database: - port: 1222 + port: 1222 ``` The config file can be placed at `/etc/badaas/badaas.yml` or `$HOME/.config/badaas/badaas.yml` or in the same folder as the badaas binary `./badaas.yml`. @@ -94,6 +172,6 @@ If needed, the location can be overridden using the config key `config_path`. See [this section](./CONTRIBUTING.md). -## Licence +## License -Badaas is Licenced under the [Mozilla Public License Version 2.0](./LICENSE). +Badaas is Licensed under the [Mozilla Public License Version 2.0](./LICENSE). diff --git a/badaas.example.yml b/badaas.example.yml index c00e461c..1c80e94a 100644 --- a/badaas.example.yml +++ b/badaas.example.yml @@ -37,7 +37,7 @@ database: server: # The address to bind badaas to. # default ("0.0.0.0") - host: "" + host: "" # The port badaas should use. # default (8000) @@ -45,7 +45,7 @@ server: # The maximum timeout for the http server in seconds. # default (15) - timeout: 15 + timeout: 15 # The settings for the pagination. pagination: @@ -64,7 +64,7 @@ logger: template: "Receive {{method}} request on {{url}}" # The settings for session service -# This section contains some good defaults, don't change thoses value unless you need to. +# This section contains some good defaults, don't change those value unless you need to. session: # The duration of a user session, in seconds # Default (14400) equal to 4 hours @@ -81,5 +81,4 @@ default: # The admin settings for the first run admin: # The admin password for the first run. Won't change is the admin user already exists. - password: admin - \ No newline at end of file + password: admin \ No newline at end of file diff --git a/changelog.md b/changelog.md index e62f794b..63b1dbe5 100644 --- a/changelog.md +++ b/changelog.md @@ -9,27 +9,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Setup project (ci and sonar) +- Setup project (ci and sonar). - Setup e2e test solution (cucumber + docker). - Setup Docker based build system. -- Add default api endpoint `info` -- Setup command based pattern using verdeter -- Add an http error handling mecanism -- Add a json controller -- Add a dto package +- Add default api endpoint `info`. +- Setup command based pattern using verdeter. +- Add an http error handling mechanism. +- Add a json controller. +- Add a dto package. - The tasks in the CI are ran in parallel. -- Add a Generic CRUD Repository +- Add a Generic CRUD Repository. - Add a configuration structure containing all the configuration holder. - Refactor codebase to use the DI framework uber-go/fx. Now all services and controllers relies on interfaces. -- Add an generic ID to the repository interface -- Add a retry mecanism for the database connection +- Add an generic ID to the repository interface. +- Add a retry mechanism for the database connection. - Add `init` flag to migrate database and create admin user. - Add a CONTRIBUTING.md and a documentation file for configuration (configuration.md) - Add a session services. - Add a basic authentication controller. - Now config keys are only declared once with constants in the `configuration/` package. - Add a dto that is returned on a successful login. -- Update verdeter to version v0.4.0 +- Update verdeter to version v0.4.0. +- Transform BadAas into a library. - -[unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased \ No newline at end of file +[unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased diff --git a/configuration.md b/configuration.md index cd2e9486..20b7652a 100644 --- a/configuration.md +++ b/configuration.md @@ -1,15 +1,15 @@ # Configuration -To see a complete example of a working config file: head to [`badaas.example.yml`](./badaas.example.yml) +To see a complete example of a working config file: head to [`badaas.example.yml`](./badaas.example.yml). As said in the README: > Badaas can be configured using environment variables, CLI flags or a configuration file. > CLI flags take priority on the environment variables and the environment variables take priority on the content of the configuration file. -In this documentation file, we will mainly focus our attention on config files but we won't forget that we can use environement variables and CLI flags to change Badaas' config. +In this documentation file, we will mainly focus our attention on config files but we won't forget that we can use environment variables and CLI flags to change Badaas' config. -The config file can be formated in any syntax that [github.com/spf13/viper](https://github.com/spf13/viper) supports but we will only use YAML syntax in our docs. +The config file can be formatted in any syntax that [`viper`](https://github.com/spf13/viper) supports but we will only use YAML syntax in our docs. - [Configuration](#configuration) - [Database](#database) @@ -59,13 +59,13 @@ database: retryTime: 5 ``` -Please note that the init section `init:` is not mandatory. Badaas is suited with a simple but effective retry mecanism that will retry `database.init.retry` time to establish a connection with the database. Badaas will wait `database.init.retryTime` seconds between each retry. +Please note that the init section `init:` is not mandatory. Badaas is suited with a simple but effective retry mechanism that will retry `database.init.retry` time to establish a connection with the database. Badaas will wait `database.init.retryTime` seconds between each retry. ## Logger -Badaas use a structured logger that can output json logs in production and user adapted logs for debug using the `logger.mode` key. +Badaas use a structured logger that can output json logs in production and user adapted logs for debug using the `logger.mode` key. -Badaas offers the possibility to change the log message of the Middleware Logger but provides a sane default. It is formated using the Jinja syntax. The values available are `method`, `url` and `protocol`. +Badaas offers the possibility to change the log message of the Middleware Logger but provides a sane default. It is formatted using the Jinja syntax. The values available are `method`, `url` and `protocol`. ```yml # The settings for the logger. @@ -82,7 +82,7 @@ logger: You can change the host Badaas will bind to, the port and the timeout in seconds. -Additionaly you can change the number of elements returned by default for a paginated response. +Additionally you can change the number of elements returned by default for a paginated response. ```yml # The settings for the http server. @@ -127,7 +127,6 @@ Session are extended if the user made a request to badaas in the "roll duration" Please see the diagram below to see what is the roll duration relative to the session duration. - ```txt | session duration | |<----------------------------------------->| @@ -139,7 +138,7 @@ Please see the diagram below to see what is the roll duration relative to the se ```yml # The settings for session service -# This section contains some good defaults, don't change thoses value unless you need to. +# This section contains some good defaults, don't change those value unless you need to. session: # The duration of a user session, in seconds # Default (14400) equal to 4 hours diff --git a/go.mod b/go.mod index fe0924be..b63125a8 100644 --- a/go.mod +++ b/go.mod @@ -8,17 +8,17 @@ require ( github.com/google/uuid v1.3.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 - github.com/jackc/pgconn v1.14.1 + github.com/jackc/pgconn v1.14.0 github.com/magiconair/properties v1.8.7 github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 - go.uber.org/fx v1.20.0 + go.uber.org/fx v1.19.3 go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.11.0 + golang.org/x/crypto v0.9.0 gorm.io/driver/postgres v1.5.2 - gorm.io/gorm v1.25.2 + gorm.io/gorm v1.25.1 ) require github.com/felixge/httpsnoop v1.0.1 // indirect @@ -35,17 +35,17 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.4.2 // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.9 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -55,8 +55,8 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 68dd6625..df9b03a0 100644 --- a/go.sum +++ b/go.sum @@ -170,8 +170,8 @@ github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= -github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -198,8 +198,8 @@ github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrU github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v5 v5.4.2 h1:u1gmGDwbdRUZiwisBm/Ky2M14uQyUP65bG8+20nnyrg= -github.com/jackc/pgx/v5 v5.4.2/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -246,8 +246,8 @@ github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61/go.mod h1:WboHq+ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= -github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -269,8 +269,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= @@ -297,6 +297,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= @@ -321,8 +322,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= -go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= -go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= +go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= +go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -346,8 +347,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -485,13 +486,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -501,8 +502,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -666,8 +667,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= -gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= -gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go.work.sum b/go.work.sum index fcf390bc..f27a97f4 100644 --- a/go.work.sum +++ b/go.work.sum @@ -131,7 +131,6 @@ cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27 cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -160,11 +159,7 @@ github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCw github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -188,8 +183,6 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5 github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -201,30 +194,13 @@ github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR3 github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= -github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= -github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= -github.com/jackc/pgx/v5 v5.4.2 h1:u1gmGDwbdRUZiwisBm/Ky2M14uQyUP65bG8+20nnyrg= -github.com/jackc/pgx/v5 v5.4.2/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY= -github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -238,9 +214,6 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= -github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -251,30 +224,11 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sagikazarmark/crypt v0.10.0/go.mod h1:gwTNHQVoOS3xp9Xvz5LLR+1AauC5M6880z5NWzdhOyQ= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s= @@ -282,29 +236,14 @@ go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQa go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= -go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= -go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= -go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= +go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -312,9 +251,7 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= @@ -324,7 +261,6 @@ golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -334,34 +270,19 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= @@ -399,9 +320,4 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= -gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= -gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/sonar-project.properties b/sonar-project.properties index f0158b76..9b068f33 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,5 +5,5 @@ sonar.host.url=https://sonarcloud.io sonar.sources=. sonar.exclusions=**/*_test.go,mocks/***,vendor/*** sonar.tests=. -sonar.test.inclusions=**/*_test.go -sonar.go.coverage.reportPaths=./coverage.out/coverage.out +sonar.test.inclusions=**/*_test.go,test_e2e/*** +sonar.go.coverage.reportPaths=coverage_unit.out/coverage_unit.out From 227c9c282551352f14cde338f5c2450df4b5d9a1 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 31 Jul 2023 15:44:51 +0200 Subject: [PATCH 21/90] remove unnecesary init.sh for cockroach and add health check --- docker/test_api/docker-compose.yml | 3 ++- docker/test_db/docker-compose.yml | 7 ++++++- docker/test_db/init.sh | 32 ------------------------------ docker/wait_for_db.sh | 6 ++++++ 4 files changed, 14 insertions(+), 34 deletions(-) delete mode 100755 docker/test_db/init.sh create mode 100755 docker/wait_for_db.sh diff --git a/docker/test_api/docker-compose.yml b/docker/test_api/docker-compose.yml index 2a6e0173..6796447c 100644 --- a/docker/test_api/docker-compose.yml +++ b/docker/test_api/docker-compose.yml @@ -15,4 +15,5 @@ services: - "8000:8000" restart: always depends_on: - - db + db: + condition: service_healthy diff --git a/docker/test_db/docker-compose.yml b/docker/test_db/docker-compose.yml index b937793b..2a071c16 100644 --- a/docker/test_db/docker-compose.yml +++ b/docker/test_db/docker-compose.yml @@ -8,10 +8,15 @@ services: volumes: - .:/cockroach/files working_dir: /cockroach - entrypoint: ./files/init.sh + entrypoint: /cockroach/cockroach.sh start-single-node --insecure --log-config-file=files/logs.yaml ports: - "26257:26257" - "8080:8080" # Web based dashboard environment: - COCKROACH_USER=root - COCKROACH_DATABASE=badaas_db + healthcheck: + test: curl --fail http://localhost:8080 || exit 1 + interval: 10s + timeout: 5s + retries: 5 diff --git a/docker/test_db/init.sh b/docker/test_db/init.sh deleted file mode 100755 index 3f65962b..00000000 --- a/docker/test_db/init.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -set -e - -echo "******************************* Listing Env Variables..." -printenv -echo "******************************* Starting single cockroach node..." - -./cockroach start-single-node --insecure --log-config-file=files/logs.yaml --background - -echo "******************************* Init database" -echo "******************************* |=> Creating init.sql" - -cat > init.sql < Applying init.sql" - -./cockroach sql --insecure --file init.sql - -echo "******************************* To the moon" - -# tail logs to make them accesible with docker logs -tail -f cockroach-data/logs/cockroach.log \ No newline at end of file diff --git a/docker/wait_for_db.sh b/docker/wait_for_db.sh new file mode 100755 index 00000000..04263a18 --- /dev/null +++ b/docker/wait_for_db.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +until [ "`docker inspect -f {{.State.Health.Status}} badaas-test-db`"=="healthy" ]; do + printf '.'; + sleep 1; +done; \ No newline at end of file From e51325ebfad01f8efbc436bff8d9307d0a4ffd2c Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 31 Jul 2023 15:56:47 +0200 Subject: [PATCH 22/90] move db connection to orm module and make automigration possible by fx --- orm/ModuleFx.go | 21 +++++++ orm/db.go | 54 ++++++++++++++++ orm/db_test.go | 22 +++++++ orm/orm.go | 15 +++++ persistence/ModuleFx.go | 6 +- persistence/gormdatabase/db.go | 96 +++++------------------------ persistence/gormdatabase/db_test.go | 35 ----------- test_e2e/badaas_e2e_test.go | 12 +++- 8 files changed, 140 insertions(+), 121 deletions(-) create mode 100644 orm/ModuleFx.go create mode 100644 orm/db.go create mode 100644 orm/db_test.go create mode 100644 orm/orm.go delete mode 100644 persistence/gormdatabase/db_test.go diff --git a/orm/ModuleFx.go b/orm/ModuleFx.go new file mode 100644 index 00000000..3f7cebaf --- /dev/null +++ b/orm/ModuleFx.go @@ -0,0 +1,21 @@ +package orm + +import ( + "go.uber.org/fx" +) + +type GetModelsResult struct { + fx.Out + + Models []any `group:"modelsTables"` +} + +var AutoMigrate = fx.Module( + "AutoMigrate", + fx.Invoke( + fx.Annotate( + autoMigrate, + fx.ParamTags(`group:"modelsTables"`), + ), + ), +) diff --git a/orm/db.go b/orm/db.go new file mode 100644 index 00000000..9b05a115 --- /dev/null +++ b/orm/db.go @@ -0,0 +1,54 @@ +package orm + +import ( + "fmt" + "time" + + "go.uber.org/zap" + "gorm.io/driver/postgres" + "gorm.io/gorm" + + "github.com/ditrit/badaas/persistence/gormdatabase/gormzap" +) + +func CreateDialector(host, username, password, sslmode, dbname string, port int) gorm.Dialector { + return postgres.Open(CreateDSN( + host, username, password, sslmode, dbname, port, + )) +} + +func CreateDSN(host, username, password, sslmode, dbname string, port int) string { + return fmt.Sprintf( + "user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", + username, password, host, port, sslmode, dbname, + ) +} + +func ConnectToDialector( + logger *zap.Logger, + dialector gorm.Dialector, + retryAmount uint, + retryTime time.Duration, +) (*gorm.DB, error) { + var err error + var database *gorm.DB + for numberRetry := uint(0); numberRetry < retryAmount; numberRetry++ { + database, err = gorm.Open(dialector, &gorm.Config{ + Logger: gormzap.New(logger), + }) + + if err == nil { + logger.Sugar().Debugf("Database connection is active") + return database, nil + } + + logger.Sugar().Debugf("Database connection failed with error %q", err.Error()) + logger.Sugar().Debugf( + "Retrying database connection %d/%d in %s", + numberRetry+1, retryAmount, retryTime.String(), + ) + time.Sleep(retryTime) + } + + return nil, err +} diff --git a/orm/db_test.go b/orm/db_test.go new file mode 100644 index 00000000..58b750ca --- /dev/null +++ b/orm/db_test.go @@ -0,0 +1,22 @@ +package orm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateDSN(t *testing.T) { + assert.Equal( + t, + "user=username password=password host=192.168.2.5 port=1225 sslmode=disable dbname=badaas_db", + CreateDSN( + "192.168.2.5", + "username", + "password", + "disable", + "badaas_db", + 1225, + ), + ) +} diff --git a/orm/orm.go b/orm/orm.go new file mode 100644 index 00000000..ef137bda --- /dev/null +++ b/orm/orm.go @@ -0,0 +1,15 @@ +package orm + +import ( + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" +) + +func autoMigrate(modelsLists [][]any, db *gorm.DB) error { + if len(modelsLists) > 0 { + allModels := pie.Flat(modelsLists) + return db.AutoMigrate(allModels...) + } + + return nil +} diff --git a/persistence/ModuleFx.go b/persistence/ModuleFx.go index 708030fd..2a2a5706 100644 --- a/persistence/ModuleFx.go +++ b/persistence/ModuleFx.go @@ -5,6 +5,7 @@ import ( "go.uber.org/fx" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/gormdatabase" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/repository" @@ -15,13 +16,14 @@ import ( // Provides: // // - The database connection -// +// - badaas-orm auto-migration // - The repositories var PersistanceModule = fx.Module( "persistence", // Database connection fx.Provide(gormdatabase.SetupDatabaseConnection), - + // auto-migrate + orm.AutoMigrate, // repositories fx.Provide(repository.NewCRUDRepository[models.Session, uuid.UUID]), fx.Provide(repository.NewCRUDRepository[models.User, uuid.UUID]), diff --git a/persistence/gormdatabase/db.go b/persistence/gormdatabase/db.go index e62e1c81..26776e52 100644 --- a/persistence/gormdatabase/db.go +++ b/persistence/gormdatabase/db.go @@ -1,21 +1,16 @@ package gormdatabase import ( - "fmt" - "time" - "go.uber.org/zap" - "gorm.io/driver/postgres" "gorm.io/gorm" "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/persistence/gormdatabase/gormzap" - "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/orm" ) // Create the dsn string from the configuration -func createDsnFromConf(databaseConfiguration configuration.DatabaseConfiguration) string { - dsn := createDsn( +func createDialectorFromConf(databaseConfiguration configuration.DatabaseConfiguration) gorm.Dialector { + return orm.CreateDialector( databaseConfiguration.GetHost(), databaseConfiguration.GetUsername(), databaseConfiguration.GetPassword(), @@ -23,80 +18,19 @@ func createDsnFromConf(databaseConfiguration configuration.DatabaseConfiguration databaseConfiguration.GetDBName(), databaseConfiguration.GetPort(), ) - return dsn } -// Create the dsn strings with the provided args -func createDsn(host, username, password, sslmode, dbname string, port int) string { - return fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", - username, password, host, port, sslmode, dbname, +// Creates the database object with using the database configuration and exec the setup +func SetupDatabaseConnection( + logger *zap.Logger, + databaseConfiguration configuration.DatabaseConfiguration, +) (*gorm.DB, error) { + dialector := createDialectorFromConf(databaseConfiguration) + + return orm.ConnectToDialector( + logger, + dialector, + databaseConfiguration.GetRetry(), + databaseConfiguration.GetRetryTime(), ) } - -// Creates the database object with using the database configuration -// and then executes the auto-migration -func SetupDatabaseConnection(logger *zap.Logger, databaseConfiguration configuration.DatabaseConfiguration) (*gorm.DB, error) { - db, err := CreateDatabaseConnectionFromConfiguration(logger, databaseConfiguration) - if err != nil { - return nil, err - } - - err = AutoMigrate(logger, db) - if err != nil { - logger.Error("migration failed") - return nil, err - } - logger.Info("AutoMigration was executed successfully") - - return db, nil -} - -// Creates the database object with using the database configuration -func CreateDatabaseConnectionFromConfiguration(logger *zap.Logger, databaseConfiguration configuration.DatabaseConfiguration) (*gorm.DB, error) { - dsn := createDsnFromConf(databaseConfiguration) - var err error - var database *gorm.DB - for numberRetry := uint(0); numberRetry < databaseConfiguration.GetRetry(); numberRetry++ { - database, err = initializeDBFromDsn(dsn, logger) - if err == nil { - logger.Sugar().Debugf("Database connection is active") - return database, err - } - logger.Sugar().Debugf("Database connection failed with error %q", err.Error()) - logger.Sugar().Debugf("Retrying database connection %d/%d in %s", - numberRetry+1, databaseConfiguration.GetRetry(), databaseConfiguration.GetRetryTime().String()) - time.Sleep(databaseConfiguration.GetRetryTime()) - } - - return nil, err -} - -// Initialize the database with the dsn string -func initializeDBFromDsn(dsn string, logger *zap.Logger) (*gorm.DB, error) { - database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ - Logger: gormzap.New(logger), - }) - if err != nil { - return nil, err - } - - rawDatabase, err := database.DB() - if err != nil { - return nil, err - } - // ping the underlying database - err = rawDatabase.Ping() - if err != nil { - return nil, err - } - return database, nil -} - -// Run the auto-migration -func AutoMigrate(logger *zap.Logger, database *gorm.DB) error { - err := database.AutoMigrate(models.ListOfTables...) - if err != nil { - return err - } - return nil -} diff --git a/persistence/gormdatabase/db_test.go b/persistence/gormdatabase/db_test.go deleted file mode 100644 index 05eef996..00000000 --- a/persistence/gormdatabase/db_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package gormdatabase - -import ( - "testing" - - configurationmocks "github.com/ditrit/badaas/mocks/configuration" - "github.com/stretchr/testify/assert" -) - -func TestCreateDsnFromconf(t *testing.T) { - conf := configurationmocks.NewDatabaseConfiguration(t) - conf.On("GetPort").Return(1225) - conf.On("GetHost").Return("192.168.2.5") - conf.On("GetDBName").Return("badaas_db") - conf.On("GetUsername").Return("username") - conf.On("GetPassword").Return("password") - conf.On("GetSSLMode").Return("disable") - assert.Equal(t, "user=username password=password host=192.168.2.5 port=1225 sslmode=disable dbname=badaas_db", - createDsnFromConf(conf)) -} - -func TestCreateDsn(t *testing.T) { - assert.Equal(t, - "user=username password=password host=192.168.2.5 port=1225 sslmode=disable dbname=badaas_db", - createDsn( - "192.168.2.5", - "username", - "password", - "disable", - "badaas_db", - 1225, - ), - "no dsn should be empty", - ) -} diff --git a/test_e2e/badaas_e2e_test.go b/test_e2e/badaas_e2e_test.go index b31a2e5e..12cd1c4c 100644 --- a/test_e2e/badaas_e2e_test.go +++ b/test_e2e/badaas_e2e_test.go @@ -41,8 +41,10 @@ func TestMain(_ *testing.M) { pflag.Parse() opts.Paths = pflag.Args() - logger, _ := zap.NewDevelopment() - var err error + logger, err := zap.NewDevelopment() + if err != nil { + panic(err) + } viper.Set(configuration.DatabasePortKey, 26257) viper.Set(configuration.DatabaseHostKey, "localhost") @@ -52,7 +54,8 @@ func TestMain(_ *testing.M) { viper.Set(configuration.DatabaseSslmodeKey, "disable") viper.Set(configuration.DatabaseRetryKey, 10) viper.Set(configuration.DatabaseRetryDurationKey, 5) - db, err = gormdatabase.CreateDatabaseConnectionFromConfiguration( + + db, err = gormdatabase.SetupDatabaseConnection( logger, configuration.NewDatabaseConfiguration(), ) @@ -66,6 +69,9 @@ func TestMain(_ *testing.M) { Options: &opts, }.Run() + // let db cleaned + CleanDB(db) + os.Exit(status) } From ff2318e5e561a2aeecf9e0be63d9dd2f12f1d7bb Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 31 Jul 2023 15:36:59 +0200 Subject: [PATCH 23/90] info and auth modules now also provide the corresponding services and middlewares --- README.md | 16 ++++----- badaas.go | 2 -- controllers/ModuleFx.go => modules.go | 33 ++++++++++++++----- ...createSuperUser_test.go => modules_test.go | 2 +- router/ModuleFx.go | 4 +-- {controllers => router}/routes.go | 7 ++-- {controllers => router}/routes_test.go | 10 +++--- services/ModuleFx.go | 19 +++++++++-- test_e2e/test_api.go | 5 ++- 9 files changed, 63 insertions(+), 35 deletions(-) rename controllers/ModuleFx.go => modules.go (57%) rename controllers/createSuperUser_test.go => modules_test.go (99%) rename {controllers => router}/routes.go (83%) rename {controllers => router}/routes_test.go (89%) diff --git a/README.md b/README.md index 9346cfd2..54d7a586 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,8 @@ You are free to choose which badaas functionalities you wish to use. To add them ```go func main() { badaas.BaDaaS.AddModules( - controllers.InfoControllerModule, - controllers.AuthControllerModule, + badaas.InfoModule, + badaas.AuthModule, ).Provide( NewAPIVersion, ).Start() @@ -111,14 +111,14 @@ Once you have defined the functionalities of your project (the `/hello` route in ### Provided functionalities -#### InfoControllerModule +#### InfoModule -`InfoControllerModule` adds the path `/info`, where the api version will be answered. To set the version you want to be responded you must provide a function that returns it: +`InfoModule` adds the path `/info`, where the api version will be answered. To set the version you want to be responded you must provide a function that returns it: ```go func main() { badaas.BaDaaS.AddModules( - controllers.InfoControllerModule, + badaas.InfoModule, ).Provide( NewAPIVersion, ).Start() @@ -129,14 +129,14 @@ func NewAPIVersion() *semver.Version { } ``` -#### AuthControllerModule +#### AuthModule -`AuthControllerModule` adds `/login` and `/logout`, which allow us to add authentication to our application in a simple way: +`AuthModule` adds `/login` and `/logout`, which allow us to add authentication to our application in a simple way: ```go func main() { badaas.BaDaaS.AddModules( - controllers.AuthControllerModule, + badaas.AuthModule, ).Start() } ``` diff --git a/badaas.go b/badaas.go index c3196ac8..af29a837 100644 --- a/badaas.go +++ b/badaas.go @@ -12,7 +12,6 @@ import ( "github.com/ditrit/badaas/logger" "github.com/ditrit/badaas/persistence" "github.com/ditrit/badaas/router" - "github.com/ditrit/badaas/services" "github.com/ditrit/verdeter" ) @@ -69,7 +68,6 @@ func (badaas BaDaaSInitializer) runHTTPServer(cmd *cobra.Command, args []string) router.RouterModule, logger.LoggerModule, persistence.PersistanceModule, - services.ServicesModule, // logger for fx fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { diff --git a/controllers/ModuleFx.go b/modules.go similarity index 57% rename from controllers/ModuleFx.go rename to modules.go index 175cbc81..ce6ec2b0 100644 --- a/controllers/ModuleFx.go +++ b/modules.go @@ -1,4 +1,4 @@ -package controllers +package badaas import ( "strings" @@ -7,19 +7,33 @@ import ( "go.uber.org/zap" "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/controllers" + "github.com/ditrit/badaas/router" + "github.com/ditrit/badaas/router/middlewares" + "github.com/ditrit/badaas/services" "github.com/ditrit/badaas/services/userservice" ) -var InfoControllerModule = fx.Module( - "infoController", - fx.Provide(NewInfoController), - fx.Invoke(AddInfoRoutes), +var InfoModule = fx.Module( + "info", + // controller + fx.Provide(controllers.NewInfoController), + // routes + fx.Invoke(router.AddInfoRoutes), ) -var AuthControllerModule = fx.Module( - "authController", - fx.Provide(NewBasicAuthenticationController), - fx.Invoke(AddAuthRoutes), +var AuthModule = fx.Module( + "auth", + // service + services.AuthServiceModule, + + // controller + fx.Provide(controllers.NewBasicAuthenticationController), + + // routes + fx.Provide(middlewares.NewAuthenticationMiddleware), + fx.Invoke(router.AddAuthRoutes), + fx.Invoke(createSuperUser), ) @@ -38,5 +52,6 @@ func createSuperUser( } logger.Sugar().Infof("The superadmin user already exists in database") } + return nil } diff --git a/controllers/createSuperUser_test.go b/modules_test.go similarity index 99% rename from controllers/createSuperUser_test.go rename to modules_test.go index 6e160b76..05ce5c53 100644 --- a/controllers/createSuperUser_test.go +++ b/modules_test.go @@ -1,4 +1,4 @@ -package controllers +package badaas import ( "errors" diff --git a/router/ModuleFx.go b/router/ModuleFx.go index e5561dc2..cda5eed7 100644 --- a/router/ModuleFx.go +++ b/router/ModuleFx.go @@ -1,8 +1,9 @@ package router import ( - "github.com/ditrit/badaas/router/middlewares" "go.uber.org/fx" + + "github.com/ditrit/badaas/router/middlewares" ) // RouterModule for fx @@ -12,6 +13,5 @@ var RouterModule = fx.Module( // middlewares fx.Provide(middlewares.NewJSONController), fx.Provide(middlewares.NewMiddlewareLogger), - fx.Provide(middlewares.NewAuthenticationMiddleware), fx.Invoke(middlewares.AddLoggerMiddleware), ) diff --git a/controllers/routes.go b/router/routes.go similarity index 83% rename from controllers/routes.go rename to router/routes.go index 0b71536b..68d9fe38 100644 --- a/controllers/routes.go +++ b/router/routes.go @@ -1,15 +1,16 @@ -package controllers +package router import ( "github.com/gorilla/mux" + "github.com/ditrit/badaas/controllers" "github.com/ditrit/badaas/router/middlewares" ) func AddInfoRoutes( router *mux.Router, jsonController middlewares.JSONController, - infoController InformationController, + infoController controllers.InformationController, ) { router.HandleFunc( "/info", @@ -24,7 +25,7 @@ func AddInfoRoutes( func AddAuthRoutes( router *mux.Router, authenticationMiddleware middlewares.AuthenticationMiddleware, - basicAuthenticationController BasicAuthenticationController, + basicAuthenticationController controllers.BasicAuthenticationController, jsonController middlewares.JSONController, ) { router.HandleFunc( diff --git a/controllers/routes_test.go b/router/routes_test.go similarity index 89% rename from controllers/routes_test.go rename to router/routes_test.go index 44840b05..fb8b5161 100644 --- a/controllers/routes_test.go +++ b/router/routes_test.go @@ -1,4 +1,4 @@ -package controllers +package router import ( "net/http" @@ -10,9 +10,9 @@ import ( "github.com/stretchr/testify/mock" "go.uber.org/zap" + "github.com/ditrit/badaas/controllers" mockControllers "github.com/ditrit/badaas/mocks/controllers" mockMiddlewares "github.com/ditrit/badaas/mocks/router/middlewares" - "github.com/ditrit/badaas/router" "github.com/ditrit/badaas/router/middlewares" ) @@ -20,9 +20,9 @@ var logger, _ = zap.NewDevelopment() func TestAddInfoRoutes(t *testing.T) { jsonController := middlewares.NewJSONController(logger) - informationController := NewInfoController(semver.MustParse("1.0.1")) + informationController := controllers.NewInfoController(semver.MustParse("1.0.1")) - router := router.NewRouter() + router := NewRouter() AddInfoRoutes( router, jsonController, @@ -51,7 +51,7 @@ func TestAddLoginRoutes(t *testing.T) { authenticationMiddleware := mockMiddlewares.NewAuthenticationMiddleware(t) - router := router.NewRouter() + router := NewRouter() AddAuthRoutes( router, authenticationMiddleware, diff --git a/services/ModuleFx.go b/services/ModuleFx.go index 8b03a9b6..2ac31c8d 100644 --- a/services/ModuleFx.go +++ b/services/ModuleFx.go @@ -3,12 +3,27 @@ package services import ( "go.uber.org/fx" + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/services/sessionservice" "github.com/ditrit/badaas/services/userservice" ) -var ServicesModule = fx.Module( - "services", +var AuthServiceModule = fx.Module( + "authService", + // models + fx.Provide(getAuthModels), + + // services fx.Provide(userservice.NewUserService), fx.Provide(sessionservice.NewSessionService), ) + +func getAuthModels() orm.GetModelsResult { + return orm.GetModelsResult{ + Models: []any{ + models.Session{}, + models.User{}, + }, + } +} diff --git a/test_e2e/test_api.go b/test_e2e/test_api.go index 8100b731..c8ab617d 100644 --- a/test_e2e/test_api.go +++ b/test_e2e/test_api.go @@ -4,13 +4,12 @@ import ( "github.com/Masterminds/semver/v3" "github.com/ditrit/badaas" - "github.com/ditrit/badaas/controllers" ) func main() { badaas.BaDaaS.AddModules( - controllers.InfoControllerModule, - controllers.AuthControllerModule, + badaas.InfoModule, + badaas.AuthModule, ).Provide( NewAPIVersion, ).Start() From 8ccf49c1c8c3acb56c0d24d748933c469e22f49e Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 31 Jul 2023 16:01:47 +0200 Subject: [PATCH 24/90] use gotestsum to run tests + create tests report + tool to install dependencies --- .github/workflows/CI.yml | 11 ++++++++++- CONTRIBUTING.md | 5 +++++ Makefile | 7 ++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 315d232a..38228fb2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -51,12 +51,21 @@ jobs: with: go-version: '^1.18' cache: true + - name: Install gotestsum + run: go install gotest.tools/gotestsum@latest - name: Run unit tests - run: go test ./... -coverprofile=coverage_unit.out -v + run: gotestsum --junitfile unit-tests.xml ./... -coverpkg=./... -coverprofile=coverage_unit.out - uses: actions/upload-artifact@v3 with: name: coverage_unit path: coverage_unit.out + - name: Test Report + uses: dorny/test-reporter@v1 + if: always() # run this step even if previous steps failed + with: + name: Unit Tests Report # Name of the check run which will be created + path: unit-tests.xml # Path to test results + reporter: java-junit # Format of test results e2e-tests: name: E2E Tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 201ba296..d83e912f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,7 @@ # Contribute to the development of badaas - [Tests](#tests) + - [Dependencies](#dependencies) - [Unit tests](#unit-tests) - [Feature tests (of end to end tests)](#feature-tests-of-end-to-end-tests) - [Logger](#logger) @@ -12,6 +13,10 @@ ## Tests +### Dependencies + +Running tests have some dependencies as: `mockery`, `gotestsum`, etc.. Install them with `make install dependencies`. + ### Unit tests We use the standard test suite in combination with [github.com/stretchr/testify](https://github.com/stretchr/testify) to do our unit testing. Mocks are generated using [mockery](https://github.com/vektra/mockery) a mock generator using this command `make test_generate_mocks`. diff --git a/Makefile b/Makefile index f53517d1..0d9652fc 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,13 @@ +install_dependencies: + go install gotest.tools/gotestsum@latest + go install github.com/vektra/mockery/v2@v2.20.0 + go install github.com/ditrit/badaas-cli@latest + lint: golangci-lint run test_unit: - go test ./... -v + gotestsum --format pkgname ./... test_e2e: docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" up -d From e3da4cca8944e7cf67bef24ad0d5d1002813bd84 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 31 Jul 2023 16:57:04 +0200 Subject: [PATCH 25/90] add base models to orm --- CONTRIBUTING.md | 2 +- controllers/basicAuth_test.go | 18 ++- mocks/orm/BadaasID.go | 25 ++++ .../services/sessionservice/SessionService.go | 16 +-- orm/baseModels.go | 34 +++++ orm/uuid.go | 88 +++++++++++++ orm/uuid_test.go | 23 ++++ persistence/models/BaseModel.go | 19 --- persistence/models/Session.go | 14 ++- persistence/models/Session_test.go | 10 +- persistence/models/User.go | 4 +- .../middlewares/middlewareAuthentication.go | 12 +- services/sessionservice/session.go | 52 ++++---- services/sessionservice/session_test.go | 117 +++++++++--------- services/sessionservice/sessionctx.go | 6 +- services/sessionservice/sessionctx_test.go | 6 +- 16 files changed, 306 insertions(+), 140 deletions(-) create mode 100644 mocks/orm/BadaasID.go create mode 100644 orm/baseModels.go create mode 100644 orm/uuid.go create mode 100644 orm/uuid_test.go delete mode 100644 persistence/models/BaseModel.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d83e912f..3b758d98 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ This is the directory structure we use for the project: - `logger/` *(Go code)*: Contains the logger creation logic. Please don't call it from your own services and code, use the dependency injection system. - `persistance/` *(Go code)*: - `gormdatabase/` *(Go code)*: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. - - `models/` *(Go code)*: Contains the models. (For a structure to me considered a valid model, it has to embed `models.BaseModel` and satisfy the `models.Tabler` interface. This interface returns the name of the sql table.). + - `models/` *(Go code)*: Contains the models (for a structure to me considered a valid model, it has to embed `badaas/orm.UUIDModel` or `badaas/orm.UIntModel`). - `dto/` *(Go code)*: Contains the Data Transfer Objects. They are used mainly to decode json payloads. - `pagination/` *(Go code)*: Contains the pagination logic. - `repository/` *(Go code)*: Contains the repository interfaces and implementations to manage queries to the database. diff --git a/controllers/basicAuth_test.go b/controllers/basicAuth_test.go index d388a421..20d89e01 100644 --- a/controllers/basicAuth_test.go +++ b/controllers/basicAuth_test.go @@ -5,16 +5,17 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + "github.com/ditrit/badaas/controllers" "github.com/ditrit/badaas/httperrors" mocksSessionService "github.com/ditrit/badaas/mocks/services/sessionservice" mocksUserService "github.com/ditrit/badaas/mocks/services/userservice" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "go.uber.org/zap" - "go.uber.org/zap/zaptest/observer" ) func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { @@ -39,7 +40,6 @@ func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { payload, err := controller.BasicLoginHandler(response, request) assert.Equal(t, controllers.HTTPErrRequestMalformed, err) assert.Nil(t, payload) - } func Test_BasicLoginHandler_UserNotFound(t *testing.T) { @@ -73,7 +73,6 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { payload, err := controller.BasicLoginHandler(response, request) assert.Error(t, err) assert.Nil(t, payload) - } func Test_BasicLoginHandler_LoginFailed(t *testing.T) { @@ -94,7 +93,7 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { ) userService := mocksUserService.NewUserService(t) user := &models.User{ - BaseModel: models.BaseModel{}, + UUIDModel: orm.UUIDModel{}, Username: "bob", Email: "bob@email.com", Password: []byte("hash of 1234"), @@ -116,7 +115,6 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { payload, err := controller.BasicLoginHandler(response, request) assert.Error(t, err) assert.Nil(t, payload) - } func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { @@ -137,8 +135,8 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { ) userService := mocksUserService.NewUserService(t) user := &models.User{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, Username: "bob", Email: "bob@email.com", diff --git a/mocks/orm/BadaasID.go b/mocks/orm/BadaasID.go new file mode 100644 index 00000000..cf2c0098 --- /dev/null +++ b/mocks/orm/BadaasID.go @@ -0,0 +1,25 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// BadaasID is an autogenerated mock type for the BadaasID type +type BadaasID struct { + mock.Mock +} + +type mockConstructorTestingTNewBadaasID interface { + mock.TestingT + Cleanup(func()) +} + +// NewBadaasID creates a new instance of BadaasID. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewBadaasID(t mockConstructorTestingTNewBadaasID) *BadaasID { + mock := &BadaasID{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/services/sessionservice/SessionService.go b/mocks/services/sessionservice/SessionService.go index 101cfba9..e7f0d108 100644 --- a/mocks/services/sessionservice/SessionService.go +++ b/mocks/services/sessionservice/SessionService.go @@ -10,9 +10,9 @@ import ( models "github.com/ditrit/badaas/persistence/models" - sessionservice "github.com/ditrit/badaas/services/sessionservice" + orm "github.com/ditrit/badaas/orm" - uuid "github.com/google/uuid" + sessionservice "github.com/ditrit/badaas/services/sessionservice" ) // SessionService is an autogenerated mock type for the SessionService type @@ -21,21 +21,21 @@ type SessionService struct { } // IsValid provides a mock function with given fields: sessionUUID -func (_m *SessionService) IsValid(sessionUUID uuid.UUID) (bool, *sessionservice.SessionClaims) { +func (_m *SessionService) IsValid(sessionUUID orm.UUID) (bool, *sessionservice.SessionClaims) { ret := _m.Called(sessionUUID) var r0 bool var r1 *sessionservice.SessionClaims - if rf, ok := ret.Get(0).(func(uuid.UUID) (bool, *sessionservice.SessionClaims)); ok { + if rf, ok := ret.Get(0).(func(orm.UUID) (bool, *sessionservice.SessionClaims)); ok { return rf(sessionUUID) } - if rf, ok := ret.Get(0).(func(uuid.UUID) bool); ok { + if rf, ok := ret.Get(0).(func(orm.UUID) bool); ok { r0 = rf(sessionUUID) } else { r0 = ret.Get(0).(bool) } - if rf, ok := ret.Get(1).(func(uuid.UUID) *sessionservice.SessionClaims); ok { + if rf, ok := ret.Get(1).(func(orm.UUID) *sessionservice.SessionClaims); ok { r1 = rf(sessionUUID) } else { if ret.Get(1) != nil { @@ -79,11 +79,11 @@ func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims } // RollSession provides a mock function with given fields: _a0 -func (_m *SessionService) RollSession(_a0 uuid.UUID) httperrors.HTTPError { +func (_m *SessionService) RollSession(_a0 orm.UUID) httperrors.HTTPError { ret := _m.Called(_a0) var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(uuid.UUID) httperrors.HTTPError); ok { + if rf, ok := ret.Get(0).(func(orm.UUID) httperrors.HTTPError); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { diff --git a/orm/baseModels.go b/orm/baseModels.go new file mode 100644 index 00000000..3a0b4816 --- /dev/null +++ b/orm/baseModels.go @@ -0,0 +1,34 @@ +package orm + +import ( + "time" + + "github.com/google/uuid" + + "gorm.io/gorm" +) + +// supported types for model identifier +type BadaasID interface { + uint | UUID +} + +// Base Model for gorm +// +// Every model intended to be saved in the database must embed this UUIDModel or UIntModel +// reference: https://gorm.io/docs/models.html#gorm-Model +type UUIDModel struct { + ID UUID `gorm:"primarykey;not null"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} + +func (model *UUIDModel) BeforeCreate(tx *gorm.DB) (err error) { + if model.ID == UUID(uuid.Nil) { + model.ID = UUID(uuid.New()) + } + return nil +} + +type UIntModel gorm.Model diff --git a/orm/uuid.go b/orm/uuid.go new file mode 100644 index 00000000..6b6ca817 --- /dev/null +++ b/orm/uuid.go @@ -0,0 +1,88 @@ +package orm + +import ( + "context" + "database/sql/driver" + + "github.com/google/uuid" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" +) + +type UUID uuid.UUID + +var NilUUID = UUID(uuid.Nil) + +func (id UUID) GormDBDataType(_ *gorm.DB, _ *schema.Field) string { + return "uuid" +} + +func (id UUID) String() string { + return uuid.UUID(id).String() +} + +func (id UUID) URN() string { + return uuid.UUID(id).URN() +} + +func (id UUID) Variant() uuid.Variant { + return uuid.UUID(id).Variant() +} + +func (id UUID) Version() uuid.Version { + return uuid.UUID(id).Version() +} + +func (id UUID) MarshalText() ([]byte, error) { + return uuid.UUID(id).MarshalText() +} + +func (id *UUID) UnmarshalText(data []byte) error { + return (*uuid.UUID)(id).UnmarshalText(data) +} + +func (id UUID) MarshalBinary() ([]byte, error) { + return uuid.UUID(id).MarshalBinary() +} + +func (id *UUID) UnmarshalBinary(data []byte) error { + return (*uuid.UUID)(id).UnmarshalBinary(data) +} + +func (id *UUID) Scan(src interface{}) error { + return (*uuid.UUID)(id).Scan(src) +} + +func (id UUID) GormValue(_ context.Context, _ *gorm.DB) clause.Expr { + if len(id) == 0 { + return gorm.Expr("NULL") + } + + return gorm.Expr("?", id.String()) +} + +func (id UUID) Value() (driver.Value, error) { + return uuid.UUID(id).Value() +} + +func (id UUID) Time() uuid.Time { + return uuid.UUID(id).Time() +} + +func (id UUID) ClockSequence() int { + return uuid.UUID(id).ClockSequence() +} + +func NewUUID() UUID { + return UUID(uuid.New()) +} + +func ParseUUID(s string) (UUID, error) { + uid, err := uuid.Parse(s) + if err != nil { + return UUID(uuid.Nil), err + } + + return UUID(uid), nil +} diff --git a/orm/uuid_test.go b/orm/uuid_test.go new file mode 100644 index 00000000..1e6828ec --- /dev/null +++ b/orm/uuid_test.go @@ -0,0 +1,23 @@ +package orm_test + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/orm" +) + +func TestParseCorrectUUID(t *testing.T) { + uuidString := uuid.New().String() + uuid, err := orm.ParseUUID(uuidString) + assert.Nil(t, err) + assert.Equal(t, uuidString, uuid.String()) +} + +func TestParseIncorrectUUID(t *testing.T) { + uid, err := orm.ParseUUID("not uuid") + assert.Error(t, err) + assert.Equal(t, orm.NilUUID, uid) +} diff --git a/persistence/models/BaseModel.go b/persistence/models/BaseModel.go deleted file mode 100644 index acf3c2e0..00000000 --- a/persistence/models/BaseModel.go +++ /dev/null @@ -1,19 +0,0 @@ -package models - -import ( - "time" - - "github.com/google/uuid" - "gorm.io/gorm" -) - -// Base Model for gorm -// -// Every model intended to be saved in the database must embed this BaseModel -// reference: https://gorm.io/docs/models.html#gorm-Model -type BaseModel struct { - ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"` - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt gorm.DeletedAt `gorm:"index"` -} diff --git a/persistence/models/Session.go b/persistence/models/Session.go index 89bdb1b7..e6512491 100644 --- a/persistence/models/Session.go +++ b/persistence/models/Session.go @@ -3,16 +3,24 @@ package models import ( "time" - "github.com/google/uuid" + "github.com/ditrit/badaas/orm" ) // Represent a user session type Session struct { - BaseModel - UserID uuid.UUID `gorm:"not null"` + orm.UUIDModel + UserID orm.UUID `gorm:"not null"` ExpiresAt time.Time `gorm:"not null"` } +// Create a new session +func NewSession(userID orm.UUID, sessionDuration time.Duration) *Session { + return &Session{ + UserID: userID, + ExpiresAt: time.Now().Add(sessionDuration), + } +} + // Return true is expired func (session *Session) IsExpired() bool { return time.Now().After(session.ExpiresAt) diff --git a/persistence/models/Session_test.go b/persistence/models/Session_test.go index a1fdd240..28176820 100644 --- a/persistence/models/Session_test.go +++ b/persistence/models/Session_test.go @@ -4,10 +4,18 @@ import ( "testing" "time" - "github.com/ditrit/badaas/persistence/models" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/persistence/models" ) +func TestNewSession(t *testing.T) { + sessionInstance := models.NewSession(orm.NilUUID, time.Second) + assert.NotNil(t, sessionInstance) + assert.Equal(t, orm.NilUUID, sessionInstance.UserID) +} + func TestExpired(t *testing.T) { sessionInstance := &models.Session{ ExpiresAt: time.Now().Add(time.Second), diff --git a/persistence/models/User.go b/persistence/models/User.go index d65aa425..9717cd05 100644 --- a/persistence/models/User.go +++ b/persistence/models/User.go @@ -1,8 +1,10 @@ package models +import "github.com/ditrit/badaas/orm" + // Represents a user type User struct { - BaseModel + orm.UUIDModel Username string `gorm:"not null"` Email string `gorm:"unique;not null"` diff --git a/router/middlewares/middlewareAuthentication.go b/router/middlewares/middlewareAuthentication.go index b1560dd5..66f5a956 100644 --- a/router/middlewares/middlewareAuthentication.go +++ b/router/middlewares/middlewareAuthentication.go @@ -3,15 +3,14 @@ package middlewares import ( "net/http" + "go.uber.org/zap" + "github.com/ditrit/badaas/httperrors" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/services/sessionservice" - "github.com/google/uuid" - "go.uber.org/zap" ) -var ( - NotAuthenticated = httperrors.NewUnauthorizedError("Authentication Error", "not authenticated") -) +var NotAuthenticated = httperrors.NewUnauthorizedError("Authentication Error", "not authenticated") // The authentication middleware type AuthenticationMiddleware interface { @@ -43,7 +42,8 @@ func (authenticationMiddleware *authenticationMiddleware) Handle(next http.Handl NotAuthenticated.Write(response, authenticationMiddleware.logger) return } - extractedUUID, err := uuid.Parse(accessTokenCookie.Value) + + extractedUUID, err := orm.ParseUUID(accessTokenCookie.Value) if err != nil { NotAuthenticated.Write(response, authenticationMiddleware.logger) return diff --git a/services/sessionservice/session.go b/services/sessionservice/session.go index 9c41818f..142c8c18 100644 --- a/services/sessionservice/session.go +++ b/services/sessionservice/session.go @@ -6,13 +6,16 @@ import ( "sync" "time" + "go.uber.org/zap" + "gorm.io/gorm" + "github.com/Masterminds/squirrel" + "github.com/ditrit/badaas/configuration" "github.com/ditrit/badaas/httperrors" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/repository" - "github.com/google/uuid" - "go.uber.org/zap" ) // Errors @@ -25,8 +28,8 @@ var ( // SessionService handle sessions type SessionService interface { - IsValid(sessionUUID uuid.UUID) (bool, *SessionClaims) - RollSession(uuid.UUID) httperrors.HTTPError + IsValid(sessionUUID orm.UUID) (bool, *SessionClaims) + RollSession(orm.UUID) httperrors.HTTPError LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError LogUserOut(sessionClaims *SessionClaims, response http.ResponseWriter) httperrors.HTTPError } @@ -36,8 +39,8 @@ var _ SessionService = (*sessionServiceImpl)(nil) // The SessionService concrete interface type sessionServiceImpl struct { - sessionRepository repository.CRUDRepository[models.Session, uuid.UUID] - cache map[uuid.UUID]*models.Session + sessionRepository repository.CRUDRepository[models.Session, orm.UUID] + cache map[orm.UUID]*models.Session mutex sync.Mutex logger *zap.Logger sessionConfiguration configuration.SessionConfiguration @@ -46,11 +49,12 @@ type sessionServiceImpl struct { // The SessionService constructor func NewSessionService( logger *zap.Logger, - sessionRepository repository.CRUDRepository[models.Session, uuid.UUID], + sessionRepository repository.CRUDRepository[models.Session, orm.UUID], sessionConfiguration configuration.SessionConfiguration, + db *gorm.DB, ) SessionService { sessionService := &sessionServiceImpl{ - cache: make(map[uuid.UUID]*models.Session), + cache: make(map[orm.UUID]*models.Session), logger: logger, sessionRepository: sessionRepository, sessionConfiguration: sessionConfiguration, @@ -59,17 +63,9 @@ func NewSessionService( return sessionService } -// Create a new session -func newSession(userID uuid.UUID, sessionDuration time.Duration) *models.Session { - return &models.Session{ - UserID: userID, - ExpiresAt: time.Now().Add(sessionDuration), - } -} - // Return true if the session exists and is still valid. // A instance of SessionClaims is returned to be added to the request context if the conditions previously mentioned are met. -func (sessionService *sessionServiceImpl) IsValid(sessionUUID uuid.UUID) (bool, *SessionClaims) { +func (sessionService *sessionServiceImpl) IsValid(sessionUUID orm.UUID) (bool, *SessionClaims) { sessionInstance := sessionService.get(sessionUUID) if sessionInstance == nil { return false, nil @@ -79,13 +75,15 @@ func (sessionService *sessionServiceImpl) IsValid(sessionUUID uuid.UUID) (bool, // Get a session from cache // return nil if not found -func (sessionService *sessionServiceImpl) get(sessionUUID uuid.UUID) *models.Session { +func (sessionService *sessionServiceImpl) get(sessionUUID orm.UUID) *models.Session { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + session, ok := sessionService.cache[sessionUUID] if ok { return session } + sessionsFoundWithUUID, databaseError := sessionService.sessionRepository.Find(squirrel.Eq{"uuid": sessionUUID.String()}, nil, nil) if databaseError != nil { return nil @@ -100,18 +98,21 @@ func (sessionService *sessionServiceImpl) get(sessionUUID uuid.UUID) *models.Ses func (sessionService *sessionServiceImpl) add(session *models.Session) httperrors.HTTPError { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() - herr := sessionService.sessionRepository.Create(session) - if herr != nil { - return herr + + err := sessionService.sessionRepository.Create(session) + if err != nil { + return err } + sessionService.cache[session.ID] = session sessionService.logger.Debug("Added session", zap.String("uuid", session.ID.String())) + return nil } // Initialize the session service func (sessionService *sessionServiceImpl) init() { - sessionService.cache = make(map[uuid.UUID]*models.Session) + sessionService.cache = make(map[orm.UUID]*models.Session) go func() { for { sessionService.removeExpired() @@ -131,7 +132,8 @@ func (sessionService *sessionServiceImpl) pullFromDB() { if err != nil { panic(err) } - newSessionCache := make(map[uuid.UUID]*models.Session) + + newSessionCache := make(map[orm.UUID]*models.Session) for _, sessionFromDatabase := range sessionsFromDatabase { newSessionCache[sessionFromDatabase.ID] = sessionFromDatabase } @@ -185,7 +187,7 @@ func (sessionService *sessionServiceImpl) delete(session *models.Session) httper } // Roll a session. If the session is close to expiration, extend its duration. -func (sessionService *sessionServiceImpl) RollSession(sessionUUID uuid.UUID) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) RollSession(sessionUUID orm.UUID) httperrors.HTTPError { rollInterval := sessionService.sessionConfiguration.GetRollDuration() sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() session := sessionService.get(sessionUUID) @@ -214,7 +216,7 @@ func (sessionService *sessionServiceImpl) RollSession(sessionUUID uuid.UUID) htt // Log in a user func (sessionService *sessionServiceImpl) LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError { sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() - session := newSession(user.ID, sessionDuration) + session := models.NewSession(user.ID, sessionDuration) err := sessionService.add(session) if err != nil { return err diff --git a/services/sessionservice/session_test.go b/services/sessionservice/session_test.go index 422b831f..d676d0d8 100644 --- a/services/sessionservice/session_test.go +++ b/services/sessionservice/session_test.go @@ -6,25 +6,20 @@ import ( "time" "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/httperrors" - configurationmocks "github.com/ditrit/badaas/mocks/configuration" - repositorymocks "github.com/ditrit/badaas/mocks/persistence/repository" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" -) -func TestNewSession(t *testing.T) { - sessionInstance := newSession(uuid.Nil, time.Second) - assert.NotNil(t, sessionInstance) - assert.Equal(t, uuid.Nil, sessionInstance.UserID) -} + "github.com/ditrit/badaas/httperrors" + configurationmocks "github.com/ditrit/badaas/mocks/configuration" + repositorymocks "github.com/ditrit/badaas/mocks/persistence/repository" + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/persistence/pagination" +) func TestLogInUser(t *testing.T) { sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) @@ -48,19 +43,19 @@ func TestLogInUser(t *testing.T) { func setupTest( t *testing.T, ) ( - *repositorymocks.CRUDRepository[models.Session, uuid.UUID], + *repositorymocks.CRUDRepository[models.Session, orm.UUID], *sessionServiceImpl, *observer.ObservedLogs, *configurationmocks.SessionConfiguration, ) { core, logs := observer.New(zap.DebugLevel) logger := zap.New(core) - sessionRepositoryMock := repositorymocks.NewCRUDRepository[models.Session, uuid.UUID](t) + sessionRepositoryMock := repositorymocks.NewCRUDRepository[models.Session, orm.UUID](t) sessionConfiguration := configurationmocks.NewSessionConfiguration(t) service := &sessionServiceImpl{ sessionRepository: sessionRepositoryMock, logger: logger, - cache: make(map[uuid.UUID]*models.Session), + cache: make(map[orm.UUID]*models.Session), sessionConfiguration: sessionConfiguration, } @@ -86,22 +81,22 @@ func TestLogInUserDbError(t *testing.T) { func TestIsValid(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock.On("Create", mock.Anything).Return(nil) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: orm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } err := service.add(session) require.NoError(t, err) assert.Len(t, service.cache, 1) - assert.Equal(t, uuid.Nil, service.cache[uuidSample].UserID) + assert.Equal(t, orm.NilUUID, service.cache[uuidSample].UserID) isValid, claims := service.IsValid(uuidSample) require.True(t, isValid) assert.Equal(t, *claims, SessionClaims{ - UserID: uuid.Nil, + UserID: orm.NilUUID, SessionUUID: uuidSample, }) } @@ -111,22 +106,21 @@ func TestIsValid_SessionNotFound(t *testing.T) { sessionRepositoryMock. On("Find", mock.Anything, mock.Anything, mock.Anything). Return(pagination.NewPage([]*models.Session{}, 0, 125, 1236), nil) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() isValid, _ := service.IsValid(uuidSample) require.False(t, isValid) - // } func TestLogOutUser(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock.On("Delete", mock.Anything).Return(nil) response := httptest.NewRecorder() - uuidSample := uuid.New() + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: orm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session @@ -139,12 +133,12 @@ func TestLogOutUserDbError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock.On("Delete", mock.Anything).Return(httperrors.NewInternalServerError("db errors", "oh we failed to delete the session", nil)) response := httptest.NewRecorder() - uuidSample := uuid.New() + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: orm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session @@ -159,17 +153,18 @@ func TestLogOutUser_SessionNotFound(t *testing.T) { On("Find", mock.Anything, nil, nil). Return(nil, httperrors.NewInternalServerError("db errors", "oh we failed to delete the session", nil)) response := httptest.NewRecorder() - uuidSample := uuid.New() + + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session sessionClaims := makeSessionClaims(session) - sessionClaims.SessionUUID = uuid.Nil + sessionClaims.SessionUUID = orm.NilUUID err := service.LogUserOut(sessionClaims, response) require.Error(t, err) assert.Len(t, service.cache, 1) @@ -181,13 +176,13 @@ func TestRollSession(t *testing.T) { sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() originalExpirationTime := time.Now().Add(sessionDuration / 5) session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session @@ -201,13 +196,13 @@ func TestRollSession_Expired(t *testing.T) { sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() originalExpirationTime := time.Now().Add(-time.Hour) session := &models.Session{ - BaseModel: models.BaseModel{ + UUIDModel: orm.UUIDModel{ ID: uuidSample, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session @@ -221,18 +216,18 @@ func TestRollSession_falseUUID(t *testing.T) { sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() originalExpirationTime := time.Now().Add(-time.Hour) session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session repoSession.On("Find", mock.Anything, nil, nil).Return(pagination.NewPage([]*models.Session{}, 0, 2, 5), nil) - err := service.RollSession(uuid.New()) + err := service.RollSession(orm.NewUUID()) require.NoError(t, err) } @@ -247,17 +242,17 @@ func TestRollSession_sessionNotFound(t *testing.T) { sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration) - err := service.RollSession(uuid.Nil) + err := service.RollSession(orm.NilUUID) require.NoError(t, err) } func Test_pullFromDB(t *testing.T) { sessionRepositoryMock, service, logs, _ := setupTest(t) session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } sessionRepositoryMock.On("GetAll", nil).Return([]*models.Session{session}, nil) @@ -280,12 +275,12 @@ func Test_pullFromDB_repoError(t *testing.T) { func Test_removeExpired(t *testing.T) { sessionRepositoryMock, service, logs, _ := setupTest(t) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. @@ -305,12 +300,12 @@ func Test_removeExpired(t *testing.T) { func Test_removeExpired_RepositoryError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. @@ -323,12 +318,12 @@ func Test_removeExpired_RepositoryError(t *testing.T) { func Test_get(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - uuidSample := uuid.New() + uuidSample := orm.NewUUID() session := &models.Session{ - BaseModel: models.BaseModel{ - ID: uuid.Nil, + UUIDModel: orm.UUIDModel{ + ID: orm.NilUUID, }, - UserID: uuid.Nil, + UserID: orm.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. diff --git a/services/sessionservice/sessionctx.go b/services/sessionservice/sessionctx.go index 6710766a..ec7a70ac 100644 --- a/services/sessionservice/sessionctx.go +++ b/services/sessionservice/sessionctx.go @@ -3,14 +3,14 @@ package sessionservice import ( "context" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" - "github.com/google/uuid" ) // The session claims passed in the request context type SessionClaims struct { - UserID uuid.UUID - SessionUUID uuid.UUID + UserID orm.UUID + SessionUUID orm.UUID } // Unique claim key type diff --git a/services/sessionservice/sessionctx_test.go b/services/sessionservice/sessionctx_test.go index 88b3f954..129dc101 100644 --- a/services/sessionservice/sessionctx_test.go +++ b/services/sessionservice/sessionctx_test.go @@ -6,14 +6,16 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/orm" ) func TestSessionCtx(t *testing.T) { ctx := context.Background() - sessionClaims := &SessionClaims{uuid.Nil, uuid.New()} + sessionClaims := &SessionClaims{orm.NilUUID, orm.UUID(uuid.New())} ctx = SetSessionClaimsContext(ctx, sessionClaims) claims := GetSessionClaimsFromContext(ctx) - assert.Equal(t, uuid.Nil, claims.UserID) + assert.Equal(t, orm.NilUUID, claims.UserID) } func TestSessionCtxPanic(t *testing.T) { From d12b69e2c358cf32cc6d58b144da5b316bbff6e3 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 31 Jul 2023 17:01:41 +0200 Subject: [PATCH 26/90] add crud services and repositories in orm module --- mocks/orm/CRUDRepository.go | 163 ++++++++++++ mocks/orm/CRUDService.go | 118 +++++++++ mocks/orm/Condition.go | 59 +++++ mocks/persistence/models/Tabler.go | 39 --- mocks/persistence/pagination/Paginator.go | 53 ---- .../persistence/repository/CRUDRepository.go | 222 ---------------- mocks/persistence/repository/SortOption.go | 53 ---- orm/ModuleFx.go | 76 ++++++ orm/condition.go | 157 ++++++++++++ orm/crudRepository.go | 127 +++++++++ orm/crudService.go | 54 ++++ orm/orm.go | 5 + persistence/ModuleFx.go | 8 - persistence/models/Session.go | 7 - persistence/models/Session_test.go | 4 - persistence/models/Tabler.go | 12 - persistence/models/User.go | 10 +- persistence/pagination/Page.go | 40 --- persistence/pagination/Page_test.go | 145 ----------- persistence/pagination/Paginator.go | 36 --- persistence/pagination/Paginator_test.go | 22 -- persistence/repository/CRUDRepository.go | 20 -- persistence/repository/CRUDRepositoryImpl.go | 240 ------------------ .../repository/CRUDRepositoryImpl_test.go | 52 ---- persistence/repository/SortOption.go | 27 -- persistence/repository/SortOption_test.go | 14 - 26 files changed, 764 insertions(+), 999 deletions(-) create mode 100644 mocks/orm/CRUDRepository.go create mode 100644 mocks/orm/CRUDService.go create mode 100644 mocks/orm/Condition.go delete mode 100644 mocks/persistence/models/Tabler.go delete mode 100644 mocks/persistence/pagination/Paginator.go delete mode 100644 mocks/persistence/repository/CRUDRepository.go delete mode 100644 mocks/persistence/repository/SortOption.go create mode 100644 orm/condition.go create mode 100644 orm/crudRepository.go create mode 100644 orm/crudService.go delete mode 100644 persistence/models/Tabler.go delete mode 100644 persistence/pagination/Page.go delete mode 100644 persistence/pagination/Page_test.go delete mode 100644 persistence/pagination/Paginator.go delete mode 100644 persistence/pagination/Paginator_test.go delete mode 100644 persistence/repository/CRUDRepository.go delete mode 100644 persistence/repository/CRUDRepositoryImpl.go delete mode 100644 persistence/repository/CRUDRepositoryImpl_test.go delete mode 100644 persistence/repository/SortOption.go delete mode 100644 persistence/repository/SortOption_test.go diff --git a/mocks/orm/CRUDRepository.go b/mocks/orm/CRUDRepository.go new file mode 100644 index 00000000..db93a68f --- /dev/null +++ b/mocks/orm/CRUDRepository.go @@ -0,0 +1,163 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + orm "github.com/ditrit/badaas/orm" + mock "github.com/stretchr/testify/mock" + gorm "gorm.io/gorm" +) + +// CRUDRepository is an autogenerated mock type for the CRUDRepository type +type CRUDRepository[T interface{}, ID orm.BadaasID] struct { + mock.Mock +} + +// Create provides a mock function with given fields: tx, entity +func (_m *CRUDRepository[T, ID]) Create(tx *gorm.DB, entity *T) error { + ret := _m.Called(tx, entity) + + var r0 error + if rf, ok := ret.Get(0).(func(*gorm.DB, *T) error); ok { + r0 = rf(tx, entity) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Delete provides a mock function with given fields: tx, entity +func (_m *CRUDRepository[T, ID]) Delete(tx *gorm.DB, entity *T) error { + ret := _m.Called(tx, entity) + + var r0 error + if rf, ok := ret.Get(0).(func(*gorm.DB, *T) error); ok { + r0 = rf(tx, entity) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetByID provides a mock function with given fields: tx, id +func (_m *CRUDRepository[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, error) { + ret := _m.Called(tx, id) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, ID) (*T, error)); ok { + return rf(tx, id) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, ID) *T); ok { + r0 = rf(tx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, ID) error); ok { + r1 = rf(tx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Query provides a mock function with given fields: tx, conditions +func (_m *CRUDRepository[T, ID]) Query(tx *gorm.DB, conditions ...orm.Condition[T]) ([]*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, tx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []*T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) ([]*T, error)); ok { + return rf(tx, conditions...) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) []*T); ok { + r0 = rf(tx, conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, ...orm.Condition[T]) error); ok { + r1 = rf(tx, conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// QueryOne provides a mock function with given fields: tx, conditions +func (_m *CRUDRepository[T, ID]) QueryOne(tx *gorm.DB, conditions ...orm.Condition[T]) (*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, tx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) (*T, error)); ok { + return rf(tx, conditions...) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) *T); ok { + r0 = rf(tx, conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, ...orm.Condition[T]) error); ok { + r1 = rf(tx, conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Save provides a mock function with given fields: tx, entity +func (_m *CRUDRepository[T, ID]) Save(tx *gorm.DB, entity *T) error { + ret := _m.Called(tx, entity) + + var r0 error + if rf, ok := ret.Get(0).(func(*gorm.DB, *T) error); ok { + r0 = rf(tx, entity) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewCRUDRepository interface { + mock.TestingT + Cleanup(func()) +} + +// NewCRUDRepository creates a new instance of CRUDRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCRUDRepository[T interface{}, ID orm.BadaasID](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { + mock := &CRUDRepository[T, ID]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/CRUDService.go b/mocks/orm/CRUDService.go new file mode 100644 index 00000000..1a047339 --- /dev/null +++ b/mocks/orm/CRUDService.go @@ -0,0 +1,118 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + orm "github.com/ditrit/badaas/orm" + mock "github.com/stretchr/testify/mock" +) + +// CRUDService is an autogenerated mock type for the CRUDService type +type CRUDService[T interface{}, ID orm.BadaasID] struct { + mock.Mock +} + +// GetByID provides a mock function with given fields: id +func (_m *CRUDService[T, ID]) GetByID(id ID) (*T, error) { + ret := _m.Called(id) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(ID) (*T, error)); ok { + return rf(id) + } + if rf, ok := ret.Get(0).(func(ID) *T); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(ID) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Query provides a mock function with given fields: conditions +func (_m *CRUDService[T, ID]) Query(conditions ...orm.Condition[T]) ([]*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []*T + var r1 error + if rf, ok := ret.Get(0).(func(...orm.Condition[T]) ([]*T, error)); ok { + return rf(conditions...) + } + if rf, ok := ret.Get(0).(func(...orm.Condition[T]) []*T); ok { + r0 = rf(conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*T) + } + } + + if rf, ok := ret.Get(1).(func(...orm.Condition[T]) error); ok { + r1 = rf(conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// QueryOne provides a mock function with given fields: conditions +func (_m *CRUDService[T, ID]) QueryOne(conditions ...orm.Condition[T]) (*T, error) { + _va := make([]interface{}, len(conditions)) + for _i := range conditions { + _va[_i] = conditions[_i] + } + var _ca []interface{} + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *T + var r1 error + if rf, ok := ret.Get(0).(func(...orm.Condition[T]) (*T, error)); ok { + return rf(conditions...) + } + if rf, ok := ret.Get(0).(func(...orm.Condition[T]) *T); ok { + r0 = rf(conditions...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*T) + } + } + + if rf, ok := ret.Get(1).(func(...orm.Condition[T]) error); ok { + r1 = rf(conditions...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewCRUDService interface { + mock.TestingT + Cleanup(func()) +} + +// NewCRUDService creates a new instance of CRUDService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCRUDService[T interface{}, ID orm.BadaasID](t mockConstructorTestingTNewCRUDService) *CRUDService[T, ID] { + mock := &CRUDService[T, ID]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/Condition.go b/mocks/orm/Condition.go new file mode 100644 index 00000000..2d214a05 --- /dev/null +++ b/mocks/orm/Condition.go @@ -0,0 +1,59 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + gorm "gorm.io/gorm" +) + +// Condition is an autogenerated mock type for the Condition type +type Condition[T interface{}] struct { + mock.Mock +} + +// ApplyTo provides a mock function with given fields: query, tableName +func (_m *Condition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + ret := _m.Called(query, tableName) + + var r0 *gorm.DB + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, string) (*gorm.DB, error)); ok { + return rf(query, tableName) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, string) *gorm.DB); ok { + r0 = rf(query, tableName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gorm.DB) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, string) error); ok { + r1 = rf(query, tableName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// interfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *Condition[T]) interfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +type mockConstructorTestingTNewCondition interface { + mock.TestingT + Cleanup(func()) +} + +// NewCondition creates a new instance of Condition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCondition[T interface{}](t mockConstructorTestingTNewCondition) *Condition[T] { + mock := &Condition[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/persistence/models/Tabler.go b/mocks/persistence/models/Tabler.go deleted file mode 100644 index 6a50e56e..00000000 --- a/mocks/persistence/models/Tabler.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Tabler is an autogenerated mock type for the Tabler type -type Tabler struct { - mock.Mock -} - -// TableName provides a mock function with given fields: -func (_m *Tabler) TableName() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -type mockConstructorTestingTNewTabler interface { - mock.TestingT - Cleanup(func()) -} - -// NewTabler creates a new instance of Tabler. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewTabler(t mockConstructorTestingTNewTabler) *Tabler { - mock := &Tabler{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/persistence/pagination/Paginator.go b/mocks/persistence/pagination/Paginator.go deleted file mode 100644 index ef2c6359..00000000 --- a/mocks/persistence/pagination/Paginator.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Paginator is an autogenerated mock type for the Paginator type -type Paginator struct { - mock.Mock -} - -// Limit provides a mock function with given fields: -func (_m *Paginator) Limit() uint { - ret := _m.Called() - - var r0 uint - if rf, ok := ret.Get(0).(func() uint); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint) - } - - return r0 -} - -// Offset provides a mock function with given fields: -func (_m *Paginator) Offset() uint { - ret := _m.Called() - - var r0 uint - if rf, ok := ret.Get(0).(func() uint); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint) - } - - return r0 -} - -type mockConstructorTestingTNewPaginator interface { - mock.TestingT - Cleanup(func()) -} - -// NewPaginator creates a new instance of Paginator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewPaginator(t mockConstructorTestingTNewPaginator) *Paginator { - mock := &Paginator{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/persistence/repository/CRUDRepository.go b/mocks/persistence/repository/CRUDRepository.go deleted file mode 100644 index eeaecbbb..00000000 --- a/mocks/persistence/repository/CRUDRepository.go +++ /dev/null @@ -1,222 +0,0 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. - -package mocks - -import ( - httperrors "github.com/ditrit/badaas/httperrors" - mock "github.com/stretchr/testify/mock" - - models "github.com/ditrit/badaas/persistence/models" - - pagination "github.com/ditrit/badaas/persistence/pagination" - - repository "github.com/ditrit/badaas/persistence/repository" - - squirrel "github.com/Masterminds/squirrel" -) - -// CRUDRepository is an autogenerated mock type for the CRUDRepository type -type CRUDRepository[T models.Tabler, ID interface{}] struct { - mock.Mock -} - -// Count provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Count(_a0 squirrel.Sqlizer) (uint, httperrors.HTTPError) { - ret := _m.Called(_a0) - - var r0 uint - var r1 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(squirrel.Sqlizer) (uint, httperrors.HTTPError)); ok { - return rf(_a0) - } - if rf, ok := ret.Get(0).(func(squirrel.Sqlizer) uint); ok { - r0 = rf(_a0) - } else { - r0 = ret.Get(0).(uint) - } - - if rf, ok := ret.Get(1).(func(squirrel.Sqlizer) httperrors.HTTPError); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// Create provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Create(_a0 *T) httperrors.HTTPError { - ret := _m.Called(_a0) - - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*T) httperrors.HTTPError); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) - } - } - - return r0 -} - -// Delete provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Delete(_a0 *T) httperrors.HTTPError { - ret := _m.Called(_a0) - - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*T) httperrors.HTTPError); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) - } - } - - return r0 -} - -// Find provides a mock function with given fields: _a0, _a1, _a2 -func (_m *CRUDRepository[T, ID]) Find(_a0 squirrel.Sqlizer, _a1 pagination.Paginator, _a2 repository.SortOption) (*pagination.Page[T], httperrors.HTTPError) { - ret := _m.Called(_a0, _a1, _a2) - - var r0 *pagination.Page[T] - var r1 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) (*pagination.Page[T], httperrors.HTTPError)); ok { - return rf(_a0, _a1, _a2) - } - if rf, ok := ret.Get(0).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) *pagination.Page[T]); ok { - r0 = rf(_a0, _a1, _a2) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*pagination.Page[T]) - } - } - - if rf, ok := ret.Get(1).(func(squirrel.Sqlizer, pagination.Paginator, repository.SortOption) httperrors.HTTPError); ok { - r1 = rf(_a0, _a1, _a2) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// GetAll provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) GetAll(_a0 repository.SortOption) ([]*T, httperrors.HTTPError) { - ret := _m.Called(_a0) - - var r0 []*T - var r1 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(repository.SortOption) ([]*T, httperrors.HTTPError)); ok { - return rf(_a0) - } - if rf, ok := ret.Get(0).(func(repository.SortOption) []*T); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*T) - } - } - - if rf, ok := ret.Get(1).(func(repository.SortOption) httperrors.HTTPError); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// GetByID provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) GetByID(_a0 ID) (*T, httperrors.HTTPError) { - ret := _m.Called(_a0) - - var r0 *T - var r1 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(ID) (*T, httperrors.HTTPError)); ok { - return rf(_a0) - } - if rf, ok := ret.Get(0).(func(ID) *T); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*T) - } - } - - if rf, ok := ret.Get(1).(func(ID) httperrors.HTTPError); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -// Save provides a mock function with given fields: _a0 -func (_m *CRUDRepository[T, ID]) Save(_a0 *T) httperrors.HTTPError { - ret := _m.Called(_a0) - - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*T) httperrors.HTTPError); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) - } - } - - return r0 -} - -// Transaction provides a mock function with given fields: fn -func (_m *CRUDRepository[T, ID]) Transaction(fn func(repository.CRUDRepository[T, ID]) (interface{}, error)) (interface{}, httperrors.HTTPError) { - ret := _m.Called(fn) - - var r0 interface{} - var r1 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) (interface{}, httperrors.HTTPError)); ok { - return rf(fn) - } - if rf, ok := ret.Get(0).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) interface{}); ok { - r0 = rf(fn) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(interface{}) - } - } - - if rf, ok := ret.Get(1).(func(func(repository.CRUDRepository[T, ID]) (interface{}, error)) httperrors.HTTPError); ok { - r1 = rf(fn) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } - } - - return r0, r1 -} - -type mockConstructorTestingTNewCRUDRepository interface { - mock.TestingT - Cleanup(func()) -} - -// NewCRUDRepository creates a new instance of CRUDRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCRUDRepository[T models.Tabler, ID interface{}](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { - mock := &CRUDRepository[T, ID]{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/persistence/repository/SortOption.go b/mocks/persistence/repository/SortOption.go deleted file mode 100644 index 807bd26c..00000000 --- a/mocks/persistence/repository/SortOption.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// SortOption is an autogenerated mock type for the SortOption type -type SortOption struct { - mock.Mock -} - -// Column provides a mock function with given fields: -func (_m *SortOption) Column() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Desc provides a mock function with given fields: -func (_m *SortOption) Desc() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -type mockConstructorTestingTNewSortOption interface { - mock.TestingT - Cleanup(func()) -} - -// NewSortOption creates a new instance of SortOption. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewSortOption(t mockConstructorTestingTNewSortOption) *SortOption { - mock := &SortOption{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/orm/ModuleFx.go b/orm/ModuleFx.go index 3f7cebaf..a55b9bfa 100644 --- a/orm/ModuleFx.go +++ b/orm/ModuleFx.go @@ -1,6 +1,10 @@ package orm import ( + "fmt" + "log" + "reflect" + "go.uber.org/fx" ) @@ -19,3 +23,75 @@ var AutoMigrate = fx.Module( ), ), ) + +func GetCRUDServiceModule[T any]() fx.Option { + entity := *new(T) + + moduleName := fmt.Sprintf( + "%TCRUDServiceModule", + entity, + ) + + kind := getModelKind(entity) + switch kind { + case KindUUIDModel: + return fx.Module( + moduleName, + // repository + fx.Provide(NewCRUDRepository[T, UUID]), + // service + fx.Provide(NewCRUDService[T, UUID]), + ) + case KindUIntModel: + return fx.Module( + moduleName, + // repository + fx.Provide(NewCRUDRepository[T, uint]), + // service + fx.Provide(NewCRUDService[T, uint]), + ) + default: + log.Printf("type %T is not a BaDaaS model\n", entity) + return fx.Invoke(failNotBaDaaSModel()) + } +} + +func failNotBaDaaSModel() error { + return fmt.Errorf("type is not a BaDaaS model") +} + +type modelKind uint + +const ( + KindUUIDModel modelKind = iota + KindUIntModel + KindNotModel +) + +func getModelKind(entity any) modelKind { + entityType := getEntityType(entity) + + _, isUUIDModel := entityType.FieldByName("UUIDModel") + if isUUIDModel { + return KindUUIDModel + } + + _, isUIntModel := entityType.FieldByName("UIntModel") + if isUIntModel { + return KindUIntModel + } + + return KindNotModel +} + +// Get the reflect.Type of any entity or pointer to entity +func getEntityType(entity any) reflect.Type { + entityType := reflect.TypeOf(entity) + + // entityType will be a pointer if the relation can be nullable + if entityType.Kind() == reflect.Pointer { + entityType = entityType.Elem() + } + + return entityType +} diff --git a/orm/condition.go b/orm/condition.go new file mode 100644 index 00000000..0a5bb3be --- /dev/null +++ b/orm/condition.go @@ -0,0 +1,157 @@ +package orm + +import ( + "fmt" + + "gorm.io/gorm" +) + +const DeletedAtField = "DeletedAt" + +type Condition[T any] interface { + // Applies the condition to the "query" + // using the "tableName" as name for the table holding + // the data for object of type T + ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) + + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T], + // since if no method receives by parameter a type T, + // any other Condition[T2] would also be considered a Condition[T]. + interfaceVerificationMethod(T) +} + +type WhereCondition[T any] struct { + Field string + Column string + ColumnPrefix string + Value any +} + +func (condition WhereCondition[T]) interfaceVerificationMethod(t T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +// Returns a gorm Where condition that can be used +// to filter that the Field as a value of Value +func (condition WhereCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + sql, values := condition.GetSQL(query, tableName) + + if condition.Field == DeletedAtField { + query = query.Unscoped() + } + + return query.Where( + sql, + values..., + ), nil +} + +func (condition WhereCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []any) { + columnName := condition.Column + if columnName == "" { + columnName = query.NamingStrategy.ColumnName(tableName, condition.Field) + } + columnName = condition.ColumnPrefix + columnName + + return fmt.Sprintf( + "%s.%s = ?", + tableName, + columnName, + ), []any{condition.Value} +} + +type JoinCondition[T1 any, T2 any] struct { + T1Field string + T2Field string + Conditions []Condition[T2] +} + +func (condition JoinCondition[T1, T2]) interfaceVerificationMethod(t T1) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +// Applies a join between the tables of T1 and T2 +// previousTableName is the name of the table of T1 +// It also applies the nested conditions +func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, previousTableName string) (*gorm.DB, error) { + // get the name of the table for T2 + toBeJoinedTableName, err := getTableName(query, *new(T2)) + if err != nil { + return nil, err + } + + // add a suffix to avoid tables with the same name when joining + // the same table more than once + nextTableName := toBeJoinedTableName + "_" + previousTableName + + // get the sql to do the join with T2 + joinQuery := condition.getSQLJoin(query, toBeJoinedTableName, nextTableName, previousTableName) + + whereConditions, joinConditions := divideConditionsByType(condition.Conditions) + + // apply WhereConditions to join in "on" clause + conditionsValues := []any{} + isDeletedAtConditionPresent := false + for _, condition := range whereConditions { + if condition.Field == DeletedAtField { + isDeletedAtConditionPresent = true + } + sql, values := condition.GetSQL(query, nextTableName) + joinQuery += " AND " + sql + conditionsValues = append(conditionsValues, values...) + } + + if !isDeletedAtConditionPresent { + joinQuery += fmt.Sprintf( + " AND %s.deleted_at IS NULL", + nextTableName, + ) + } + + // add the join to the query + query = query.Joins(joinQuery, conditionsValues...) + + // apply nested joins + for _, joinCondition := range joinConditions { + query, err = joinCondition.ApplyTo(query, nextTableName) + if err != nil { + return nil, err + } + } + + return query, nil +} + +// Returns the SQL string to do a join between T1 and T2 +// taking into account that the ID attribute necessary to do it +// can be either in T1's or T2's table. +func (condition JoinCondition[T1, T2]) getSQLJoin(query *gorm.DB, toBeJoinedTableName, nextTableName, previousTableName string) string { + return fmt.Sprintf( + `JOIN %[1]s %[2]s ON %[2]s.%[3]s = %[4]s.%[5]s + `, + toBeJoinedTableName, + nextTableName, + query.NamingStrategy.ColumnName(nextTableName, condition.T2Field), + previousTableName, + query.NamingStrategy.ColumnName(previousTableName, condition.T1Field), + ) +} + +// Divides a list of conditions by its type: WhereConditions and JoinConditions +func divideConditionsByType[T any]( + conditions []Condition[T], +) (thisEntityConditions []WhereCondition[T], joinConditions []Condition[T]) { + for _, condition := range conditions { + switch typedCondition := condition.(type) { + case WhereCondition[T]: + thisEntityConditions = append(thisEntityConditions, typedCondition) + default: + joinConditions = append(joinConditions, typedCondition) + } + } + + return +} diff --git a/orm/crudRepository.go b/orm/crudRepository.go new file mode 100644 index 00000000..b1d7883b --- /dev/null +++ b/orm/crudRepository.go @@ -0,0 +1,127 @@ +package orm + +import ( + "errors" + "sync" + + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +// Generic CRUD Repository +// T can be any model whose identifier attribute is of type ID +type CRUDRepository[T any, ID BadaasID] interface { + // Create model "model" inside transaction "tx" + Create(tx *gorm.DB, entity *T) error + + // ----- read ----- + // Get a model by its ID + GetByID(tx *gorm.DB, id ID) (*T, error) + + // Get only one model that match "conditions" inside transaction "tx" + // or returns error if 0 or more than 1 are found. + QueryOne(tx *gorm.DB, conditions ...Condition[T]) (*T, error) + + // Get the list of models that match "conditions" inside transaction "tx" + Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) + + // Save model "model" inside transaction "tx" + Save(tx *gorm.DB, entity *T) error + + // Delete model "model" inside transaction "tx" + Delete(tx *gorm.DB, entity *T) error +} + +var ( + ErrMoreThanOneObjectFound = errors.New("found more that one object that meet the requested conditions") + ErrObjectNotFound = errors.New("no object exists that meets the requested conditions") +) + +// Implementation of the Generic CRUD Repository +type CRUDRepositoryImpl[T any, ID BadaasID] struct { + CRUDRepository[T, ID] +} + +// Constructor of the Generic CRUD Repository +func NewCRUDRepository[T any, ID BadaasID]() CRUDRepository[T, ID] { + return &CRUDRepositoryImpl[T, ID]{} +} + +// Create object "entity" inside transaction "tx" +func (repository *CRUDRepositoryImpl[T, ID]) Create(tx *gorm.DB, entity *T) error { + return tx.Create(entity).Error +} + +// Delete object "entity" inside transaction "tx" +func (repository *CRUDRepositoryImpl[T, ID]) Delete(tx *gorm.DB, entity *T) error { + return tx.Delete(entity).Error +} + +// Save object "entity" inside transaction "tx" +func (repository *CRUDRepositoryImpl[T, ID]) Save(tx *gorm.DB, entity *T) error { + return tx.Save(entity).Error +} + +// Get a model by its ID +func (repository *CRUDRepositoryImpl[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, error) { + var model T + + err := tx.First(&model, "id = ?", id).Error + if err != nil { + return nil, err + } + + return &model, nil +} + +// Get only one model that match "conditions" inside transaction "tx" +// or returns error if 0 or more than 1 are found. +func (repository *CRUDRepositoryImpl[T, ID]) QueryOne(tx *gorm.DB, conditions ...Condition[T]) (*T, error) { + entities, err := repository.Query(tx, conditions...) + if err != nil { + return nil, err + } + + switch { + case len(entities) == 1: + return entities[0], nil + case len(entities) == 0: + return nil, ErrObjectNotFound + default: + return nil, ErrMoreThanOneObjectFound + } +} + +// Get the list of models that match "conditions" inside transaction "tx" +func (repository *CRUDRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) { + initialTableName, err := getTableName(tx, *new(T)) + if err != nil { + return nil, err + } + + query := tx + for _, condition := range conditions { + query, err = condition.ApplyTo(query, initialTableName) + if err != nil { + return nil, err + } + } + + // execute query + var entities []*T + err = query.Find(&entities).Error + + return entities, err +} + +// Get the name of the table in "db" in which the data for "entity" is saved +// returns error is table name can not be found by gorm, +// probably because the type of "entity" is not registered using AddModel +func getTableName(db *gorm.DB, entity any) (string, error) { + schemaName, err := schema.Parse(entity, &sync.Map{}, db.NamingStrategy) + if err != nil { + return "", err + } + + return schemaName.Table, nil +} diff --git a/orm/crudService.go b/orm/crudService.go new file mode 100644 index 00000000..eeb409bc --- /dev/null +++ b/orm/crudService.go @@ -0,0 +1,54 @@ +package orm + +import ( + "gorm.io/gorm" +) + +// T can be any model whose identifier attribute is of type ID +type CRUDService[T any, ID BadaasID] interface { + // Get the model of type T that has the "id" + GetByID(id ID) (*T, error) + + // Get only one model that match "conditions" + // or return error if 0 or more than 1 are found. + QueryOne(conditions ...Condition[T]) (*T, error) + + // Get the list of models that match "conditions" + Query(conditions ...Condition[T]) ([]*T, error) +} + +// check interface compliance +var _ CRUDService[UUIDModel, UUID] = (*crudServiceImpl[UUIDModel, UUID])(nil) + +// Implementation of the CRUD Service +type crudServiceImpl[T any, ID BadaasID] struct { + CRUDService[T, ID] + db *gorm.DB + repository CRUDRepository[T, ID] +} + +func NewCRUDService[T any, ID BadaasID]( + db *gorm.DB, + repository CRUDRepository[T, ID], +) CRUDService[T, ID] { + return &crudServiceImpl[T, ID]{ + db: db, + repository: repository, + } +} + +// Get the model of type T that has the "id" +func (service *crudServiceImpl[T, ID]) GetByID(id ID) (*T, error) { + return service.repository.GetByID(service.db, id) +} + +// Get only one model that match "conditions" +// or return error if 0 or more than 1 are found. +func (service *crudServiceImpl[T, ID]) QueryOne(conditions ...Condition[T]) (*T, error) { + return service.repository.QueryOne(service.db, conditions...) +} + +// Get the list of models that match "conditions" +func (service *crudServiceImpl[T, ID]) Query(conditions ...Condition[T]) ([]*T, error) { + return service.repository.Query(service.db, conditions...) +} diff --git a/orm/orm.go b/orm/orm.go index ef137bda..0f525279 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -5,6 +5,11 @@ import ( "gorm.io/gorm" ) +func GetCRUD[T any, ID BadaasID](db *gorm.DB) (CRUDService[T, ID], CRUDRepository[T, ID]) { + repository := NewCRUDRepository[T, ID]() + return NewCRUDService(db, repository), repository +} + func autoMigrate(modelsLists [][]any, db *gorm.DB) error { if len(modelsLists) > 0 { allModels := pie.Flat(modelsLists) diff --git a/persistence/ModuleFx.go b/persistence/ModuleFx.go index 2a2a5706..4277f90f 100644 --- a/persistence/ModuleFx.go +++ b/persistence/ModuleFx.go @@ -1,14 +1,10 @@ package persistence import ( - "github.com/google/uuid" - "go.uber.org/fx" "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/gormdatabase" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/repository" ) // PersistanceModule for fx @@ -17,14 +13,10 @@ import ( // // - The database connection // - badaas-orm auto-migration -// - The repositories var PersistanceModule = fx.Module( "persistence", // Database connection fx.Provide(gormdatabase.SetupDatabaseConnection), // auto-migrate orm.AutoMigrate, - // repositories - fx.Provide(repository.NewCRUDRepository[models.Session, uuid.UUID]), - fx.Provide(repository.NewCRUDRepository[models.User, uuid.UUID]), ) diff --git a/persistence/models/Session.go b/persistence/models/Session.go index e6512491..0b92fee9 100644 --- a/persistence/models/Session.go +++ b/persistence/models/Session.go @@ -30,10 +30,3 @@ func (session *Session) IsExpired() bool { func (session *Session) CanBeRolled(rollInterval time.Duration) bool { return time.Now().After(session.ExpiresAt.Add(-rollInterval)) } - -// Return the pluralized table name -// -// Satisfie the Tabler interface -func (Session) TableName() string { - return "sessions" -} diff --git a/persistence/models/Session_test.go b/persistence/models/Session_test.go index 28176820..7d0746e9 100644 --- a/persistence/models/Session_test.go +++ b/persistence/models/Session_test.go @@ -34,7 +34,3 @@ func TestCanBeRolled(t *testing.T) { time.Sleep(400 * time.Millisecond) assert.True(t, sessionInstance.CanBeRolled(sessionDuration)) } - -func TestTableName(t *testing.T) { - assert.Equal(t, "sessions", models.Session{}.TableName()) -} diff --git a/persistence/models/Tabler.go b/persistence/models/Tabler.go deleted file mode 100644 index eac89ab8..00000000 --- a/persistence/models/Tabler.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -var ListOfTables = []any{ - User{}, - Session{}, -} - -// The interface "type" need to implement to be considered models -type Tabler interface { - // pluralized name - TableName() string -} diff --git a/persistence/models/User.go b/persistence/models/User.go index 9717cd05..750399cf 100644 --- a/persistence/models/User.go +++ b/persistence/models/User.go @@ -12,9 +12,9 @@ type User struct { Password []byte `gorm:"not null"` } -// Return the pluralized table name -// -// Satisfie the Tabler interface -func (User) TableName() string { - return "users" +func UserEmailCondition(email string) orm.Condition[User] { + return orm.WhereCondition[User]{ + Field: "email", + Value: email, + } } diff --git a/persistence/pagination/Page.go b/persistence/pagination/Page.go deleted file mode 100644 index 2d76a050..00000000 --- a/persistence/pagination/Page.go +++ /dev/null @@ -1,40 +0,0 @@ -package pagination - -import "github.com/ditrit/badaas/persistence/models" - -// A page hold ressources and data regarding the pagination -type Page[T models.Tabler] struct { - Ressources []*T `json:"ressources"` - // max d'element par page - Limit uint `json:"limit"` - // page courante - Offset uint `json:"offset"` - // total d'element en base - Total uint `json:"total"` - // total de pages - TotalPages uint `json:"totalPages"` - HasNextPage bool `json:"hasNextpage"` - HasPreviousPage bool `json:"hasPreviousPage"` - IsFirstPage bool `json:"isFirstPage"` - IsLastPage bool `json:"isLastPage"` - HasContent bool `json:"hasContent"` -} - -// Create a new page -func NewPage[T models.Tabler](records []*T, offset, size, nbElemTotal uint) *Page[T] { - nbPage := nbElemTotal / size - p := Page[T]{ - Ressources: records, - Limit: size, - Offset: offset, - Total: nbElemTotal, - TotalPages: nbPage, - - HasNextPage: nbElemTotal > (offset+1)*size, - HasPreviousPage: offset >= 1, - IsFirstPage: offset == 0, - IsLastPage: offset == (nbPage - 1), - HasContent: len(records) != 0, - } - return &p -} diff --git a/persistence/pagination/Page_test.go b/persistence/pagination/Page_test.go deleted file mode 100644 index 999e5201..00000000 --- a/persistence/pagination/Page_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package pagination_test - -import ( - "testing" - - "github.com/ditrit/badaas/persistence/pagination" - "github.com/stretchr/testify/assert" -) - -type Whatever struct { - DumbData int -} - -func (Whatever) TableName() string { - return "whatevers" -} - -var ( - // test fixture - ressources = []*Whatever{ - {10}, - {11}, - {12}, - {13}, - {14}, - {15}, - {16}, - {17}, - {18}, - {19}, - } -) - -func TestNewPage(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.ElementsMatch(t, ressources, p.Ressources) - assert.Equal(t, uint(10), p.Limit) - assert.Equal(t, uint(1), p.Offset) - assert.Equal(t, uint(5), p.TotalPages) -} - -func TestPageHasNextPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 4, // page 4: last page - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.HasNextPage) -} - -func TestPageHasNextPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.HasNextPage) -} - -func TestPageIsLastPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.IsLastPage) -} - -func TestPageIsLastPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 4, // page 4: last page - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.IsLastPage) -} - -func TestPageHasPreviousPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 0, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.HasPreviousPage) -} - -func TestPageHasPreviousPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.HasPreviousPage) -} - -func TestPageIsFirstPageFalse(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.IsFirstPage) -} - -func TestPageIsFirstPageTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 0, // page 0: first page - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.IsFirstPage) -} - -func TestPageHasContentFalse(t *testing.T) { - p := pagination.NewPage( - []*Whatever{}, // no content - 0, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.False(t, p.HasPreviousPage) -} - -func TestPageHasContentTrue(t *testing.T) { - p := pagination.NewPage( - ressources, - 1, // page 1 - 10, // 10 elems per page - 50, // 50 elem in total - ) - assert.True(t, p.HasContent) -} diff --git a/persistence/pagination/Paginator.go b/persistence/pagination/Paginator.go deleted file mode 100644 index e2811a72..00000000 --- a/persistence/pagination/Paginator.go +++ /dev/null @@ -1,36 +0,0 @@ -package pagination - -// Handle pagination -type Paginator interface { - Offset() uint - Limit() uint -} - -type paginatorImpl struct { - offset uint - limit uint -} - -// Constructor of Paginator -func NewPaginator(page, limit uint) Paginator { - if page == 0 { - page = 1 - } - if limit == 0 { - limit = 1 - } - return &paginatorImpl{ - offset: page, - limit: limit, - } -} - -// Return the page number -func (p *paginatorImpl) Offset() uint { - return p.offset -} - -// Return the max number of records for one page -func (p *paginatorImpl) Limit() uint { - return p.limit -} diff --git a/persistence/pagination/Paginator_test.go b/persistence/pagination/Paginator_test.go deleted file mode 100644 index 8fc58329..00000000 --- a/persistence/pagination/Paginator_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package pagination_test - -import ( - "testing" - - "github.com/ditrit/badaas/persistence/pagination" - "github.com/stretchr/testify/assert" -) - -func TestPaginator(t *testing.T) { - paginator := pagination.NewPaginator(uint(0), uint(12)) - assert.NotNil(t, paginator) - assert.Equal(t, uint(12), paginator.Limit()) - - paginator = pagination.NewPaginator(uint(2), uint(12)) - assert.NotNil(t, paginator) - assert.Equal(t, uint(12), paginator.Limit()) - - paginator = pagination.NewPaginator(uint(2), uint(0)) - assert.NotNil(t, paginator) - assert.Equal(t, uint(1), paginator.Limit()) -} diff --git a/persistence/repository/CRUDRepository.go b/persistence/repository/CRUDRepository.go deleted file mode 100644 index e3c45662..00000000 --- a/persistence/repository/CRUDRepository.go +++ /dev/null @@ -1,20 +0,0 @@ -package repository - -import ( - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" -) - -// Generic CRUD Repository -type CRUDRepository[T models.Tabler, ID any] interface { - Create(*T) httperrors.HTTPError - Delete(*T) httperrors.HTTPError - Save(*T) httperrors.HTTPError - GetByID(ID) (*T, httperrors.HTTPError) - GetAll(SortOption) ([]*T, httperrors.HTTPError) - Count(squirrel.Sqlizer) (uint, httperrors.HTTPError) - Find(squirrel.Sqlizer, pagination.Paginator, SortOption) (*pagination.Page[T], httperrors.HTTPError) - Transaction(fn func(CRUDRepository[T, ID]) (any, error)) (any, httperrors.HTTPError) -} diff --git a/persistence/repository/CRUDRepositoryImpl.go b/persistence/repository/CRUDRepositoryImpl.go deleted file mode 100644 index 3aa84d1b..00000000 --- a/persistence/repository/CRUDRepositoryImpl.go +++ /dev/null @@ -1,240 +0,0 @@ -package repository - -import ( - "fmt" - "net/http" - - "github.com/Masterminds/squirrel" - "go.uber.org/zap" - "gorm.io/gorm" - "gorm.io/gorm/clause" - - "github.com/ditrit/badaas/configuration" - "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/gormdatabase" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" -) - -// Return a database error -func DatabaseError(message string, golangError error) httperrors.HTTPError { - return httperrors.NewInternalServerError( - "database error", - message, - golangError, - ) -} - -// Implementation of the Generic CRUD Repository -type CRUDRepositoryImpl[T models.Tabler, ID any] struct { - CRUDRepository[T, ID] - gormDatabase *gorm.DB - logger *zap.Logger - paginationConfiguration configuration.PaginationConfiguration -} - -// Constructor of the Generic CRUD Repository -func NewCRUDRepository[T models.Tabler, ID any]( - database *gorm.DB, - logger *zap.Logger, - paginationConfiguration configuration.PaginationConfiguration, -) CRUDRepository[T, ID] { - return &CRUDRepositoryImpl[T, ID]{ - gormDatabase: database, - logger: logger, - paginationConfiguration: paginationConfiguration, - } -} - -// Run the function passed as parameter, if it returns the error and rollback the transaction. -// If no error is returned, it commits the transaction and return the interface{} value. -func (repository *CRUDRepositoryImpl[T, ID]) Transaction(transactionFunction func(CRUDRepository[T, ID]) (any, error)) (any, httperrors.HTTPError) { - transaction := repository.gormDatabase.Begin() - defer func() { - if recoveredError := recover(); recoveredError != nil { - transaction.Rollback() - } - }() - returnValue, err := transactionFunction(&CRUDRepositoryImpl[T, ID]{gormDatabase: transaction}) - if err != nil { - transaction.Rollback() - return nil, DatabaseError("transaction failed", err) - } - err = transaction.Commit().Error - if err != nil { - return nil, DatabaseError("transaction failed to commit", err) - } - return returnValue, nil -} - -// Create an entity of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Create(entity *T) httperrors.HTTPError { - err := repository.gormDatabase.Create(entity).Error - if err != nil { - if gormdatabase.IsDuplicateKeyError(err) { - return httperrors.NewHTTPError( - http.StatusConflict, - fmt.Sprintf("%T already exist in database", entity), - "", - nil, false) - } - return DatabaseError( - fmt.Sprintf("could not create %v in %s", entity, (*entity).TableName()), - err, - ) - - } - return nil -} - -// Delete an entity of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Delete(entity *T) httperrors.HTTPError { - err := repository.gormDatabase.Delete(entity).Error - if err != nil { - return DatabaseError( - fmt.Sprintf("could not delete %v in %s", entity, (*entity).TableName()), - err, - ) - } - return nil -} - -// Save an entity of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Save(entity *T) httperrors.HTTPError { - err := repository.gormDatabase.Save(entity).Error - if err != nil { - return DatabaseError( - fmt.Sprintf("could not save user %v in %s", entity, (*entity).TableName()), - err, - ) - } - return nil -} - -// Get an entity of a Model By ID -func (repository *CRUDRepositoryImpl[T, ID]) GetByID(id ID) (*T, httperrors.HTTPError) { - var entity T - transaction := repository.gormDatabase.First(&entity, "id = ?", id) - if transaction.Error != nil { - return nil, DatabaseError( - fmt.Sprintf("could not get %s by id %v", entity.TableName(), id), - transaction.Error, - ) - } - return &entity, nil -} - -// Get all entities of a Model -func (repository *CRUDRepositoryImpl[T, ID]) GetAll(sortOption SortOption) ([]*T, httperrors.HTTPError) { - var entities []*T - transaction := repository.gormDatabase - if sortOption != nil { - transaction = transaction.Order(buildClauseFromSortOption(sortOption)) - } - transaction.Find(&entities) - if transaction.Error != nil { - var emptyInstanceForError T - return nil, DatabaseError( - fmt.Sprintf("could not get %s", emptyInstanceForError.TableName()), - transaction.Error, - ) - } - return entities, nil -} - -// Build a gorm order clause from a SortOption -func buildClauseFromSortOption(sortOption SortOption) clause.OrderByColumn { - return clause.OrderByColumn{Column: clause.Column{Name: sortOption.Column()}, Desc: sortOption.Desc()} -} - -// Count entities of a models -func (repository *CRUDRepositoryImpl[T, ID]) Count(filters squirrel.Sqlizer) (uint, httperrors.HTTPError) { - whereClause, values, httpError := repository.compileSQL(filters) - if httpError != nil { - return 0, httpError - } - return repository.count(whereClause, values) -} - -// Count the number of record that match the where clause with the provided values on the db -func (repository *CRUDRepositoryImpl[T, ID]) count(whereClause string, values []interface{}) (uint, httperrors.HTTPError) { - var entity *T - var count int64 - transaction := repository.gormDatabase.Model(entity).Where(whereClause, values).Count(&count) - if transaction.Error != nil { - var emptyInstanceForError T - return 0, DatabaseError( - fmt.Sprintf("could not count data from %s with condition %q", emptyInstanceForError.TableName(), whereClause), - transaction.Error, - ) - } - return uint(count), nil -} - -// Find entities of a Model -func (repository *CRUDRepositoryImpl[T, ID]) Find( - filters squirrel.Sqlizer, - page pagination.Paginator, - sortOption SortOption, -) (*pagination.Page[T], httperrors.HTTPError) { - transaction := repository.gormDatabase.Begin() - defer func() { - if recoveredError := recover(); recoveredError != nil { - transaction.Rollback() - } - }() - var instances []*T - whereClause, values, httpError := repository.compileSQL(filters) - - if httpError != nil { - return nil, httpError - } - if page != nil { - transaction = transaction. - Offset( - int((page.Offset() - 1) * page.Limit()), - ). - Limit( - int(page.Limit()), - ) - } else { - page = pagination.NewPaginator(0, repository.paginationConfiguration.GetMaxElemPerPage()) - } - if sortOption != nil { - transaction = transaction.Order(buildClauseFromSortOption(sortOption)) - } - transaction = transaction.Where(whereClause, values...).Find(&instances) - if transaction.Error != nil { - transaction.Rollback() - var emptyInstanceForError T - return nil, DatabaseError( - fmt.Sprintf("could not get data from %s with condition %q", emptyInstanceForError.TableName(), whereClause), - transaction.Error, - ) - } - // Get Count - nbElem, httpError := repository.count(whereClause, values) - if httpError != nil { - transaction.Rollback() - return nil, httpError - } - err := transaction.Commit().Error - if err != nil { - return nil, DatabaseError( - "transaction failed to commit", err) - } - return pagination.NewPage(instances, page.Offset(), page.Limit(), nbElem), nil -} - -// compile the sql where clause -func (repository *CRUDRepositoryImpl[T, ID]) compileSQL(filters squirrel.Sqlizer) (string, []interface{}, httperrors.HTTPError) { - compiledSQLString, values, err := filters.ToSql() - if err != nil { - return "", []interface{}{}, httperrors.NewInternalServerError( - "sql error", - fmt.Sprintf("Failed to build the sql request (condition=%v)", filters), - err, - ) - } - return compiledSQLString, values, nil -} diff --git a/persistence/repository/CRUDRepositoryImpl_test.go b/persistence/repository/CRUDRepositoryImpl_test.go deleted file mode 100644 index 1dac5dbd..00000000 --- a/persistence/repository/CRUDRepositoryImpl_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package repository - -import ( - "testing" - - "github.com/Masterminds/squirrel" - mocks "github.com/ditrit/badaas/mocks/configuration" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -func TestDatabaseError(t *testing.T) { - err := DatabaseError("test err", assert.AnError) - require.NotNil(t, err) - assert.True(t, err.Log()) -} - -type dumbModel struct{} - -func (dumbModel) TableName() string { - return "dumb_models" -} - -func TestNewRepository(t *testing.T) { - paginationConfiguration := mocks.NewPaginationConfiguration(t) - dumbModelRepository := NewCRUDRepository[dumbModel, uint](nil, zap.L(), paginationConfiguration) - assert.NotNil(t, dumbModelRepository) -} - -func TestCompileSql_NoError(t *testing.T) { - paginationConfiguration := mocks.NewPaginationConfiguration(t) - dumbModelRepository := &CRUDRepositoryImpl[dumbModel, uint]{ - gormDatabase: nil, - logger: zap.L(), - paginationConfiguration: paginationConfiguration, - } - _, _, err := dumbModelRepository.compileSQL(squirrel.Eq{"name": "qsdqsd"}) - assert.Nil(t, err) -} - -func TestCompileSql_Err(t *testing.T) { - paginationConfiguration := mocks.NewPaginationConfiguration(t) - dumbModelRepository := &CRUDRepositoryImpl[dumbModel, uint]{ - gormDatabase: nil, - logger: zap.L(), - paginationConfiguration: paginationConfiguration, - } - _, _, err := dumbModelRepository.compileSQL(squirrel.GtOrEq{"name": nil}) - - assert.Error(t, err) -} diff --git a/persistence/repository/SortOption.go b/persistence/repository/SortOption.go deleted file mode 100644 index 0fda0c40..00000000 --- a/persistence/repository/SortOption.go +++ /dev/null @@ -1,27 +0,0 @@ -package repository - -type SortOption interface { - Column() string - Desc() bool -} - -// SortOption constructor -func NewSortOption(column string, desc bool) SortOption { - return &sortOption{column, desc} -} - -// Sorting option for the repository -type sortOption struct { - column string - desc bool -} - -// return the column name to sort on -func (sortOption *sortOption) Column() string { - return sortOption.column -} - -// return true for descending sort and false for ascending -func (sortOption *sortOption) Desc() bool { - return sortOption.desc -} diff --git a/persistence/repository/SortOption_test.go b/persistence/repository/SortOption_test.go deleted file mode 100644 index 89eba6c7..00000000 --- a/persistence/repository/SortOption_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package repository_test - -import ( - "testing" - - "github.com/ditrit/badaas/persistence/repository" - "github.com/stretchr/testify/assert" -) - -func TestNewSortOption(t *testing.T) { - sortOption := repository.NewSortOption("a", true) - assert.Equal(t, "a", sortOption.Column()) - assert.True(t, sortOption.Desc()) -} From e61ac473042d7dcd5f628359a6f42736a58379e7 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 31 Jul 2023 17:04:19 +0200 Subject: [PATCH 27/90] adapt existing services and controllers to use orm module --- controllers/basicAuth.go | 80 +++++++++--- controllers/basicAuth_test.go | 9 +- httperrors/httperrors.go | 30 +++-- .../services/sessionservice/SessionService.go | 38 +++--- mocks/services/userservice/UserService.go | 14 +-- services/ModuleFx.go | 3 + services/sessionservice/session.go | 82 ++++++------ services/sessionservice/session_test.go | 113 +++++++++-------- services/userservice/userservice.go | 56 +++++---- services/userservice/userservice_test.go | 118 +++++++----------- 10 files changed, 291 insertions(+), 252 deletions(-) diff --git a/controllers/basicAuth.go b/controllers/basicAuth.go index 3a65cfb5..7d9205fb 100644 --- a/controllers/basicAuth.go +++ b/controllers/basicAuth.go @@ -2,24 +2,27 @@ package controllers import ( "encoding/json" + "errors" + "fmt" "net/http" + "time" + + "go.uber.org/zap" "github.com/ditrit/badaas/httperrors" + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models/dto" "github.com/ditrit/badaas/services/sessionservice" "github.com/ditrit/badaas/services/userservice" - "go.uber.org/zap" ) -var ( - // Sent when the request is malformed - HTTPErrRequestMalformed httperrors.HTTPError = httperrors.NewHTTPError( - http.StatusBadRequest, - "Request malformed", - "The schema of the received data is not correct", - nil, - false, - ) +// HTTPErrRequestMalformed is sent when the request is malformed +var HTTPErrRequestMalformed httperrors.HTTPError = httperrors.NewHTTPError( + http.StatusBadRequest, + "Request malformed", + "The schema of the received data is not correct", + nil, + false, ) type BasicAuthenticationController interface { @@ -56,16 +59,32 @@ func (basicAuthController *basicAuthenticationController) BasicLoginHandler(w ht if err != nil { return nil, HTTPErrRequestMalformed } - user, herr := basicAuthController.userService.GetUser(loginJSONStruct) - if herr != nil { - return nil, herr + + user, err := basicAuthController.userService.GetUser(loginJSONStruct) + if err != nil { + if errors.Is(err, orm.ErrObjectNotFound) { + return nil, httperrors.NewErrorNotFound( + "user", + fmt.Sprintf("no user found with email %q", loginJSONStruct.Email), + ) + } else if errors.Is(err, userservice.ErrWrongPassword) { + return nil, httperrors.NewUnauthorizedError( + "wrong password", "the provided password is incorrect", + ) + } + + return nil, httperrors.NewDBError(err) } // On valid password, generate a session and return it's uuid to the client - herr = basicAuthController.sessionService.LogUserIn(user, w) + session, err := basicAuthController.sessionService.LogUserIn(user) + if err != nil { + return nil, httperrors.NewDBError(err) + } + + herr := createAndSetAccessTokenCookie(w, session.ID.String()) if herr != nil { return nil, herr - } return dto.DTOLoginSuccess{ @@ -77,5 +96,34 @@ func (basicAuthController *basicAuthenticationController) BasicLoginHandler(w ht // Log Out the user func (basicAuthController *basicAuthenticationController) Logout(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { - return nil, basicAuthController.sessionService.LogUserOut(sessionservice.GetSessionClaimsFromContext(r.Context()), w) + herr := basicAuthController.sessionService.LogUserOut(sessionservice.GetSessionClaimsFromContext(r.Context())) + if herr != nil { + return nil, herr + } + + herr = createAndSetAccessTokenCookie(w, "") + if herr != nil { + return nil, herr + } + + return nil, nil +} + +func createAndSetAccessTokenCookie(w http.ResponseWriter, sessionUUID string) httperrors.HTTPError { + accessToken := &http.Cookie{ + Name: "access_token", + Path: "/", + Value: sessionUUID, + HttpOnly: true, + SameSite: http.SameSiteNoneMode, // TODO change to http.SameSiteStrictMode in prod + Secure: false, // TODO change to true in prod + Expires: time.Now().Add(48 * time.Hour), + } + err := accessToken.Valid() + if err != nil { + return httperrors.NewInternalServerError("access token error", "unable to create access token", err) + } + + http.SetCookie(w, accessToken) + return nil } diff --git a/controllers/basicAuth_test.go b/controllers/basicAuth_test.go index 20d89e01..254bc011 100644 --- a/controllers/basicAuth_test.go +++ b/controllers/basicAuth_test.go @@ -4,6 +4,7 @@ import ( "net/http/httptest" "strings" "testing" + "time" "github.com/stretchr/testify/assert" "go.uber.org/zap" @@ -103,8 +104,8 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { Return(user, nil) sessionService := mocksSessionService.NewSessionService(t) sessionService. - On("LogUserIn", user, response). - Return(httperrors.AnError) + On("LogUserIn", user). + Return(nil, httperrors.AnError) controller := controllers.NewBasicAuthenticationController( logger, @@ -147,8 +148,8 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { Return(user, nil) sessionService := mocksSessionService.NewSessionService(t) sessionService. - On("LogUserIn", user, response). - Return(nil) + On("LogUserIn", user). + Return(models.NewSession(user.ID, time.Duration(5)), nil) controller := controllers.NewBasicAuthenticationController( logger, diff --git a/httperrors/httperrors.go b/httperrors/httperrors.go index 69c03c6f..0a09fb2b 100644 --- a/httperrors/httperrors.go +++ b/httperrors/httperrors.go @@ -5,23 +5,22 @@ import ( "fmt" "net/http" - "github.com/ditrit/badaas/persistence/models/dto" "go.uber.org/zap" -) -var ( - // AnError is an HTTPError instance useful for testing. If the code does not care - // about HTTPError specifics, and only needs to return the HTTPError for example, this - // HTTPError should be used to make the test code more readable. - AnError HTTPError = &HTTPErrorImpl{ - Status: -1, - Err: "TESTING ERROR", - Message: "USE ONLY FOR TESTING", - GolangError: nil, - toLog: true, - } + "github.com/ditrit/badaas/persistence/models/dto" ) +// AnError is an HTTPError instance useful for testing. If the code does not care +// about HTTPError specifics, and only needs to return the HTTPError for example, this +// HTTPError should be used to make the test code more readable. +var AnError HTTPError = &HTTPErrorImpl{ + Status: -1, + Err: "TESTING ERROR", + Message: "USE ONLY FOR TESTING", + GolangError: nil, + toLog: true, +} + type HTTPError interface { error @@ -115,6 +114,11 @@ func NewInternalServerError(errorName string, msg string, err error) HTTPError { ) } +// Constructor for an HttpError "DB Error", a internal server error produced by a query +func NewDBError(err error) HTTPError { + return NewInternalServerError("db error", "database query failed", err) +} + // A constructor for an HttpError "Unauthorized Error" func NewUnauthorizedError(errorName string, msg string) HTTPError { return NewHTTPError( diff --git a/mocks/services/sessionservice/SessionService.go b/mocks/services/sessionservice/SessionService.go index e7f0d108..fdf023f2 100644 --- a/mocks/services/sessionservice/SessionService.go +++ b/mocks/services/sessionservice/SessionService.go @@ -3,8 +3,6 @@ package mocks import ( - http "net/http" - httperrors "github.com/ditrit/badaas/httperrors" mock "github.com/stretchr/testify/mock" @@ -46,29 +44,39 @@ func (_m *SessionService) IsValid(sessionUUID orm.UUID) (bool, *sessionservice.S return r0, r1 } -// LogUserIn provides a mock function with given fields: user, response -func (_m *SessionService) LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError { - ret := _m.Called(user, response) +// LogUserIn provides a mock function with given fields: user +func (_m *SessionService) LogUserIn(user *models.User) (*models.Session, error) { + ret := _m.Called(user) - var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*models.User, http.ResponseWriter) httperrors.HTTPError); ok { - r0 = rf(user, response) + var r0 *models.Session + var r1 error + if rf, ok := ret.Get(0).(func(*models.User) (*models.Session, error)); ok { + return rf(user) + } + if rf, ok := ret.Get(0).(func(*models.User) *models.Session); ok { + r0 = rf(user) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(httperrors.HTTPError) + r0 = ret.Get(0).(*models.Session) } } - return r0 + if rf, ok := ret.Get(1).(func(*models.User) error); ok { + r1 = rf(user) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -// LogUserOut provides a mock function with given fields: sessionClaims, response -func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims, response http.ResponseWriter) httperrors.HTTPError { - ret := _m.Called(sessionClaims, response) +// LogUserOut provides a mock function with given fields: sessionClaims +func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims) httperrors.HTTPError { + ret := _m.Called(sessionClaims) var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(*sessionservice.SessionClaims, http.ResponseWriter) httperrors.HTTPError); ok { - r0 = rf(sessionClaims, response) + if rf, ok := ret.Get(0).(func(*sessionservice.SessionClaims) httperrors.HTTPError); ok { + r0 = rf(sessionClaims) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(httperrors.HTTPError) diff --git a/mocks/services/userservice/UserService.go b/mocks/services/userservice/UserService.go index bd6bb257..6ecc1e6b 100644 --- a/mocks/services/userservice/UserService.go +++ b/mocks/services/userservice/UserService.go @@ -3,9 +3,7 @@ package mocks import ( - httperrors "github.com/ditrit/badaas/httperrors" dto "github.com/ditrit/badaas/persistence/models/dto" - mock "github.com/stretchr/testify/mock" models "github.com/ditrit/badaas/persistence/models" @@ -17,12 +15,12 @@ type UserService struct { } // GetUser provides a mock function with given fields: _a0 -func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, httperrors.HTTPError) { +func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, error) { ret := _m.Called(_a0) var r0 *models.User - var r1 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(dto.UserLoginDTO) (*models.User, httperrors.HTTPError)); ok { + var r1 error + if rf, ok := ret.Get(0).(func(dto.UserLoginDTO) (*models.User, error)); ok { return rf(_a0) } if rf, ok := ret.Get(0).(func(dto.UserLoginDTO) *models.User); ok { @@ -33,12 +31,10 @@ func (_m *UserService) GetUser(_a0 dto.UserLoginDTO) (*models.User, httperrors.H } } - if rf, ok := ret.Get(1).(func(dto.UserLoginDTO) httperrors.HTTPError); ok { + if rf, ok := ret.Get(1).(func(dto.UserLoginDTO) error); ok { r1 = rf(_a0) } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(httperrors.HTTPError) - } + r1 = ret.Error(1) } return r0, r1 diff --git a/services/ModuleFx.go b/services/ModuleFx.go index 2ac31c8d..0259a5b8 100644 --- a/services/ModuleFx.go +++ b/services/ModuleFx.go @@ -13,6 +13,9 @@ var AuthServiceModule = fx.Module( "authService", // models fx.Provide(getAuthModels), + // repositories + fx.Provide(orm.NewCRUDRepository[models.Session, orm.UUID]), + fx.Provide(orm.NewCRUDRepository[models.User, orm.UUID]), // services fx.Provide(userservice.NewUserService), diff --git a/services/sessionservice/session.go b/services/sessionservice/session.go index 142c8c18..bcf67cb6 100644 --- a/services/sessionservice/session.go +++ b/services/sessionservice/session.go @@ -2,20 +2,16 @@ package sessionservice import ( "fmt" - "net/http" "sync" "time" "go.uber.org/zap" "gorm.io/gorm" - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/configuration" "github.com/ditrit/badaas/httperrors" "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/repository" ) // Errors @@ -29,9 +25,10 @@ var ( // SessionService handle sessions type SessionService interface { IsValid(sessionUUID orm.UUID) (bool, *SessionClaims) + // TODO services should not work with httperrors RollSession(orm.UUID) httperrors.HTTPError - LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError - LogUserOut(sessionClaims *SessionClaims, response http.ResponseWriter) httperrors.HTTPError + LogUserIn(user *models.User) (*models.Session, error) + LogUserOut(sessionClaims *SessionClaims) httperrors.HTTPError } // Check interface compliance @@ -39,17 +36,18 @@ var _ SessionService = (*sessionServiceImpl)(nil) // The SessionService concrete interface type sessionServiceImpl struct { - sessionRepository repository.CRUDRepository[models.Session, orm.UUID] + sessionRepository orm.CRUDRepository[models.Session, orm.UUID] cache map[orm.UUID]*models.Session mutex sync.Mutex logger *zap.Logger sessionConfiguration configuration.SessionConfiguration + db *gorm.DB } // The SessionService constructor func NewSessionService( logger *zap.Logger, - sessionRepository repository.CRUDRepository[models.Session, orm.UUID], + sessionRepository orm.CRUDRepository[models.Session, orm.UUID], sessionConfiguration configuration.SessionConfiguration, db *gorm.DB, ) SessionService { @@ -58,6 +56,7 @@ func NewSessionService( logger: logger, sessionRepository: sessionRepository, sessionConfiguration: sessionConfiguration, + db: db, } sessionService.init() return sessionService @@ -84,22 +83,23 @@ func (sessionService *sessionServiceImpl) get(sessionUUID orm.UUID) *models.Sess return session } - sessionsFoundWithUUID, databaseError := sessionService.sessionRepository.Find(squirrel.Eq{"uuid": sessionUUID.String()}, nil, nil) - if databaseError != nil { + session, err := sessionService.sessionRepository.GetByID( + sessionService.db, + sessionUUID, + ) + if err != nil { return nil } - if !sessionsFoundWithUUID.HasContent { - return nil // no sessions found in database - } - return sessionsFoundWithUUID.Ressources[0] + + return session } // Add a session to the cache -func (sessionService *sessionServiceImpl) add(session *models.Session) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) add(session *models.Session) error { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() - err := sessionService.sessionRepository.Create(session) + err := sessionService.sessionRepository.Create(sessionService.db, session) if err != nil { return err } @@ -128,7 +128,8 @@ func (sessionService *sessionServiceImpl) init() { func (sessionService *sessionServiceImpl) pullFromDB() { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() - sessionsFromDatabase, err := sessionService.sessionRepository.GetAll(nil) + + sessionsFromDatabase, err := sessionService.sessionRepository.Query(sessionService.db) if err != nil { panic(err) } @@ -148,11 +149,12 @@ func (sessionService *sessionServiceImpl) pullFromDB() { func (sessionService *sessionServiceImpl) removeExpired() { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + var i int for sessionUUID, session := range sessionService.cache { if session.IsExpired() { // Delete the session in the database - err := sessionService.sessionRepository.Delete(session) + err := sessionService.sessionRepository.Delete(sessionService.db, session) if err != nil { panic(err) } @@ -173,8 +175,9 @@ func (sessionService *sessionServiceImpl) removeExpired() { func (sessionService *sessionServiceImpl) delete(session *models.Session) httperrors.HTTPError { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + sessionUUID := session.ID - err := sessionService.sessionRepository.Delete(session) + err := sessionService.sessionRepository.Delete(sessionService.db, session) if err != nil { return httperrors.NewInternalServerError( "session error", @@ -195,64 +198,51 @@ func (sessionService *sessionServiceImpl) RollSession(sessionUUID orm.UUID) http // no session to roll, no error return nil } + if session.IsExpired() { return HERRSessionExpired } + if session.CanBeRolled(rollInterval) { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() + session.ExpiresAt = session.ExpiresAt.Add(sessionDuration) - herr := sessionService.sessionRepository.Save(session) - if herr != nil { - return herr + err := sessionService.sessionRepository.Save(sessionService.db, session) + if err != nil { + return httperrors.NewDBError(err) } + sessionService.logger.Warn("Rolled session", zap.String("userID", session.UserID.String()), zap.String("sessionID", session.ID.String())) } + return nil } // Log in a user -func (sessionService *sessionServiceImpl) LogUserIn(user *models.User, response http.ResponseWriter) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) LogUserIn(user *models.User) (*models.Session, error) { sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() session := models.NewSession(user.ID, sessionDuration) err := sessionService.add(session) if err != nil { - return err + return nil, err } - CreateAndSetAccessTokenCookie(response, session.ID.String()) - return nil + return session, nil } // Log out a user. -func (sessionService *sessionServiceImpl) LogUserOut(sessionClaims *SessionClaims, response http.ResponseWriter) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) LogUserOut(sessionClaims *SessionClaims) httperrors.HTTPError { session := sessionService.get(sessionClaims.SessionUUID) if session == nil { return httperrors.NewUnauthorizedError("Authentication Error", "not authenticated") } + err := sessionService.delete(session) if err != nil { return err } - CreateAndSetAccessTokenCookie(response, "") - return nil -} -// Create an access token and send it in a cookie -func CreateAndSetAccessTokenCookie(w http.ResponseWriter, sessionUUID string) { - accessToken := &http.Cookie{ - Name: "access_token", - Path: "/", - Value: sessionUUID, - HttpOnly: true, - SameSite: http.SameSiteNoneMode, // TODO change to http.SameSiteStrictMode in prod - Secure: false, // TODO change to true in prod - Expires: time.Now().Add(48 * time.Hour), - } - err := accessToken.Valid() - if err != nil { - panic(err) - } - http.SetCookie(w, accessToken) + return nil } diff --git a/services/sessionservice/session_test.go b/services/sessionservice/session_test.go index d676d0d8..42bf0b02 100644 --- a/services/sessionservice/session_test.go +++ b/services/sessionservice/session_test.go @@ -1,78 +1,82 @@ package sessionservice import ( - "net/http/httptest" + "errors" "testing" "time" - "github.com/Masterminds/squirrel" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + "gorm.io/gorm" "github.com/ditrit/badaas/httperrors" - configurationmocks "github.com/ditrit/badaas/mocks/configuration" - repositorymocks "github.com/ditrit/badaas/mocks/persistence/repository" + configurationMocks "github.com/ditrit/badaas/mocks/configuration" + ormMocks "github.com/ditrit/badaas/mocks/orm" "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/pagination" ) -func TestLogInUser(t *testing.T) { - sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) - sessionRepositoryMock.On("Create", mock.Anything).Return(nil) - sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) - response := httptest.NewRecorder() - user := &models.User{ - Username: "bob", - Email: "bob@email.com", - } - err := service.LogUserIn(user, response) - require.NoError(t, err) - assert.Len(t, service.cache, 1) - assert.Equal(t, 1, logs.Len()) - log := logs.All()[0] - assert.Equal(t, "Added session", log.Message) - require.Len(t, log.Context, 1) -} +var gormDB *gorm.DB // make values for test func setupTest( t *testing.T, ) ( - *repositorymocks.CRUDRepository[models.Session, orm.UUID], + *ormMocks.CRUDRepository[models.Session, orm.UUID], *sessionServiceImpl, *observer.ObservedLogs, - *configurationmocks.SessionConfiguration, + *configurationMocks.SessionConfiguration, ) { core, logs := observer.New(zap.DebugLevel) logger := zap.New(core) - sessionRepositoryMock := repositorymocks.NewCRUDRepository[models.Session, orm.UUID](t) - sessionConfiguration := configurationmocks.NewSessionConfiguration(t) + sessionRepositoryMock := ormMocks.NewCRUDRepository[models.Session, orm.UUID](t) + sessionConfiguration := configurationMocks.NewSessionConfiguration(t) service := &sessionServiceImpl{ sessionRepository: sessionRepositoryMock, logger: logger, cache: make(map[orm.UUID]*models.Session), sessionConfiguration: sessionConfiguration, + db: gormDB, } return sessionRepositoryMock, service, logs, sessionConfiguration } +func TestLogInUser(t *testing.T) { + sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) + sessionRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) + + sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) + user := &models.User{ + Username: "bob", + Email: "bob@email.com", + } + _, err := service.LogUserIn(user) + require.NoError(t, err) + assert.Len(t, service.cache, 1) + assert.Equal(t, 1, logs.Len()) + log := logs.All()[0] + assert.Equal(t, "Added session", log.Message) + require.Len(t, log.Context, 1) +} + func TestLogInUserDbError(t *testing.T) { sessionRepositoryMock, service, logs, sessionConfigurationMock := setupTest(t) - sessionRepositoryMock.On("Create", mock.Anything).Return(httperrors.NewInternalServerError("db err", "nil", nil)) + sessionRepositoryMock. + On("Create", gormDB, mock.Anything). + Return(errors.New("db err")) + sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) - response := httptest.NewRecorder() user := &models.User{ Username: "bob", Email: "bob@email.com", } - err := service.LogUserIn(user, response) + _, err := service.LogUserIn(user) require.Error(t, err) assert.Len(t, service.cache, 0) assert.Equal(t, 0, logs.Len()) @@ -80,7 +84,7 @@ func TestLogInUserDbError(t *testing.T) { func TestIsValid(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Create", mock.Anything).Return(nil) + sessionRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) uuidSample := orm.NewUUID() session := &models.Session{ UUIDModel: orm.UUIDModel{ @@ -104,8 +108,8 @@ func TestIsValid(t *testing.T) { func TestIsValid_SessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock. - On("Find", mock.Anything, mock.Anything, mock.Anything). - Return(pagination.NewPage([]*models.Session{}, 0, 125, 1236), nil) + On("GetByID", gormDB, mock.Anything). + Return(nil, errors.New("not-found")) uuidSample := orm.NewUUID() isValid, _ := service.IsValid(uuidSample) require.False(t, isValid) @@ -113,8 +117,7 @@ func TestIsValid_SessionNotFound(t *testing.T) { func TestLogOutUser(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Delete", mock.Anything).Return(nil) - response := httptest.NewRecorder() + sessionRepositoryMock.On("Delete", gormDB, mock.Anything).Return(nil) uuidSample := orm.NewUUID() session := &models.Session{ UUIDModel: orm.UUIDModel{ @@ -124,16 +127,18 @@ func TestLogOutUser(t *testing.T) { ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session - err := service.LogUserOut(makeSessionClaims(session), response) + err := service.LogUserOut(makeSessionClaims(session)) require.NoError(t, err) assert.Len(t, service.cache, 0) } func TestLogOutUserDbError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Delete", mock.Anything).Return(httperrors.NewInternalServerError("db errors", "oh we failed to delete the session", nil)) - response := httptest.NewRecorder() + sessionRepositoryMock. + On("Delete", gormDB, mock.Anything). + Return(errors.New("db errors")) uuidSample := orm.NewUUID() + session := &models.Session{ UUIDModel: orm.UUIDModel{ ID: uuidSample, @@ -142,7 +147,7 @@ func TestLogOutUserDbError(t *testing.T) { ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session - err := service.LogUserOut(makeSessionClaims(session), response) + err := service.LogUserOut(makeSessionClaims(session)) require.Error(t, err) assert.Len(t, service.cache, 1) } @@ -150,9 +155,8 @@ func TestLogOutUserDbError(t *testing.T) { func TestLogOutUser_SessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock. - On("Find", mock.Anything, nil, nil). - Return(nil, httperrors.NewInternalServerError("db errors", "oh we failed to delete the session", nil)) - response := httptest.NewRecorder() + On("GetByID", gormDB, mock.Anything). + Return(nil, errors.New("not-found")) uuidSample := orm.NewUUID() session := &models.Session{ @@ -165,14 +169,14 @@ func TestLogOutUser_SessionNotFound(t *testing.T) { service.cache[uuidSample] = session sessionClaims := makeSessionClaims(session) sessionClaims.SessionUUID = orm.NilUUID - err := service.LogUserOut(sessionClaims, response) + err := service.LogUserOut(sessionClaims) require.Error(t, err) assert.Len(t, service.cache, 1) } func TestRollSession(t *testing.T) { sessionRepositoryMock, service, _, sessionConfigurationMock := setupTest(t) - sessionRepositoryMock.On("Save", mock.Anything).Return(nil) + sessionRepositoryMock.On("Save", gormDB, mock.Anything).Return(nil) sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) @@ -226,7 +230,11 @@ func TestRollSession_falseUUID(t *testing.T) { ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session - repoSession.On("Find", mock.Anything, nil, nil).Return(pagination.NewPage([]*models.Session{}, 0, 2, 5), nil) + + repoSession. + On("GetByID", gormDB, mock.Anything). + Return(nil, errors.New("not-found")) + err := service.RollSession(orm.NewUUID()) require.NoError(t, err) } @@ -234,9 +242,8 @@ func TestRollSession_falseUUID(t *testing.T) { func TestRollSession_sessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, sessionConfigurationMock := setupTest(t) sessionRepositoryMock. - On("Find", squirrel.Eq{"uuid": "00000000-0000-0000-0000-000000000000"}, nil, nil). - Return( - pagination.NewPage([]*models.Session{}, 0, 10, 0), nil) + On("GetByID", gormDB, orm.NilUUID). + Return(nil, errors.New("not-found")) sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) @@ -255,7 +262,7 @@ func Test_pullFromDB(t *testing.T) { UserID: orm.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } - sessionRepositoryMock.On("GetAll", nil).Return([]*models.Session{session}, nil) + sessionRepositoryMock.On("Query", gormDB).Return([]*models.Session{session}, nil) service.pullFromDB() assert.Len(t, service.cache, 1) @@ -269,7 +276,7 @@ func Test_pullFromDB(t *testing.T) { func Test_pullFromDB_repoError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("GetAll", nil).Return(nil, httperrors.AnError) + sessionRepositoryMock.On("Query", gormDB).Return(nil, httperrors.AnError) assert.PanicsWithError(t, httperrors.AnError.Error(), func() { service.pullFromDB() }) } @@ -284,7 +291,7 @@ func Test_removeExpired(t *testing.T) { ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. - On("Delete", session). + On("Delete", gormDB, session). Return(nil) service.cache[uuidSample] = session @@ -309,7 +316,7 @@ func Test_removeExpired_RepositoryError(t *testing.T) { ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. - On("Delete", session). + On("Delete", gormDB, session). Return(httperrors.AnError) service.cache[uuidSample] = session @@ -327,8 +334,8 @@ func Test_get(t *testing.T) { ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. - On("Find", mock.Anything, nil, nil). - Return(pagination.NewPage([]*models.Session{session}, 0, 12, 13), nil) + On("GetByID", gormDB, mock.Anything). + Return(session, nil) sessionFound := service.get(uuidSample) assert.Equal(t, sessionFound, session) diff --git a/services/userservice/userservice.go b/services/userservice/userservice.go index 3f3da399..821dbe44 100644 --- a/services/userservice/userservice.go +++ b/services/userservice/userservice.go @@ -1,42 +1,47 @@ package userservice import ( + "errors" "fmt" - "github.com/Masterminds/squirrel" - "github.com/ditrit/badaas/httperrors" + "go.uber.org/zap" + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" - "github.com/ditrit/badaas/persistence/repository" "github.com/ditrit/badaas/services/auth/protocols/basicauth" validator "github.com/ditrit/badaas/validators" - "github.com/google/uuid" - "go.uber.org/zap" ) // UserService provide functions related to Users type UserService interface { NewUser(username, email, password string) (*models.User, error) - GetUser(dto.UserLoginDTO) (*models.User, httperrors.HTTPError) + GetUser(dto.UserLoginDTO) (*models.User, error) } +var ErrWrongPassword = errors.New("password is incorrect") + // Check interface compliance var _ UserService = (*userServiceImpl)(nil) // The UserService concrete implementation type userServiceImpl struct { - userRepository repository.CRUDRepository[models.User, uuid.UUID] + userRepository orm.CRUDRepository[models.User, orm.UUID] logger *zap.Logger + db *gorm.DB } // UserService constructor func NewUserService( logger *zap.Logger, - userRepository repository.CRUDRepository[models.User, uuid.UUID], + userRepository orm.CRUDRepository[models.User, orm.UUID], + db *gorm.DB, ) UserService { return &userServiceImpl{ logger: logger, userRepository: userRepository, + db: db, } } @@ -46,37 +51,40 @@ func (userService *userServiceImpl) NewUser(username, email, password string) (* if err != nil { return nil, fmt.Errorf("the provided email is not valid") } + u := &models.User{ Username: username, Email: sanitizedEmail, Password: basicauth.SaltAndHashPassword(password), } - httpError := userService.userRepository.Create(u) - if httpError != nil { - return nil, httpError + err = userService.userRepository.Create(userService.db, u) + if err != nil { + return nil, err } - userService.logger.Info("Successfully created a new user", - zap.String("email", sanitizedEmail), zap.String("username", username)) + + userService.logger.Info( + "Successfully created a new user", + zap.String("email", sanitizedEmail), + zap.String("username", username), + ) return u, nil } // Get user if the email and password provided are correct, return an error if not. -func (userService *userServiceImpl) GetUser(userLoginDTO dto.UserLoginDTO) (*models.User, httperrors.HTTPError) { - users, herr := userService.userRepository.Find(squirrel.Eq{"email": userLoginDTO.Email}, nil, nil) - if herr != nil { - return nil, herr - } - if !users.HasContent { - return nil, httperrors.NewErrorNotFound("user", - fmt.Sprintf("no user found with email %q", userLoginDTO.Email)) +func (userService *userServiceImpl) GetUser(userLoginDTO dto.UserLoginDTO) (*models.User, error) { + user, err := userService.userRepository.QueryOne( + userService.db, + models.UserEmailCondition(userLoginDTO.Email), + ) + if err != nil { + return nil, err } - user := users.Ressources[0] - // Check password if !basicauth.CheckUserPassword(user.Password, userLoginDTO.Password) { - return nil, httperrors.NewUnauthorizedError("wrong password", "the provided password is incorrect") + return nil, ErrWrongPassword } + return user, nil } diff --git a/services/userservice/userservice_test.go b/services/userservice/userservice_test.go index df435ed8..eb65ff63 100644 --- a/services/userservice/userservice_test.go +++ b/services/userservice/userservice_test.go @@ -1,31 +1,35 @@ package userservice_test import ( + "errors" "testing" - "github.com/ditrit/badaas/httperrors" - repositorymocks "github.com/ditrit/badaas/mocks/persistence/repository" - "github.com/ditrit/badaas/persistence/models" - "github.com/ditrit/badaas/persistence/models/dto" - "github.com/ditrit/badaas/persistence/pagination" - "github.com/ditrit/badaas/services/userservice" - "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + "gorm.io/gorm" + + ormMocks "github.com/ditrit/badaas/mocks/orm" + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/persistence/models/dto" + "github.com/ditrit/badaas/services/userservice" ) +var gormDB *gorm.DB + func TestNewUserService(t *testing.T) { // creating logger observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userRespositoryMock.On("Create", mock.Anything).Return(nil) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) + + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@email.com", "1234") assert.NoError(t, err) assert.NotNil(t, user) @@ -49,13 +53,13 @@ func TestNewUserServiceDatabaseError(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userRespositoryMock.On( - "Create", mock.Anything, + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock.On( + "Create", gormDB, mock.Anything, ).Return( - httperrors.NewInternalServerError("database error", "test error", nil), + errors.New("database error"), ) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@email.com", "1234") assert.Error(t, err) assert.Nil(t, user) @@ -69,9 +73,9 @@ func TestNewUserServiceEmailNotValid(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@", "1234") assert.Error(t, err) assert.Nil(t, user) @@ -85,20 +89,19 @@ func TestGetUser(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) - userRespositoryMock.On( - "Create", mock.Anything, - ).Return( - nil, - ) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) + userRepositoryMock.On( + "Create", gormDB, mock.Anything, + ).Return(nil) + user, err := userService.NewUser("bob", "bob@email.com", "1234") require.NoError(t, err) - userRespositoryMock.On( - "Find", mock.Anything, nil, nil, + userRepositoryMock.On( + "QueryOne", gormDB, models.UserEmailCondition("bob@email.com"), ).Return( - pagination.NewPage([]*models.User{user}, 1, 10, 50), + user, nil, ) @@ -121,13 +124,13 @@ func TestGetUserNoUserFound(t *testing.T) { observedZapCore, _ := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) - userRespositoryMock.On( - "Find", mock.Anything, nil, nil, + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) + userRepositoryMock.On( + "QueryOne", gormDB, models.UserEmailCondition("bobnotfound@email.com"), ).Return( - nil, - httperrors.NewErrorNotFound("user", "user with email bobnotfound@email.com"), + &models.User{}, + orm.ErrObjectNotFound, ) userFound, err := userService.GetUser(dto.UserLoginDTO{Email: "bobnotfound@email.com", Password: "1234"}) @@ -136,53 +139,24 @@ func TestGetUserNoUserFound(t *testing.T) { } // Check what happen if the pass word is not correct -func TestGetUserNotCorrect(t *testing.T) { +func TestGetUserWrongPassword(t *testing.T) { // creating logger observedZapCore, _ := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRespositoryMock := repositorymocks.NewCRUDRepository[models.User, uuid.UUID](t) - userRespositoryMock.On( - "Create", mock.Anything, - ).Return( - nil, - ) - userService := userservice.NewUserService(observedLogger, userRespositoryMock) - user, err := userService.NewUser("bob", "bob@email.com", "1234") + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock.On( + "Create", gormDB, mock.Anything, + ).Return(nil) - require.NoError(t, err) - userRespositoryMock.On( - "Find", mock.Anything, nil, nil, - ).Return( - pagination.NewPage([]*models.User{user}, 1, 10, 50), - nil, - ) - - userFound, err := userService.GetUser(dto.UserLoginDTO{Email: "bob@email.com", Password: " Date: Mon, 31 Jul 2023 17:06:27 +0200 Subject: [PATCH 28/90] add integration tests --- .github/workflows/CI.yml | 41 +- CONTRIBUTING.md | 14 +- Makefile | 11 +- go.mod | 12 +- go.sum | 13 +- go.work.sum | 13 + sonar-project.properties | 4 +- test_e2e/go.mod | 66 +-- test_e2e/go.sum | 178 ++++---- test_e2e/setup.go | 21 +- testintegration/asserts.go | 33 ++ .../conditions/bicycle_conditions.go | 53 +++ .../conditions/brand_conditions.go | 40 ++ testintegration/conditions/city_conditions.go | 46 ++ .../conditions/company_conditions.go | 47 +++ .../conditions/country_conditions.go | 54 +++ .../conditions/employee_conditions.go | 53 +++ testintegration/conditions/orm.go | 3 + .../conditions/person_conditions.go | 40 ++ .../conditions/phone_conditions.go | 53 +++ .../conditions/product_conditions.go | 89 ++++ testintegration/conditions/sale_conditions.go | 72 ++++ .../conditions/seller_conditions.go | 46 ++ testintegration/crudRepository.go | 87 ++++ testintegration/crudServiceCommon.go | 127 ++++++ testintegration/db_models.go | 45 ++ testintegration/join_conditions_test.go | 337 +++++++++++++++ testintegration/models/models.go | 179 ++++++++ testintegration/orm_test.go | 97 +++++ testintegration/where_conditions_test.go | 395 ++++++++++++++++++ 30 files changed, 2105 insertions(+), 164 deletions(-) create mode 100644 testintegration/asserts.go create mode 100644 testintegration/conditions/bicycle_conditions.go create mode 100644 testintegration/conditions/brand_conditions.go create mode 100644 testintegration/conditions/city_conditions.go create mode 100644 testintegration/conditions/company_conditions.go create mode 100644 testintegration/conditions/country_conditions.go create mode 100644 testintegration/conditions/employee_conditions.go create mode 100644 testintegration/conditions/orm.go create mode 100644 testintegration/conditions/person_conditions.go create mode 100644 testintegration/conditions/phone_conditions.go create mode 100644 testintegration/conditions/product_conditions.go create mode 100644 testintegration/conditions/sale_conditions.go create mode 100644 testintegration/conditions/seller_conditions.go create mode 100644 testintegration/crudRepository.go create mode 100644 testintegration/crudServiceCommon.go create mode 100644 testintegration/db_models.go create mode 100644 testintegration/join_conditions_test.go create mode 100644 testintegration/models/models.go create mode 100644 testintegration/orm_test.go create mode 100644 testintegration/where_conditions_test.go diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 38228fb2..ae24c197 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -54,7 +54,7 @@ jobs: - name: Install gotestsum run: go install gotest.tools/gotestsum@latest - name: Run unit tests - run: gotestsum --junitfile unit-tests.xml ./... -coverpkg=./... -coverprofile=coverage_unit.out + run: gotestsum --junitfile unit-tests.xml $(go list ./... | grep -v testintegration) -coverpkg=./... -coverprofile=coverage_unit.out - uses: actions/upload-artifact@v3 with: name: coverage_unit @@ -67,6 +67,38 @@ jobs: path: unit-tests.xml # Path to test results reporter: java-junit # Format of test results + integration-tests: + name: Integration tests + needs: [unit-tests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-go@v3 + with: + go-version: '^1.18' + cache: true + - name: Install gotestsum + run: go install gotest.tools/gotestsum@latest + - name: Start containers + run: docker compose -f "docker/test_db/docker-compose.yml" up -d + - name: Run test + run: gotestsum --junitfile integration-tests.xml ./testintegration -coverpkg=./... -coverprofile=coverage_int.out + - name: Test Report + uses: dorny/test-reporter@v1 + if: always() # run this step even if previous steps failed + with: + name: Integration Tests Report # Name of the check run which will be created + path: integration-tests.xml # Path to test results + reporter: java-junit # Format of test results + - uses: actions/upload-artifact@v3 + with: + name: coverage_int + path: coverage_int.out + - name: Stop containers + run: docker stop badaas-test-db + e2e-tests: name: E2E Tests needs: [unit-tests, check-style] @@ -101,7 +133,7 @@ jobs: sonarcloud: name: SonarCloud - needs: [unit-tests, check-style] + needs: [check-style, integration-tests] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -112,6 +144,11 @@ jobs: with: name: coverage_unit path: coverage_unit.out + - name: Download int tests line coverage report + uses: actions/download-artifact@v3 + with: + name: coverage_int + path: coverage_int.out - name: SonarCloud Scan uses: sonarsource/sonarcloud-github-action@master env: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b758d98..60409007 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,8 @@ - [Tests](#tests) - [Dependencies](#dependencies) - [Unit tests](#unit-tests) - - [Feature tests (of end to end tests)](#feature-tests-of-end-to-end-tests) + - [Integration tests](#integration-tests) + - [Feature tests (of end to end tests)](#feature-tests-or-end-to-end-tests) - [Logger](#logger) - [Directory structure](#directory-structure) - [Git](#git) @@ -27,7 +28,15 @@ To run them, please run: make test_unit ``` -### Feature tests (of end to end tests) +### Integration tests + +Integration tests have a database and the dependency injection system. + +```sh +make test_integration +``` + +### Feature tests (or end to end tests) We use docker to run a Badaas instance in combination with one node of CockroachDB. @@ -53,6 +62,7 @@ This is the directory structure we use for the project: - `test_db/` : Contains the Dockerfile to build a development/test version of CockroachDB. - `test_api/` : Contains files to build a development/test version of the api. - `test_e2e/`: Contains all the feature and steps for e2e tests. +- `testintegration/`: Contains all the integration tests. - `logger/` *(Go code)*: Contains the logger creation logic. Please don't call it from your own services and code, use the dependency injection system. - `persistance/` *(Go code)*: - `gormdatabase/` *(Go code)*: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. diff --git a/Makefile b/Makefile index 0d9652fc..2d29efc2 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +PATHS = $(shell go list ./... | grep -v testintegration) + install_dependencies: go install gotest.tools/gotestsum@latest go install github.com/vektra/mockery/v2@v2.20.0 @@ -7,7 +9,12 @@ lint: golangci-lint run test_unit: - gotestsum --format pkgname ./... + gotestsum --format pkgname $(PATHS) + +test_integration: + docker compose -f "docker/test_db/docker-compose.yml" up -d + ./docker/wait_for_db.sh + gotestsum --format testname ./testintegration test_e2e: docker compose -f "docker/test_db/docker-compose.yml" -f "docker/test_api/docker-compose.yml" up -d @@ -17,5 +24,5 @@ test_e2e: test_generate_mocks: mockery --all --keeptree -.PHONY: test_unit test_e2e +.PHONY: test_unit test_integration test_e2e diff --git a/go.mod b/go.mod index b63125a8..2391c154 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,9 @@ module github.com/ditrit/badaas go 1.18 require ( - github.com/Masterminds/squirrel v1.5.4 + github.com/Masterminds/semver/v3 v3.1.1 github.com/ditrit/verdeter v0.4.0 + github.com/elliotchance/pie/v2 v2.7.0 github.com/google/uuid v1.3.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 @@ -19,14 +20,14 @@ require ( golang.org/x/crypto v0.9.0 gorm.io/driver/postgres v1.5.2 gorm.io/gorm v1.25.1 + gotest.tools v2.2.0+incompatible ) -require github.com/felixge/httpsnoop v1.0.1 // indirect - require ( - github.com/Masterminds/semver/v3 v3.1.1 github.com/davecgh/go-spew v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/goph/emperror v0.17.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -38,8 +39,6 @@ require ( github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect @@ -55,6 +54,7 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index df9b03a0..d87cc7d2 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= -github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= @@ -68,6 +66,8 @@ 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= github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= +github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg= +github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -125,6 +125,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -219,10 +220,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 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/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -359,6 +356,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 h1:ba9YlqfDGTTQ5aZ2fwOoQ1hf32QySyQkR6ODGDzHlnE= +golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -669,6 +668,8 @@ gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go.work.sum b/go.work.sum index f27a97f4..c1d496da 100644 --- a/go.work.sum +++ b/go.work.sum @@ -132,6 +132,8 @@ cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72 cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -196,9 +198,11 @@ github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4 github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -214,6 +218,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -224,6 +229,7 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sagikazarmark/crypt v0.10.0/go.mod h1:gwTNHQVoOS3xp9Xvz5LLR+1AauC5M6880z5NWzdhOyQ= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -235,17 +241,20 @@ go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -270,10 +279,13 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -283,6 +295,7 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= diff --git a/sonar-project.properties b/sonar-project.properties index 9b068f33..b6014bb3 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,5 +5,5 @@ sonar.host.url=https://sonarcloud.io sonar.sources=. sonar.exclusions=**/*_test.go,mocks/***,vendor/*** sonar.tests=. -sonar.test.inclusions=**/*_test.go,test_e2e/*** -sonar.go.coverage.reportPaths=coverage_unit.out/coverage_unit.out +sonar.test.inclusions=**/*_test.go,testintegration/***,test_e2e/*** +sonar.go.coverage.reportPaths=coverage_unit.out/coverage_unit.out,coverage_int.out/coverage_int.out diff --git a/test_e2e/go.mod b/test_e2e/go.mod index ecdb6df1..f43e61ec 100644 --- a/test_e2e/go.mod +++ b/test_e2e/go.mod @@ -2,68 +2,70 @@ module github.com/ditrit/badaas/test_e2e go 1.18 +replace github.com/ditrit/badaas => ../ + require ( github.com/Masterminds/semver/v3 v3.1.1 github.com/cucumber/godog v0.12.5 - github.com/ditrit/badaas v0.0.0-20230727191545-7a2596b72797 - github.com/elliotchance/pie/v2 v2.5.2 + github.com/ditrit/badaas v0.0.0 + github.com/elliotchance/pie/v2 v2.7.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.13.0 - go.uber.org/zap v1.23.0 - gorm.io/gorm v1.24.1 + github.com/spf13/viper v1.16.0 + go.uber.org/zap v1.24.0 + gorm.io/gorm v1.25.1 ) require ( - github.com/Masterminds/squirrel v1.5.3 // indirect github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect github.com/cucumber/messages-go/v16 v16.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/ditrit/verdeter v0.4.0 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/goph/emperror v0.17.2 // indirect + github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-memdb v1.3.3 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.13.0 // indirect + github.com/jackc/pgconn v1.14.0 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.1 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.12.0 // indirect - github.com/jackc/pgx/v4 v4.17.2 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/spf13/afero v1.9.2 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.5.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/dig v1.15.0 // indirect - go.uber.org/fx v1.18.2 // indirect - go.uber.org/multierr v1.8.0 // indirect - golang.org/x/crypto v0.1.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/fx v1.19.3 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.9.0 // indirect golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.4.5 // indirect + gorm.io/driver/postgres v1.5.2 // indirect + gotest.tools v2.2.0+incompatible // indirect ) diff --git a/test_e2e/go.sum b/test_e2e/go.sum index 0912407a..c0628486 100644 --- a/test_e2e/go.sum +++ b/test_e2e/go.sum @@ -41,8 +41,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= -github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -70,7 +68,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -93,12 +90,10 @@ 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= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/ditrit/badaas v0.0.0-20230727191545-7a2596b72797 h1:Y/dcxQT0l+m+vjspbhLSBvq6dWy6ijt3SErmegRqLiM= -github.com/ditrit/badaas v0.0.0-20230727191545-7a2596b72797/go.mod h1:ZfxtnvFsdevSvx3qKf//Js1MVqDqtysPSMbvUfn4Dvk= github.com/ditrit/verdeter v0.4.0 h1:DzEOFauuXEGNQYP6OgYtHwEyb3w9riem99u0xE/l7+o= github.com/ditrit/verdeter v0.4.0/go.mod h1:sKpWuOvYqNabLN4aNXqeBhcWpt7nf0frwqk0B5M6ax0= -github.com/elliotchance/pie/v2 v2.5.2 h1:jRENMmysCljhUmyT8ITKV0Atp6Lukm3XpeqaI87POsM= -github.com/elliotchance/pie/v2 v2.5.2/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= +github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg= +github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -106,10 +101,12 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= @@ -118,10 +115,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= @@ -192,6 +187,8 @@ github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -233,8 +230,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -244,9 +241,8 @@ github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= -github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= +github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -262,29 +258,23 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= -github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= -github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= -github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -303,33 +293,25 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 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.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 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/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= @@ -357,10 +339,8 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -381,7 +361,7 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= @@ -392,15 +372,13 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -408,14 +386,14 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -423,13 +401,14 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= -github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -437,10 +416,13 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= @@ -449,6 +431,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -459,27 +442,20 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= -go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= -go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= -go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= +go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -493,10 +469,11 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -532,6 +509,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -570,6 +548,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -589,6 +569,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -613,7 +594,6 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -636,13 +616,18 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -651,8 +636,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -675,8 +661,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -685,7 +669,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -711,6 +694,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -823,18 +807,16 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc= -gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg= -gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs= -gorm.io/gorm v1.24.1/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/test_e2e/setup.go b/test_e2e/setup.go index d554670b..8339f29a 100644 --- a/test_e2e/setup.go +++ b/test_e2e/setup.go @@ -1,24 +1,17 @@ package main import ( - "log" - "gorm.io/gorm" "github.com/ditrit/badaas/persistence/models" + "github.com/ditrit/badaas/testintegration" ) -var ListOfTables = []any{ - models.Session{}, - models.User{}, -} - func CleanDB(db *gorm.DB) { - // clean database to ensure independency between tests - for _, table := range ListOfTables { - err := db.Unscoped().Where("1 = 1").Delete(table).Error - if err != nil { - log.Fatalln("could not clean database: ", err) - } - } + testintegration.CleanDBTables(db, + []any{ + models.Session{}, + models.User{}, + }, + ) } diff --git a/testintegration/asserts.go b/testintegration/asserts.go new file mode 100644 index 00000000..b0c58e98 --- /dev/null +++ b/testintegration/asserts.go @@ -0,0 +1,33 @@ +package testintegration + +import ( + "log" + + "github.com/stretchr/testify/suite" + is "gotest.tools/assert/cmp" +) + +func EqualList[T any](ts *suite.Suite, expectedList, actualList []T) { + expectedLen := len(expectedList) + equalLen := ts.Len(actualList, expectedLen) + + if equalLen { + for i := 0; i < expectedLen; i++ { + j := 0 + for ; j < expectedLen; j++ { + if is.DeepEqual( + actualList[j], + expectedList[i], + )().Success() { + break + } + } + if j == expectedLen { + ts.Fail("Lists not equal", "element %v not in list %v", expectedList[i], actualList) + for _, element := range actualList { + log.Println(element) + } + } + } + } +} diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go new file mode 100644 index 00000000..427ad967 --- /dev/null +++ b/testintegration/conditions/bicycle_conditions.go @@ -0,0 +1,53 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func BicycleId(v orm.UUID) orm.WhereCondition[models.Bicycle] { + return orm.WhereCondition[models.Bicycle]{ + Field: "ID", + Value: v, + } +} +func BicycleCreatedAt(v time.Time) orm.WhereCondition[models.Bicycle] { + return orm.WhereCondition[models.Bicycle]{ + Field: "CreatedAt", + Value: v, + } +} +func BicycleUpdatedAt(v time.Time) orm.WhereCondition[models.Bicycle] { + return orm.WhereCondition[models.Bicycle]{ + Field: "UpdatedAt", + Value: v, + } +} +func BicycleDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Bicycle] { + return orm.WhereCondition[models.Bicycle]{ + Field: "DeletedAt", + Value: v, + } +} +func BicycleName(v string) orm.WhereCondition[models.Bicycle] { + return orm.WhereCondition[models.Bicycle]{ + Field: "Name", + Value: v, + } +} +func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.Condition[models.Bicycle] { + return orm.JoinCondition[models.Bicycle, models.Person]{ + Conditions: conditions, + T1Field: "OwnerName", + T2Field: "Name", + } +} +func BicycleOwnerName(v string) orm.WhereCondition[models.Bicycle] { + return orm.WhereCondition[models.Bicycle]{ + Field: "OwnerName", + Value: v, + } +} diff --git a/testintegration/conditions/brand_conditions.go b/testintegration/conditions/brand_conditions.go new file mode 100644 index 00000000..42111242 --- /dev/null +++ b/testintegration/conditions/brand_conditions.go @@ -0,0 +1,40 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func BrandId(v uint) orm.WhereCondition[models.Brand] { + return orm.WhereCondition[models.Brand]{ + Field: "ID", + Value: v, + } +} +func BrandCreatedAt(v time.Time) orm.WhereCondition[models.Brand] { + return orm.WhereCondition[models.Brand]{ + Field: "CreatedAt", + Value: v, + } +} +func BrandUpdatedAt(v time.Time) orm.WhereCondition[models.Brand] { + return orm.WhereCondition[models.Brand]{ + Field: "UpdatedAt", + Value: v, + } +} +func BrandDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Brand] { + return orm.WhereCondition[models.Brand]{ + Field: "DeletedAt", + Value: v, + } +} +func BrandName(v string) orm.WhereCondition[models.Brand] { + return orm.WhereCondition[models.Brand]{ + Field: "Name", + Value: v, + } +} diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go new file mode 100644 index 00000000..8b68782e --- /dev/null +++ b/testintegration/conditions/city_conditions.go @@ -0,0 +1,46 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func CityId(v orm.UUID) orm.WhereCondition[models.City] { + return orm.WhereCondition[models.City]{ + Field: "ID", + Value: v, + } +} +func CityCreatedAt(v time.Time) orm.WhereCondition[models.City] { + return orm.WhereCondition[models.City]{ + Field: "CreatedAt", + Value: v, + } +} +func CityUpdatedAt(v time.Time) orm.WhereCondition[models.City] { + return orm.WhereCondition[models.City]{ + Field: "UpdatedAt", + Value: v, + } +} +func CityDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.City] { + return orm.WhereCondition[models.City]{ + Field: "DeletedAt", + Value: v, + } +} +func CityName(v string) orm.WhereCondition[models.City] { + return orm.WhereCondition[models.City]{ + Field: "Name", + Value: v, + } +} +func CityCountryId(v orm.UUID) orm.WhereCondition[models.City] { + return orm.WhereCondition[models.City]{ + Field: "CountryID", + Value: v, + } +} diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go new file mode 100644 index 00000000..8ce6a3fe --- /dev/null +++ b/testintegration/conditions/company_conditions.go @@ -0,0 +1,47 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func CompanyId(v orm.UUID) orm.WhereCondition[models.Company] { + return orm.WhereCondition[models.Company]{ + Field: "ID", + Value: v, + } +} +func CompanyCreatedAt(v time.Time) orm.WhereCondition[models.Company] { + return orm.WhereCondition[models.Company]{ + Field: "CreatedAt", + Value: v, + } +} +func CompanyUpdatedAt(v time.Time) orm.WhereCondition[models.Company] { + return orm.WhereCondition[models.Company]{ + Field: "UpdatedAt", + Value: v, + } +} +func CompanyDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Company] { + return orm.WhereCondition[models.Company]{ + Field: "DeletedAt", + Value: v, + } +} +func CompanyName(v string) orm.WhereCondition[models.Company] { + return orm.WhereCondition[models.Company]{ + Field: "Name", + Value: v, + } +} +func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[models.Seller] { + return orm.JoinCondition[models.Seller, models.Company]{ + Conditions: conditions, + T1Field: "CompanyID", + T2Field: "ID", + } +} diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go new file mode 100644 index 00000000..082bbfa3 --- /dev/null +++ b/testintegration/conditions/country_conditions.go @@ -0,0 +1,54 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func CountryId(v orm.UUID) orm.WhereCondition[models.Country] { + return orm.WhereCondition[models.Country]{ + Field: "ID", + Value: v, + } +} +func CountryCreatedAt(v time.Time) orm.WhereCondition[models.Country] { + return orm.WhereCondition[models.Country]{ + Field: "CreatedAt", + Value: v, + } +} +func CountryUpdatedAt(v time.Time) orm.WhereCondition[models.Country] { + return orm.WhereCondition[models.Country]{ + Field: "UpdatedAt", + Value: v, + } +} +func CountryDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Country] { + return orm.WhereCondition[models.Country]{ + Field: "DeletedAt", + Value: v, + } +} +func CountryName(v string) orm.WhereCondition[models.Country] { + return orm.WhereCondition[models.Country]{ + Field: "Name", + Value: v, + } +} +func CountryCapital(conditions ...orm.Condition[models.City]) orm.Condition[models.Country] { + return orm.JoinCondition[models.Country, models.City]{ + Conditions: conditions, + T1Field: "ID", + T2Field: "CountryID", + } +} +func CityCountry(conditions ...orm.Condition[models.Country]) orm.Condition[models.City] { + return orm.JoinCondition[models.City, models.Country]{ + Conditions: conditions, + T1Field: "CountryID", + T2Field: "ID", + } +} diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go new file mode 100644 index 00000000..5360f201 --- /dev/null +++ b/testintegration/conditions/employee_conditions.go @@ -0,0 +1,53 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func EmployeeId(v orm.UUID) orm.WhereCondition[models.Employee] { + return orm.WhereCondition[models.Employee]{ + Field: "ID", + Value: v, + } +} +func EmployeeCreatedAt(v time.Time) orm.WhereCondition[models.Employee] { + return orm.WhereCondition[models.Employee]{ + Field: "CreatedAt", + Value: v, + } +} +func EmployeeUpdatedAt(v time.Time) orm.WhereCondition[models.Employee] { + return orm.WhereCondition[models.Employee]{ + Field: "UpdatedAt", + Value: v, + } +} +func EmployeeDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Employee] { + return orm.WhereCondition[models.Employee]{ + Field: "DeletedAt", + Value: v, + } +} +func EmployeeName(v string) orm.WhereCondition[models.Employee] { + return orm.WhereCondition[models.Employee]{ + Field: "Name", + Value: v, + } +} +func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.Condition[models.Employee] { + return orm.JoinCondition[models.Employee, models.Employee]{ + Conditions: conditions, + T1Field: "BossID", + T2Field: "ID", + } +} +func EmployeeBossId(v orm.UUID) orm.WhereCondition[models.Employee] { + return orm.WhereCondition[models.Employee]{ + Field: "BossID", + Value: v, + } +} diff --git a/testintegration/conditions/orm.go b/testintegration/conditions/orm.go new file mode 100644 index 00000000..78acbc6d --- /dev/null +++ b/testintegration/conditions/orm.go @@ -0,0 +1,3 @@ +package conditions + +//go:generate badaas-cli gen conditions ../models diff --git a/testintegration/conditions/person_conditions.go b/testintegration/conditions/person_conditions.go new file mode 100644 index 00000000..1d99b02b --- /dev/null +++ b/testintegration/conditions/person_conditions.go @@ -0,0 +1,40 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func PersonId(v orm.UUID) orm.WhereCondition[models.Person] { + return orm.WhereCondition[models.Person]{ + Field: "ID", + Value: v, + } +} +func PersonCreatedAt(v time.Time) orm.WhereCondition[models.Person] { + return orm.WhereCondition[models.Person]{ + Field: "CreatedAt", + Value: v, + } +} +func PersonUpdatedAt(v time.Time) orm.WhereCondition[models.Person] { + return orm.WhereCondition[models.Person]{ + Field: "UpdatedAt", + Value: v, + } +} +func PersonDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Person] { + return orm.WhereCondition[models.Person]{ + Field: "DeletedAt", + Value: v, + } +} +func PersonName(v string) orm.WhereCondition[models.Person] { + return orm.WhereCondition[models.Person]{ + Field: "Name", + Value: v, + } +} diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go new file mode 100644 index 00000000..62abfa92 --- /dev/null +++ b/testintegration/conditions/phone_conditions.go @@ -0,0 +1,53 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func PhoneId(v uint) orm.WhereCondition[models.Phone] { + return orm.WhereCondition[models.Phone]{ + Field: "ID", + Value: v, + } +} +func PhoneCreatedAt(v time.Time) orm.WhereCondition[models.Phone] { + return orm.WhereCondition[models.Phone]{ + Field: "CreatedAt", + Value: v, + } +} +func PhoneUpdatedAt(v time.Time) orm.WhereCondition[models.Phone] { + return orm.WhereCondition[models.Phone]{ + Field: "UpdatedAt", + Value: v, + } +} +func PhoneDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Phone] { + return orm.WhereCondition[models.Phone]{ + Field: "DeletedAt", + Value: v, + } +} +func PhoneName(v string) orm.WhereCondition[models.Phone] { + return orm.WhereCondition[models.Phone]{ + Field: "Name", + Value: v, + } +} +func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.Condition[models.Phone] { + return orm.JoinCondition[models.Phone, models.Brand]{ + Conditions: conditions, + T1Field: "BrandID", + T2Field: "ID", + } +} +func PhoneBrandId(v uint) orm.WhereCondition[models.Phone] { + return orm.WhereCondition[models.Phone]{ + Field: "BrandID", + Value: v, + } +} diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go new file mode 100644 index 00000000..ee037789 --- /dev/null +++ b/testintegration/conditions/product_conditions.go @@ -0,0 +1,89 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func ProductId(v orm.UUID) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "ID", + Value: v, + } +} +func ProductCreatedAt(v time.Time) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "CreatedAt", + Value: v, + } +} +func ProductUpdatedAt(v time.Time) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "UpdatedAt", + Value: v, + } +} +func ProductDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "DeletedAt", + Value: v, + } +} +func ProductString(v string) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Column: "string_something_else", + Value: v, + } +} +func ProductInt(v int) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "Int", + Value: v, + } +} +func ProductIntPointer(v int) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "IntPointer", + Value: v, + } +} +func ProductFloat(v float64) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "Float", + Value: v, + } +} +func ProductBool(v bool) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "Bool", + Value: v, + } +} +func ProductByteArray(v []uint8) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "ByteArray", + Value: v, + } +} +func ProductMultiString(v models.MultiString) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "MultiString", + Value: v, + } +} +func ProductEmbeddedInt(v int) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + Field: "EmbeddedInt", + Value: v, + } +} +func ProductGormEmbeddedInt(v int) orm.WhereCondition[models.Product] { + return orm.WhereCondition[models.Product]{ + ColumnPrefix: "gorm_embedded_", + Field: "Int", + Value: v, + } +} diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go new file mode 100644 index 00000000..d4460abe --- /dev/null +++ b/testintegration/conditions/sale_conditions.go @@ -0,0 +1,72 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func SaleId(v orm.UUID) orm.WhereCondition[models.Sale] { + return orm.WhereCondition[models.Sale]{ + Field: "ID", + Value: v, + } +} +func SaleCreatedAt(v time.Time) orm.WhereCondition[models.Sale] { + return orm.WhereCondition[models.Sale]{ + Field: "CreatedAt", + Value: v, + } +} +func SaleUpdatedAt(v time.Time) orm.WhereCondition[models.Sale] { + return orm.WhereCondition[models.Sale]{ + Field: "UpdatedAt", + Value: v, + } +} +func SaleDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Sale] { + return orm.WhereCondition[models.Sale]{ + Field: "DeletedAt", + Value: v, + } +} +func SaleCode(v int) orm.WhereCondition[models.Sale] { + return orm.WhereCondition[models.Sale]{ + Field: "Code", + Value: v, + } +} +func SaleDescription(v string) orm.WhereCondition[models.Sale] { + return orm.WhereCondition[models.Sale]{ + Field: "Description", + Value: v, + } +} +func SaleProduct(conditions ...orm.Condition[models.Product]) orm.Condition[models.Sale] { + return orm.JoinCondition[models.Sale, models.Product]{ + Conditions: conditions, + T1Field: "ProductID", + T2Field: "ID", + } +} +func SaleProductId(v orm.UUID) orm.WhereCondition[models.Sale] { + return orm.WhereCondition[models.Sale]{ + Field: "ProductID", + Value: v, + } +} +func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.Condition[models.Sale] { + return orm.JoinCondition[models.Sale, models.Seller]{ + Conditions: conditions, + T1Field: "SellerID", + T2Field: "ID", + } +} +func SaleSellerId(v orm.UUID) orm.WhereCondition[models.Sale] { + return orm.WhereCondition[models.Sale]{ + Field: "SellerID", + Value: v, + } +} diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go new file mode 100644 index 00000000..acb5860d --- /dev/null +++ b/testintegration/conditions/seller_conditions.go @@ -0,0 +1,46 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func SellerId(v orm.UUID) orm.WhereCondition[models.Seller] { + return orm.WhereCondition[models.Seller]{ + Field: "ID", + Value: v, + } +} +func SellerCreatedAt(v time.Time) orm.WhereCondition[models.Seller] { + return orm.WhereCondition[models.Seller]{ + Field: "CreatedAt", + Value: v, + } +} +func SellerUpdatedAt(v time.Time) orm.WhereCondition[models.Seller] { + return orm.WhereCondition[models.Seller]{ + Field: "UpdatedAt", + Value: v, + } +} +func SellerDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Seller] { + return orm.WhereCondition[models.Seller]{ + Field: "DeletedAt", + Value: v, + } +} +func SellerName(v string) orm.WhereCondition[models.Seller] { + return orm.WhereCondition[models.Seller]{ + Field: "Name", + Value: v, + } +} +func SellerCompanyId(v orm.UUID) orm.WhereCondition[models.Seller] { + return orm.WhereCondition[models.Seller]{ + Field: "CompanyID", + Value: v, + } +} diff --git a/testintegration/crudRepository.go b/testintegration/crudRepository.go new file mode 100644 index 00000000..ceda4b0f --- /dev/null +++ b/testintegration/crudRepository.go @@ -0,0 +1,87 @@ +package testintegration + +import ( + "github.com/stretchr/testify/suite" + "gorm.io/gorm" + "gotest.tools/assert" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type CRUDRepositoryIntTestSuite struct { + suite.Suite + db *gorm.DB + crudProductRepository orm.CRUDRepository[models.Product, orm.UUID] +} + +func NewCRUDRepositoryIntTestSuite( + db *gorm.DB, + crudProductRepository orm.CRUDRepository[models.Product, orm.UUID], +) *CRUDRepositoryIntTestSuite { + return &CRUDRepositoryIntTestSuite{ + db: db, + crudProductRepository: crudProductRepository, + } +} + +func (ts *CRUDRepositoryIntTestSuite) SetupTest() { + CleanDB(ts.db) +} + +func (ts *CRUDRepositoryIntTestSuite) TearDownSuite() { + CleanDB(ts.db) +} + +// ------------------------- GetByID -------------------------------- + +func (ts *CRUDRepositoryIntTestSuite) TestGetByIDReturnsErrorIfIDDontMatch() { + ts.createProduct(0) + _, err := ts.crudProductRepository.GetByID(ts.db, orm.NilUUID) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *CRUDRepositoryIntTestSuite) TestGetByIDReturnsEntityIfIDMatch() { + product := ts.createProduct(0) + ts.createProduct(0) + productReturned, err := ts.crudProductRepository.GetByID(ts.db, product.ID) + ts.Nil(err) + + assert.DeepEqual(ts.T(), product, productReturned) +} + +// ------------------------- QueryOne -------------------------------- + +func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsErrorIfConditionsDontMatch() { + ts.createProduct(0) + _, err := ts.crudProductRepository.QueryOne(ts.db, conditions.ProductInt(1)) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsEntityIfConditionsMatch() { + product := ts.createProduct(1) + productReturned, err := ts.crudProductRepository.QueryOne(ts.db, conditions.ProductInt(1)) + ts.Nil(err) + + assert.DeepEqual(ts.T(), product, productReturned) +} + +func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsErrorIfMoreThanOneMatchConditions() { + ts.createProduct(0) + ts.createProduct(0) + _, err := ts.crudProductRepository.QueryOne(ts.db, conditions.ProductInt(0)) + ts.Error(err, orm.ErrMoreThanOneObjectFound) +} + +// ------------------------- utils ------------------------- + +func (ts *CRUDRepositoryIntTestSuite) createProduct(intV int) *models.Product { + entity := &models.Product{ + Int: intV, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} diff --git a/testintegration/crudServiceCommon.go b/testintegration/crudServiceCommon.go new file mode 100644 index 00000000..4769ac3b --- /dev/null +++ b/testintegration/crudServiceCommon.go @@ -0,0 +1,127 @@ +package testintegration + +import ( + "github.com/stretchr/testify/suite" + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/models" +) + +type CRUDServiceCommonIntTestSuite struct { + suite.Suite + db *gorm.DB +} + +func (ts *CRUDServiceCommonIntTestSuite) SetupTest() { + CleanDB(ts.db) +} + +func (ts *CRUDServiceCommonIntTestSuite) TearDownSuite() { + CleanDB(ts.db) +} + +func (ts *CRUDServiceCommonIntTestSuite) createProduct(stringV string, intV int, floatV float64, boolV bool, intP *int) *models.Product { + entity := &models.Product{ + String: stringV, + Int: intV, + Float: floatV, + Bool: boolV, + IntPointer: intP, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createSale(code int, product *models.Product, seller *models.Seller) *models.Sale { + entity := &models.Sale{ + Code: code, + Product: *product, + Seller: seller, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createSeller(name string, company *models.Company) *models.Seller { + var companyID *orm.UUID + if company != nil { + companyID = &company.ID + } + entity := &models.Seller{ + Name: name, + CompanyID: companyID, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createCompany(name string) *models.Company { + entity := &models.Company{ + Name: name, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createCountry(name string, capital models.City) *models.Country { + entity := &models.Country{ + Name: name, + Capital: capital, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createEmployee(name string, boss *models.Employee) *models.Employee { + entity := &models.Employee{ + Name: name, + Boss: boss, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createBicycle(name string, owner models.Person) *models.Bicycle { + entity := &models.Bicycle{ + Name: name, + Owner: owner, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createBrand(name string) *models.Brand { + entity := &models.Brand{ + Name: name, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} + +func (ts *CRUDServiceCommonIntTestSuite) createPhone(name string, brand models.Brand) *models.Phone { + entity := &models.Phone{ + Name: name, + Brand: brand, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} diff --git a/testintegration/db_models.go b/testintegration/db_models.go new file mode 100644 index 00000000..2c24b637 --- /dev/null +++ b/testintegration/db_models.go @@ -0,0 +1,45 @@ +package testintegration + +import ( + "log" + + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/models" +) + +var ListOfTables = []any{ + models.Product{}, + models.Company{}, + models.Seller{}, + models.Sale{}, + models.Country{}, + models.City{}, + models.Employee{}, + models.Person{}, + models.Bicycle{}, + models.Brand{}, + models.Phone{}, +} + +func GetModels() orm.GetModelsResult { + return orm.GetModelsResult{ + Models: ListOfTables, + } +} + +func CleanDB(db *gorm.DB) { + CleanDBTables(db, pie.Reverse(ListOfTables)) +} + +func CleanDBTables(db *gorm.DB, listOfTables []any) { + // clean database to ensure independency between tests + for _, table := range listOfTables { + err := db.Unscoped().Where("1 = 1").Delete(table).Error + if err != nil { + log.Fatalln("could not clean database: ", err) + } + } +} diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go new file mode 100644 index 00000000..0ce3ef4b --- /dev/null +++ b/testintegration/join_conditions_test.go @@ -0,0 +1,337 @@ +package testintegration + +import ( + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type JoinConditionsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudSaleService orm.CRUDService[models.Sale, orm.UUID] + crudSellerService orm.CRUDService[models.Seller, orm.UUID] + crudCountryService orm.CRUDService[models.Country, orm.UUID] + crudCityService orm.CRUDService[models.City, orm.UUID] + crudEmployeeService orm.CRUDService[models.Employee, orm.UUID] + crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID] + crudPhoneService orm.CRUDService[models.Phone, uint] +} + +func NewJoinConditionsIntTestSuite( + db *gorm.DB, + crudSaleService orm.CRUDService[models.Sale, orm.UUID], + crudSellerService orm.CRUDService[models.Seller, orm.UUID], + crudCountryService orm.CRUDService[models.Country, orm.UUID], + crudCityService orm.CRUDService[models.City, orm.UUID], + crudEmployeeService orm.CRUDService[models.Employee, orm.UUID], + crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID], + crudPhoneService orm.CRUDService[models.Phone, uint], +) *JoinConditionsIntTestSuite { + return &JoinConditionsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudSaleService: crudSaleService, + crudSellerService: crudSellerService, + crudCountryService: crudCountryService, + crudCityService: crudCityService, + crudEmployeeService: crudEmployeeService, + crudBicycleService: crudBicycleService, + crudPhoneService: crudPhoneService, + } +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsUintBelongsTo() { + brand1 := ts.createBrand("google") + brand2 := ts.createBrand("apple") + + match := ts.createPhone("pixel", *brand1) + ts.createPhone("iphone", *brand2) + + entities, err := ts.crudPhoneService.Query( + conditions.PhoneBrand( + conditions.BrandName("google"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Phone{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsBelongsTo() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductInt(1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsAndFiltersTheMainEntity() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(1, product1, seller1) + ts.createSale(2, product2, seller2) + ts.createSale(2, product1, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleCode(1), + conditions.SaleProduct( + conditions.ProductInt(1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsHasOneOptional() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerName("franco"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsHasOneSelfReferential() { + boss1 := &models.Employee{ + Name: "Xavier", + } + boss2 := &models.Employee{ + Name: "Vincent", + } + + match := ts.createEmployee("franco", boss1) + ts.createEmployee("pierre", boss2) + + entities, err := ts.crudEmployeeService.Query( + conditions.EmployeeBoss( + conditions.EmployeeName("Xavier"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Employee{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOneToOne() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + ts.createCountry("Argentina", capital1) + ts.createCountry("France", capital2) + + entities, err := ts.crudCityService.Query( + conditions.CityCountry( + conditions.CountryName("Argentina"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.City{&capital1}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOneToOneReversed() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + country1 := ts.createCountry("Argentina", capital1) + ts.createCountry("France", capital2) + + entities, err := ts.crudCountryService.Query( + conditions.CountryCapital( + conditions.CityName("Buenos Aires"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Country{country1}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsWithEntityThatDefinesTableName() { + person1 := models.Person{ + Name: "franco", + } + person2 := models.Person{ + Name: "xavier", + } + + match := ts.createBicycle("BMX", person1) + ts.createBicycle("Shimano", person2) + + entities, err := ts.crudBicycleService.Query( + conditions.BicycleOwner( + conditions.PersonName("franco"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Bicycle{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnHasMany() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + match := ts.createSeller("franco", company1) + ts.createSeller("agustin", company2) + + entities, err := ts.crudSellerService.Query( + conditions.SellerCompany( + conditions.CompanyName("ditrit"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Seller{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnDifferentAttributes() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductInt(1), + conditions.ProductString("match"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsAddsDeletedAtAutomatically() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + ts.Nil(ts.db.Delete(product2).Error) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductString("match"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnDeletedAt() { + product1 := ts.createProduct("match", 1, 0.0, false, nil) + product2 := ts.createProduct("match", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + ts.Nil(ts.db.Delete(product1).Error) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductDeletedAt(product1.DeletedAt), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsDifferentEntities() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + ts.createSale(0, product1, seller2) + ts.createSale(0, product2, seller1) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductInt(1), + ), + conditions.SaleSeller( + conditions.SellerName("franco"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsMultipleTimes() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("franco", company1) + seller2 := ts.createSeller("agustin", company2) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerName("franco"), + conditions.SellerCompany( + conditions.CompanyName("ditrit"), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} diff --git a/testintegration/models/models.go b/testintegration/models/models.go new file mode 100644 index 00000000..8979faa3 --- /dev/null +++ b/testintegration/models/models.go @@ -0,0 +1,179 @@ +package models + +import ( + "database/sql/driver" + "fmt" + "strings" + + "github.com/ditrit/badaas/orm" +) + +type Employee struct { + orm.UUIDModel + + Name string + Boss *Employee // Self-Referential Has One (Employee 0..* -> 0..1 Employee) + BossID *orm.UUID +} + +func (m Employee) Equal(other Employee) bool { + return m.Name == other.Name +} + +type Company struct { + orm.UUIDModel + + Name string + Sellers []Seller // Company HasMany Sellers (Company 0..1 -> 0..* Seller) +} + +type MultiString []string + +func (s *MultiString) Scan(src interface{}) error { + switch typedSrc := src.(type) { + case string: + *s = strings.Split(typedSrc, ",") + return nil + case []byte: + str := string(typedSrc) + *s = strings.Split(str, ",") + return nil + default: + return fmt.Errorf("failed to scan multistring field - source is not a string, is %T", src) + } +} + +func (s MultiString) Value() (driver.Value, error) { + if len(s) == 0 { + return nil, nil + } + return strings.Join(s, ","), nil +} + +func (MultiString) GormDataType() string { + return "text" +} + +type ToBeEmbedded struct { + EmbeddedInt int +} + +type ToBeGormEmbedded struct { + Int int +} + +type Product struct { + orm.UUIDModel + + String string `gorm:"column:string_something_else"` + Int int + IntPointer *int + Float float64 + Bool bool + ByteArray []byte + MultiString MultiString + ToBeEmbedded + GormEmbedded ToBeGormEmbedded `gorm:"embedded;embeddedPrefix:gorm_embedded_"` +} + +func (m Product) Equal(other Product) bool { + return m.ID == other.ID +} + +type Seller struct { + orm.UUIDModel + + Name string + CompanyID *orm.UUID // Company HasMany Sellers (Company 0..1 -> 0..* Seller) +} + +type Sale struct { + orm.UUIDModel + + Code int + Description string + + // Sale belongsTo Product (Sale 0..* -> 1 Product) + Product Product + ProductID orm.UUID + + // Sale HasOne Seller (Sale 0..* -> 0..1 Seller) + Seller *Seller + SellerID *orm.UUID +} + +func (m Sale) Equal(other Sale) bool { + return m.ID == other.ID +} + +func (m Seller) Equal(other Seller) bool { + return m.Name == other.Name +} + +type Country struct { + orm.UUIDModel + + Name string + Capital City // Country HasOne City (Country 1 -> 1 City) +} + +type City struct { + orm.UUIDModel + + Name string + CountryID orm.UUID // Country HasOne City (Country 1 -> 1 City) +} + +func (m Country) Equal(other Country) bool { + return m.Name == other.Name +} + +func (m City) Equal(other City) bool { + return m.Name == other.Name +} + +type Person struct { + orm.UUIDModel + + Name string `gorm:"unique;type:VARCHAR(255)"` +} + +func (m Person) TableName() string { + return "persons_and_more_name" +} + +type Bicycle struct { + orm.UUIDModel + + Name string + // Bicycle BelongsTo Person (Bicycle 0..* -> 1 Person) + Owner Person `gorm:"references:Name;foreignKey:OwnerName"` + OwnerName string +} + +func (m Bicycle) Equal(other Bicycle) bool { + return m.Name == other.Name +} + +type Brand struct { + orm.UIntModel + + Name string +} + +func (m Brand) Equal(other Brand) bool { + return m.Name == other.Name +} + +type Phone struct { + orm.UIntModel + + Name string + // Phone belongsTo Brand (Phone 0..* -> 1 Brand) + Brand Brand + BrandID uint +} + +func (m Phone) Equal(other Phone) bool { + return m.Name == other.Name +} diff --git a/testintegration/orm_test.go b/testintegration/orm_test.go new file mode 100644 index 00000000..fb6e2a84 --- /dev/null +++ b/testintegration/orm_test.go @@ -0,0 +1,97 @@ +package testintegration + +import ( + "testing" + "time" + + "github.com/spf13/viper" + "github.com/stretchr/testify/suite" + "go.uber.org/fx" + "go.uber.org/fx/fxevent" + "go.uber.org/zap" + "gorm.io/gorm" + + "github.com/ditrit/badaas/configuration" + "github.com/ditrit/badaas/logger" + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/models" +) + +var tGlobal *testing.T + +const ( + username = "root" + password = "postgres" + host = "localhost" + port = 26257 + sslMode = "disable" + dbName = "badaas_db" +) + +func TestBaDaaSORM(t *testing.T) { + tGlobal = t + + fx.New( + // logger + fx.Provide(NewLoggerConfiguration), + logger.LoggerModule, + + // connect to db + fx.Provide(NewGormDBConnection), + + // activate badaas-orm + fx.Provide(GetModels), + orm.AutoMigrate, + + // logger for fx + fx.WithLogger(func(logger *zap.Logger) fxevent.Logger { + return &fxevent.ZapLogger{Logger: logger} + }), + + // create crud services for models + orm.GetCRUDServiceModule[models.Seller](), + orm.GetCRUDServiceModule[models.Product](), + orm.GetCRUDServiceModule[models.Sale](), + orm.GetCRUDServiceModule[models.City](), + orm.GetCRUDServiceModule[models.Country](), + orm.GetCRUDServiceModule[models.Employee](), + orm.GetCRUDServiceModule[models.Bicycle](), + orm.GetCRUDServiceModule[models.Phone](), + orm.GetCRUDServiceModule[models.Brand](), + + // create test suites + fx.Provide(NewCRUDRepositoryIntTestSuite), + fx.Provide(NewWhereConditionsIntTestSuite), + fx.Provide(NewJoinConditionsIntTestSuite), + + // run tests + fx.Invoke(runORMTestSuites), + ).Run() +} + +func runORMTestSuites( + tsCRUDRepository *CRUDRepositoryIntTestSuite, + tsWhereConditions *WhereConditionsIntTestSuite, + tsJoinConditions *JoinConditionsIntTestSuite, + db *gorm.DB, + shutdowner fx.Shutdowner, +) { + suite.Run(tGlobal, tsCRUDRepository) + suite.Run(tGlobal, tsWhereConditions) + suite.Run(tGlobal, tsJoinConditions) + + shutdowner.Shutdown() +} + +func NewLoggerConfiguration() configuration.LoggerConfiguration { + viper.Set(configuration.LoggerModeKey, "dev") + return configuration.NewLoggerConfiguration() +} + +func NewGormDBConnection(logger *zap.Logger) (*gorm.DB, error) { + return orm.ConnectToDialector( + logger, + orm.CreateDialector(host, username, password, sslMode, dbName, port), + 10, time.Duration(5)*time.Second, + ) +} diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go new file mode 100644 index 00000000..a1abf326 --- /dev/null +++ b/testintegration/where_conditions_test.go @@ -0,0 +1,395 @@ +package testintegration + +import ( + "gorm.io/gorm" + "gotest.tools/assert" + + "github.com/google/uuid" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type WhereConditionsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudProductService orm.CRUDService[models.Product, orm.UUID] + crudSaleService orm.CRUDService[models.Sale, orm.UUID] + crudBrandService orm.CRUDService[models.Brand, uint] +} + +func NewWhereConditionsIntTestSuite( + db *gorm.DB, + crudProductService orm.CRUDService[models.Product, orm.UUID], + crudSaleService orm.CRUDService[models.Sale, orm.UUID], + crudBrandService orm.CRUDService[models.Brand, uint], +) *WhereConditionsIntTestSuite { + return &WhereConditionsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudProductService: crudProductService, + crudSaleService: crudSaleService, + crudBrandService: crudBrandService, + } +} + +// ------------------------- GetByID -------------------------------- + +func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsErrorIfNotEntityCreated() { + _, err := ts.crudProductService.GetByID(orm.NilUUID) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsErrorIfNotEntityMatch() { + ts.createProduct("", 0, 0, false, nil) + + _, err := ts.crudProductService.GetByID(orm.UUID(uuid.New())) + ts.Error(err, gorm.ErrRecordNotFound) +} + +func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsTheEntityIfItIsCreate() { + match := ts.createProduct("", 0, 0, false, nil) + + entity, err := ts.crudProductService.GetByID(match.ID) + ts.Nil(err) + + assert.DeepEqual(ts.T(), match, entity) +} + +// ------------------------- Query -------------------------------- + +func (ts *WhereConditionsIntTestSuite) TestQueryWithoutConditionsReturnsEmptyIfNotEntitiesCreated() { + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithoutConditionsReturnsTheOnlyOneIfOneEntityCreated() { + match := ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithoutConditionsReturnsTheListWhenMultipleCreated() { + match1 := ts.createProduct("", 0, 0, false, nil) + match2 := ts.createProduct("", 0, 0, false, nil) + match3 := ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsReturnsEmptyIfNotEntitiesCreated() { + entities, err := ts.crudProductService.Query( + conditions.ProductString("not_created"), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsReturnsEmptyIfNothingMatch() { + ts.createProduct("something_else", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString("not_match"), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsReturnsOneIfOnlyOneMatch() { + match := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString("match"), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsReturnsMultipleIfMultipleMatch() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString("match"), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfIntType() { + match := ts.createProduct("match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt(1), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfFloatType() { + match := ts.createProduct("match", 0, 1.1, false, nil) + ts.createProduct("not_match", 0, 2.2, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat(1.1), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfBoolType() { + match := ts.createProduct("match", 0, 0.0, true, nil) + ts.createProduct("not_match", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductBool(true), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithMultipleConditionsOfDifferentTypesWorks() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 1, 0.0, true, nil) + ts.createProduct("match", 2, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString("match"), + conditions.ProductInt(1), + conditions.ProductBool(true), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfID() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductId(match.ID), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfCreatedAt() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductCreatedAt(match.CreatedAt), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryDeletedAtConditionIsAddedAutomatically() { + match := ts.createProduct("", 0, 0.0, false, nil) + deleted := ts.createProduct("", 0, 0.0, false, nil) + + ts.Nil(ts.db.Delete(deleted).Error) + + entities, err := ts.crudProductService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +// TODO DeletedAt with nil value but not automatic + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfDeletedAtNotNil() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + + ts.Nil(ts.db.Delete(match).Error) + + entities, err := ts.crudProductService.Query( + conditions.ProductDeletedAt(match.DeletedAt), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfEmbedded() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + match.EmbeddedInt = 1 + + err := ts.db.Save(match).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductEmbeddedInt(1), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfGormEmbedded() { + match := ts.createProduct("", 0, 0.0, false, nil) + ts.createProduct("", 0, 0.0, false, nil) + match.GormEmbedded.Int = 1 + + err := ts.db.Save(match).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductGormEmbeddedInt(1), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfPointerTypeWithValue() { + intMatch := 1 + match := ts.createProduct("match", 1, 0, false, &intMatch) + intNotMatch := 2 + ts.createProduct("not_match", 2, 0, false, &intNotMatch) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer(intMatch), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfByteArrayWithContent() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + match.ByteArray = []byte{1, 2} + notMatch1.ByteArray = []byte{2, 3} + + err := ts.db.Save(match).Error + ts.Nil(err) + + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductByteArray([]byte{1, 2}), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfByteArrayEmpty() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + match.ByteArray = []byte{} + notMatch1.ByteArray = []byte{2, 3} + + err := ts.db.Save(match).Error + ts.Nil(err) + + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductByteArray([]byte{}), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfCustomType() { + match := ts.createProduct("match", 1, 0, false, nil) + notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + match.MultiString = models.MultiString{"salut", "hola"} + notMatch1.MultiString = models.MultiString{"salut", "hola", "hello"} + + err := ts.db.Save(match).Error + ts.Nil(err) + + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductMultiString(models.MultiString{"salut", "hola"}), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfRelationType() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProductId(product1.ID), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfRelationTypeOptionalWithValue() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSellerId(seller1.ID), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsOnUIntModel() { + match := ts.createBrand("match") + ts.createBrand("not_match") + + entities, err := ts.crudBrandService.Query( + conditions.BrandName("match"), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Brand{match}, entities) +} From 319dd735ee94738f1bd88cdc7fa515d3022c3ae9 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 31 Jul 2023 17:08:29 +0200 Subject: [PATCH 29/90] update documentation --- CONTRIBUTING.md | 1 + README.md | 24 +++++++++++++++--------- orm/README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 orm/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60409007..00a4d76a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,6 +64,7 @@ This is the directory structure we use for the project: - `test_e2e/`: Contains all the feature and steps for e2e tests. - `testintegration/`: Contains all the integration tests. - `logger/` *(Go code)*: Contains the logger creation logic. Please don't call it from your own services and code, use the dependency injection system. +- `orm/` *(Go code)*: Contains the code of the orm used by badaas. - `persistance/` *(Go code)*: - `gormdatabase/` *(Go code)*: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. - `models/` *(Go code)*: Contains the models (for a structure to me considered a valid model, it has to embed `badaas/orm.UUIDModel` or `badaas/orm.UIntModel`). diff --git a/README.md b/README.md index 54d7a586..2bdcffe4 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,10 @@ Badaas enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). -Badaas provides several key features: - -- **Authentication**: Badaas can authenticate users using its internal authentication scheme or externally by using protocols such as OIDC, SAML, Oauth2... -- **Authorization**: On resource access, Badaas will check if the user is authorized using a RBAC model. -- **Distribution**: Badaas is built to run in clusters by default. Communications between nodes are TLS encrypted using [shoset](https://github.com/ditrit/shoset). -- **Persistence**: Applicative objects are persisted as well as user files. Those resources are shared across the clusters to increase resiliency. -- **Querying Resources**: Resources are accessible via a REST API. -- **Posix compliant**: Badaas strives towards being a good unix citizen and respecting commonly accepted norms. (see [Configuration](#configuration)) -- **Advanced logs management**: Badaas provides an interface to interact with the logs produced by the clusters. Logs are formatted in json by default. +> **Warning** +> BaDaaS is still under development. Each of its components can have a different state of evolution that you can consult in [Features and components](#features-and-components) +- [Features and components](#features-and-components) - [Quickstart](#quickstart) - [Example](#example) - [Step-by-step instructions](#step-by-step-instructions) @@ -19,6 +13,18 @@ Badaas provides several key features: - [Contributing](#contributing) - [License](#license) +## Features and components + +Badaas provides several key features, each provided by a component that can be used independently and has a different state of evolution: + +- **Authentication**(unstable): Badaas can authenticate users using its internal authentication scheme or externally by using protocols such as OIDC, SAML, Oauth2... +- **Authorization**(wip_unstable): On resource access, Badaas will check if the user is authorized using a RBAC model. +- **Distribution**(todo): Badaas is built to run in clusters by default. Communications between nodes are TLS encrypted using [shoset](https://github.com/ditrit/shoset). +- **Persistence**(wip_unstable): Applicative objects are persisted as well as user files. Those resources are shared across the clusters to increase resiliency. To achieve this, BaDaaS uses the [BaDaaS ORM](https://github.com/ditrit/badaas/orm) component. +- **Querying Resources**(unstable): Resources are accessible via a REST API. +- **Posix compliant**(stable): Badaas strives towards being a good unix citizen and respecting commonly accepted norms. (see [Configuration](#configuration)) +- **Advanced logs management**(todo): Badaas provides an interface to interact with the logs produced by the clusters. Logs are formatted in json by default. + ## Quickstart ### Example diff --git a/orm/README.md b/orm/README.md new file mode 100644 index 00000000..240a0cff --- /dev/null +++ b/orm/README.md @@ -0,0 +1,47 @@ +# BaDaaS ORM: Backend and Distribution ORM (Object Relational Mapping) + +BaDaaS ORM is the BaDaaS component that allows for easy persistence and querying of objects. It is built on top of gorm and adds for each entity a service and a repository that allows complex queries without any extra effort. + +BaDaaS ORM can be used both within a BaDaaS application and as a stand-alone application. + +- [Installation](#installation) +- [Provided functionalities](#provided-functionalities) + - [Base models](#base-models) + - [CRUDServiceModule](#crudservicemodule) + +## Installation + +Once you have started your project with `go init`, you must add the dependency to BaDaaS: + +```bash +go get -u github.com/ditrit/badaas +``` + +## Provided functionalities + +### Base models + +badaas-orm gives you two types of base models for your classes: `orm.UUIDModel` and `orm.UIntModel`. + +To use them, simply embed the desired model in any of your classes: + +```go +type MyClass struct { + orm.UUIDModel + + // your code here +} +``` + +Once done your class will be considered a **BaDaaS Model**. + +The difference between them is the type they will use as primary key: a random uuid and an auto incremental uint respectively. Both provide date created, edited and deleted (). + +### CRUDServiceModule + +`CRUDServiceModule` provides you a CRUDService and a CRUDRepository for your badaas Model. After calling it as, for example, `orm.GetCRUDServiceModule[models.Company](),` the following can be used by dependency injection: + +- `crudCompanyService orm.CRUDService[models.Company, orm.UUID]` +- `crudCompanyRepository orm.CRUDRepository[models.Company, orm.UUID]` + +These classes will allow you to perform queries using the compilable query system generated with badaas-cli. For details on how to do this visit [badaas-cli docs](github.com/ditrit/badaas-cli/README.md). From a9c3ead59a222f2526a5c3f0ddd8b3fb0c396b8c Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 31 Jul 2023 17:08:46 +0200 Subject: [PATCH 30/90] update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 63b1dbe5..5d771516 100644 --- a/changelog.md +++ b/changelog.md @@ -31,5 +31,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add a dto that is returned on a successful login. - Update verdeter to version v0.4.0. - Transform BadAas into a library. +- Add badaas-orm with the compilable query system. [unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased From 0d23c80fa8a3abb6333c862acae073baa077faea Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 09:40:20 +0200 Subject: [PATCH 31/90] add support for operators in conditions --- go.work.sum | 5 + orm/condition.go | 62 +++++++--- orm/operator.go | 61 +++++++++ orm/operators.go | 9 ++ persistence/models/User.go | 8 +- services/userservice/userservice.go | 2 +- services/userservice/userservice_test.go | 6 +- .../conditions/bicycle_conditions.go | 49 ++++---- .../conditions/brand_conditions.go | 41 +++--- testintegration/conditions/city_conditions.go | 49 ++++---- .../conditions/company_conditions.go | 41 +++--- .../conditions/country_conditions.go | 41 +++--- .../conditions/employee_conditions.go | 49 ++++---- .../conditions/person_conditions.go | 41 +++--- .../conditions/phone_conditions.go | 49 ++++---- .../conditions/product_conditions.go | 109 ++++++++-------- testintegration/conditions/sale_conditions.go | 65 +++++----- .../conditions/seller_conditions.go | 49 ++++---- testintegration/crudRepository.go | 15 ++- testintegration/join_conditions_test.go | 64 +++++----- testintegration/models/models.go | 2 + testintegration/operators_test.go | 70 +++++++++++ testintegration/orm_test.go | 3 + testintegration/where_conditions_test.go | 117 ++++++++++-------- 24 files changed, 605 insertions(+), 402 deletions(-) create mode 100644 orm/operator.go create mode 100644 orm/operators.go create mode 100644 testintegration/operators_test.go diff --git a/go.work.sum b/go.work.sum index c1d496da..c2bf63ee 100644 --- a/go.work.sum +++ b/go.work.sum @@ -252,6 +252,7 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -270,6 +271,7 @@ golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= 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/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -296,6 +298,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= @@ -313,6 +316,7 @@ google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= @@ -329,6 +333,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/orm/condition.go b/orm/condition.go index 0a5bb3be..0632cea6 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -21,24 +21,47 @@ type Condition[T any] interface { interfaceVerificationMethod(T) } -type WhereCondition[T any] struct { +// Conditions that can be used in a where clause +// (or in a on of a join) +type WhereCondition[T any] interface { + Condition[T] + + // Get the sql string and values to use in the query + GetSQL(query *gorm.DB, tableName string) (string, []any, error) + + // Returns true if the DeletedAt column if affected by the condition + // If no condition affects the DeletedAt, the verification that it's null will be added automatically + affectsDeletedAt() bool +} + +// Condition that verifies the value of a field, +// using the Operator +type FieldCondition[TObject any, TAtribute any] struct { Field string Column string ColumnPrefix string - Value any + Operator Operator[TAtribute] } -func (condition WhereCondition[T]) interfaceVerificationMethod(t T) { +//nolint:unused // see inside +func (condition FieldCondition[TObject, TAtribute]) interfaceVerificationMethod(_ TObject) { // This method is necessary to get the compiler to verify // that an object is of type Condition[T] } // Returns a gorm Where condition that can be used // to filter that the Field as a value of Value -func (condition WhereCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { - sql, values := condition.GetSQL(query, tableName) +func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + return applyWhereCondition[TObject](condition, query, tableName) +} + +func applyWhereCondition[T any](condition WhereCondition[T], query *gorm.DB, tableName string) (*gorm.DB, error) { + sql, values, err := condition.GetSQL(query, tableName) + if err != nil { + return nil, err + } - if condition.Field == DeletedAtField { + if condition.affectsDeletedAt() { query = query.Unscoped() } @@ -48,20 +71,24 @@ func (condition WhereCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*g ), nil } -func (condition WhereCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []any) { +//nolint:unused // is used +func (condition FieldCondition[TObject, TAtribute]) affectsDeletedAt() bool { + return condition.Field == DeletedAtField +} + +func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *gorm.DB, tableName string) (string, []any, error) { columnName := condition.Column if columnName == "" { columnName = query.NamingStrategy.ColumnName(tableName, condition.Field) } - columnName = condition.ColumnPrefix + columnName - return fmt.Sprintf( - "%s.%s = ?", - tableName, - columnName, - ), []any{condition.Value} + // add column prefix and table name once we know the column name + columnName = tableName + "." + condition.ColumnPrefix + columnName + + return condition.Operator.ToSQL(columnName) } +// Condition that joins with other table type JoinCondition[T1 any, T2 any] struct { T1Field string T2Field string @@ -96,10 +123,15 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, previousTableName conditionsValues := []any{} isDeletedAtConditionPresent := false for _, condition := range whereConditions { - if condition.Field == DeletedAtField { + if condition.affectsDeletedAt() { isDeletedAtConditionPresent = true } - sql, values := condition.GetSQL(query, nextTableName) + + sql, values, err := condition.GetSQL(query, nextTableName) + if err != nil { + return nil, err + } + joinQuery += " AND " + sql conditionsValues = append(conditionsValues, values...) } diff --git a/orm/operator.go b/orm/operator.go new file mode 100644 index 00000000..f00100e2 --- /dev/null +++ b/orm/operator.go @@ -0,0 +1,61 @@ +package orm + +type Operator[T any] interface { + // Transform the Operator to a SQL string and a list of values to use in the query + // columnName is used by the operator to determine which is the objective column. + ToSQL(columnName string) (string, []any, error) + + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T], + // since if no method receives by parameter a type T, + // any other Operator[T2] would also be considered a Operator[T]. + InterfaceVerificationMethod(T) +} + +// Operator that compares the value of the column against a fixed value +// If Operations has multiple entries, operations will be nested +// Example (single): value = v1 +// Example (multi): value LIKE v1 ESCAPE v2 +type ValueOperator[T any] struct { + Operations []operation +} + +type operation struct { + SQLOperator string + Value any +} + +func (operator ValueOperator[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T] +} + +func (operator ValueOperator[T]) ToSQL(columnName string) (string, []any, error) { + operatorString := columnName + values := []any{} + + for _, operation := range operator.Operations { + operatorString += " " + operation.SQLOperator + " ?" + values = append(values, operation.Value) + } + + return operatorString, values, nil +} + +func NewValueOperator[T any](sqlOperator string, value any) ValueOperator[T] { + operator := ValueOperator[T]{} + + return operator.AddOperation(sqlOperator, value) +} + +func (operator *ValueOperator[T]) AddOperation(sqlOperator string, value any) ValueOperator[T] { + operator.Operations = append( + operator.Operations, + operation{ + Value: value, + SQLOperator: sqlOperator, + }, + ) + + return *operator +} diff --git a/orm/operators.go b/orm/operators.go new file mode 100644 index 00000000..bb92d502 --- /dev/null +++ b/orm/operators.go @@ -0,0 +1,9 @@ +package orm + +// Comparison Operators +// ref: https://www.postgresql.org/docs/current/functions-comparison.html + +// EqualTo +func Eq[T any](value T) Operator[T] { + return NewValueOperator[T]("=", value) +} diff --git a/persistence/models/User.go b/persistence/models/User.go index 750399cf..0a38a6ed 100644 --- a/persistence/models/User.go +++ b/persistence/models/User.go @@ -12,9 +12,9 @@ type User struct { Password []byte `gorm:"not null"` } -func UserEmailCondition(email string) orm.Condition[User] { - return orm.WhereCondition[User]{ - Field: "email", - Value: email, +func UserEmailCondition(operator orm.Operator[string]) orm.WhereCondition[User] { + return orm.FieldCondition[User, string]{ + Operator: operator, + Field: "Email", } } diff --git a/services/userservice/userservice.go b/services/userservice/userservice.go index 821dbe44..24387de2 100644 --- a/services/userservice/userservice.go +++ b/services/userservice/userservice.go @@ -75,7 +75,7 @@ func (userService *userServiceImpl) NewUser(username, email, password string) (* func (userService *userServiceImpl) GetUser(userLoginDTO dto.UserLoginDTO) (*models.User, error) { user, err := userService.userRepository.QueryOne( userService.db, - models.UserEmailCondition(userLoginDTO.Email), + models.UserEmailCondition(orm.Eq(userLoginDTO.Email)), ) if err != nil { return nil, err diff --git a/services/userservice/userservice_test.go b/services/userservice/userservice_test.go index eb65ff63..e76cc2cf 100644 --- a/services/userservice/userservice_test.go +++ b/services/userservice/userservice_test.go @@ -99,7 +99,7 @@ func TestGetUser(t *testing.T) { require.NoError(t, err) userRepositoryMock.On( - "QueryOne", gormDB, models.UserEmailCondition("bob@email.com"), + "QueryOne", gormDB, models.UserEmailCondition(orm.Eq("bob@email.com")), ).Return( user, nil, @@ -127,7 +127,7 @@ func TestGetUserNoUserFound(t *testing.T) { userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) userRepositoryMock.On( - "QueryOne", gormDB, models.UserEmailCondition("bobnotfound@email.com"), + "QueryOne", gormDB, models.UserEmailCondition(orm.Eq("bobnotfound@email.com")), ).Return( &models.User{}, orm.ErrObjectNotFound, @@ -154,7 +154,7 @@ func TestGetUserWrongPassword(t *testing.T) { require.NoError(t, err) userRepositoryMock.On( - "QueryOne", gormDB, models.UserEmailCondition("bob@email.com"), + "QueryOne", gormDB, models.UserEmailCondition(orm.Eq("bob@email.com")), ).Return( user, nil, diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go index 427ad967..b16e5773 100644 --- a/testintegration/conditions/bicycle_conditions.go +++ b/testintegration/conditions/bicycle_conditions.go @@ -4,38 +4,37 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func BicycleId(v orm.UUID) orm.WhereCondition[models.Bicycle] { - return orm.WhereCondition[models.Bicycle]{ - Field: "ID", - Value: v, +func BicycleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, orm.UUID]{ + Field: "ID", + Operator: operator, } } -func BicycleCreatedAt(v time.Time) orm.WhereCondition[models.Bicycle] { - return orm.WhereCondition[models.Bicycle]{ - Field: "CreatedAt", - Value: v, +func BicycleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func BicycleUpdatedAt(v time.Time) orm.WhereCondition[models.Bicycle] { - return orm.WhereCondition[models.Bicycle]{ - Field: "UpdatedAt", - Value: v, +func BicycleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func BicycleDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Bicycle] { - return orm.WhereCondition[models.Bicycle]{ - Field: "DeletedAt", - Value: v, +func BicycleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func BicycleName(v string) orm.WhereCondition[models.Bicycle] { - return orm.WhereCondition[models.Bicycle]{ - Field: "Name", - Value: v, +func BicycleName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, string]{ + Field: "Name", + Operator: operator, } } func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.Condition[models.Bicycle] { @@ -45,9 +44,9 @@ func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.Condition[mode T2Field: "Name", } } -func BicycleOwnerName(v string) orm.WhereCondition[models.Bicycle] { - return orm.WhereCondition[models.Bicycle]{ - Field: "OwnerName", - Value: v, +func BicycleOwnerName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { + return orm.FieldCondition[models.Bicycle, string]{ + Field: "OwnerName", + Operator: operator, } } diff --git a/testintegration/conditions/brand_conditions.go b/testintegration/conditions/brand_conditions.go index 42111242..9d3421ab 100644 --- a/testintegration/conditions/brand_conditions.go +++ b/testintegration/conditions/brand_conditions.go @@ -4,37 +4,36 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func BrandId(v uint) orm.WhereCondition[models.Brand] { - return orm.WhereCondition[models.Brand]{ - Field: "ID", - Value: v, +func BrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, uint]{ + Field: "ID", + Operator: operator, } } -func BrandCreatedAt(v time.Time) orm.WhereCondition[models.Brand] { - return orm.WhereCondition[models.Brand]{ - Field: "CreatedAt", - Value: v, +func BrandCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func BrandUpdatedAt(v time.Time) orm.WhereCondition[models.Brand] { - return orm.WhereCondition[models.Brand]{ - Field: "UpdatedAt", - Value: v, +func BrandUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func BrandDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Brand] { - return orm.WhereCondition[models.Brand]{ - Field: "DeletedAt", - Value: v, +func BrandDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func BrandName(v string) orm.WhereCondition[models.Brand] { - return orm.WhereCondition[models.Brand]{ - Field: "Name", - Value: v, +func BrandName(operator orm.Operator[string]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, string]{ + Field: "Name", + Operator: operator, } } diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go index 8b68782e..1f0218ff 100644 --- a/testintegration/conditions/city_conditions.go +++ b/testintegration/conditions/city_conditions.go @@ -4,43 +4,42 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func CityId(v orm.UUID) orm.WhereCondition[models.City] { - return orm.WhereCondition[models.City]{ - Field: "ID", - Value: v, +func CityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, orm.UUID]{ + Field: "ID", + Operator: operator, } } -func CityCreatedAt(v time.Time) orm.WhereCondition[models.City] { - return orm.WhereCondition[models.City]{ - Field: "CreatedAt", - Value: v, +func CityCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func CityUpdatedAt(v time.Time) orm.WhereCondition[models.City] { - return orm.WhereCondition[models.City]{ - Field: "UpdatedAt", - Value: v, +func CityUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func CityDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.City] { - return orm.WhereCondition[models.City]{ - Field: "DeletedAt", - Value: v, +func CityDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func CityName(v string) orm.WhereCondition[models.City] { - return orm.WhereCondition[models.City]{ - Field: "Name", - Value: v, +func CityName(operator orm.Operator[string]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, string]{ + Field: "Name", + Operator: operator, } } -func CityCountryId(v orm.UUID) orm.WhereCondition[models.City] { - return orm.WhereCondition[models.City]{ - Field: "CountryID", - Value: v, +func CityCountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { + return orm.FieldCondition[models.City, orm.UUID]{ + Field: "CountryID", + Operator: operator, } } diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go index 8ce6a3fe..b87db9b2 100644 --- a/testintegration/conditions/company_conditions.go +++ b/testintegration/conditions/company_conditions.go @@ -4,38 +4,37 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func CompanyId(v orm.UUID) orm.WhereCondition[models.Company] { - return orm.WhereCondition[models.Company]{ - Field: "ID", - Value: v, +func CompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, orm.UUID]{ + Field: "ID", + Operator: operator, } } -func CompanyCreatedAt(v time.Time) orm.WhereCondition[models.Company] { - return orm.WhereCondition[models.Company]{ - Field: "CreatedAt", - Value: v, +func CompanyCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func CompanyUpdatedAt(v time.Time) orm.WhereCondition[models.Company] { - return orm.WhereCondition[models.Company]{ - Field: "UpdatedAt", - Value: v, +func CompanyUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func CompanyDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Company] { - return orm.WhereCondition[models.Company]{ - Field: "DeletedAt", - Value: v, +func CompanyDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func CompanyName(v string) orm.WhereCondition[models.Company] { - return orm.WhereCondition[models.Company]{ - Field: "Name", - Value: v, +func CompanyName(operator orm.Operator[string]) orm.WhereCondition[models.Company] { + return orm.FieldCondition[models.Company, string]{ + Field: "Name", + Operator: operator, } } func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[models.Seller] { diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go index 082bbfa3..f00fddf4 100644 --- a/testintegration/conditions/country_conditions.go +++ b/testintegration/conditions/country_conditions.go @@ -4,38 +4,37 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func CountryId(v orm.UUID) orm.WhereCondition[models.Country] { - return orm.WhereCondition[models.Country]{ - Field: "ID", - Value: v, +func CountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, orm.UUID]{ + Field: "ID", + Operator: operator, } } -func CountryCreatedAt(v time.Time) orm.WhereCondition[models.Country] { - return orm.WhereCondition[models.Country]{ - Field: "CreatedAt", - Value: v, +func CountryCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func CountryUpdatedAt(v time.Time) orm.WhereCondition[models.Country] { - return orm.WhereCondition[models.Country]{ - Field: "UpdatedAt", - Value: v, +func CountryUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func CountryDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Country] { - return orm.WhereCondition[models.Country]{ - Field: "DeletedAt", - Value: v, +func CountryDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func CountryName(v string) orm.WhereCondition[models.Country] { - return orm.WhereCondition[models.Country]{ - Field: "Name", - Value: v, +func CountryName(operator orm.Operator[string]) orm.WhereCondition[models.Country] { + return orm.FieldCondition[models.Country, string]{ + Field: "Name", + Operator: operator, } } func CountryCapital(conditions ...orm.Condition[models.City]) orm.Condition[models.Country] { diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go index 5360f201..77860234 100644 --- a/testintegration/conditions/employee_conditions.go +++ b/testintegration/conditions/employee_conditions.go @@ -4,38 +4,37 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func EmployeeId(v orm.UUID) orm.WhereCondition[models.Employee] { - return orm.WhereCondition[models.Employee]{ - Field: "ID", - Value: v, +func EmployeeId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, orm.UUID]{ + Field: "ID", + Operator: operator, } } -func EmployeeCreatedAt(v time.Time) orm.WhereCondition[models.Employee] { - return orm.WhereCondition[models.Employee]{ - Field: "CreatedAt", - Value: v, +func EmployeeCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func EmployeeUpdatedAt(v time.Time) orm.WhereCondition[models.Employee] { - return orm.WhereCondition[models.Employee]{ - Field: "UpdatedAt", - Value: v, +func EmployeeUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func EmployeeDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Employee] { - return orm.WhereCondition[models.Employee]{ - Field: "DeletedAt", - Value: v, +func EmployeeDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func EmployeeName(v string) orm.WhereCondition[models.Employee] { - return orm.WhereCondition[models.Employee]{ - Field: "Name", - Value: v, +func EmployeeName(operator orm.Operator[string]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, string]{ + Field: "Name", + Operator: operator, } } func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.Condition[models.Employee] { @@ -45,9 +44,9 @@ func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.Condition[mo T2Field: "ID", } } -func EmployeeBossId(v orm.UUID) orm.WhereCondition[models.Employee] { - return orm.WhereCondition[models.Employee]{ - Field: "BossID", - Value: v, +func EmployeeBossId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { + return orm.FieldCondition[models.Employee, orm.UUID]{ + Field: "BossID", + Operator: operator, } } diff --git a/testintegration/conditions/person_conditions.go b/testintegration/conditions/person_conditions.go index 1d99b02b..c378abe7 100644 --- a/testintegration/conditions/person_conditions.go +++ b/testintegration/conditions/person_conditions.go @@ -4,37 +4,36 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func PersonId(v orm.UUID) orm.WhereCondition[models.Person] { - return orm.WhereCondition[models.Person]{ - Field: "ID", - Value: v, +func PersonId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, orm.UUID]{ + Field: "ID", + Operator: operator, } } -func PersonCreatedAt(v time.Time) orm.WhereCondition[models.Person] { - return orm.WhereCondition[models.Person]{ - Field: "CreatedAt", - Value: v, +func PersonCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func PersonUpdatedAt(v time.Time) orm.WhereCondition[models.Person] { - return orm.WhereCondition[models.Person]{ - Field: "UpdatedAt", - Value: v, +func PersonUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func PersonDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Person] { - return orm.WhereCondition[models.Person]{ - Field: "DeletedAt", - Value: v, +func PersonDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func PersonName(v string) orm.WhereCondition[models.Person] { - return orm.WhereCondition[models.Person]{ - Field: "Name", - Value: v, +func PersonName(operator orm.Operator[string]) orm.WhereCondition[models.Person] { + return orm.FieldCondition[models.Person, string]{ + Field: "Name", + Operator: operator, } } diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go index 62abfa92..7766e086 100644 --- a/testintegration/conditions/phone_conditions.go +++ b/testintegration/conditions/phone_conditions.go @@ -4,38 +4,37 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func PhoneId(v uint) orm.WhereCondition[models.Phone] { - return orm.WhereCondition[models.Phone]{ - Field: "ID", - Value: v, +func PhoneId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, uint]{ + Field: "ID", + Operator: operator, } } -func PhoneCreatedAt(v time.Time) orm.WhereCondition[models.Phone] { - return orm.WhereCondition[models.Phone]{ - Field: "CreatedAt", - Value: v, +func PhoneCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func PhoneUpdatedAt(v time.Time) orm.WhereCondition[models.Phone] { - return orm.WhereCondition[models.Phone]{ - Field: "UpdatedAt", - Value: v, +func PhoneUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func PhoneDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Phone] { - return orm.WhereCondition[models.Phone]{ - Field: "DeletedAt", - Value: v, +func PhoneDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func PhoneName(v string) orm.WhereCondition[models.Phone] { - return orm.WhereCondition[models.Phone]{ - Field: "Name", - Value: v, +func PhoneName(operator orm.Operator[string]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, string]{ + Field: "Name", + Operator: operator, } } func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.Condition[models.Phone] { @@ -45,9 +44,9 @@ func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.Condition[models. T2Field: "ID", } } -func PhoneBrandId(v uint) orm.WhereCondition[models.Phone] { - return orm.WhereCondition[models.Phone]{ - Field: "BrandID", - Value: v, +func PhoneBrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, uint]{ + Field: "BrandID", + Operator: operator, } } diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go index ee037789..ebf9e75f 100644 --- a/testintegration/conditions/product_conditions.go +++ b/testintegration/conditions/product_conditions.go @@ -4,86 +4,91 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func ProductId(v orm.UUID) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "ID", - Value: v, +func ProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, orm.UUID]{ + Field: "ID", + Operator: operator, } } -func ProductCreatedAt(v time.Time) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "CreatedAt", - Value: v, +func ProductCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func ProductUpdatedAt(v time.Time) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "UpdatedAt", - Value: v, +func ProductUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func ProductDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "DeletedAt", - Value: v, +func ProductDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func ProductString(v string) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Column: "string_something_else", - Value: v, +func ProductString(operator orm.Operator[string]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, string]{ + Column: "string_something_else", + Operator: operator, } } -func ProductInt(v int) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "Int", - Value: v, +func ProductInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, int]{ + Field: "Int", + Operator: operator, } } -func ProductIntPointer(v int) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "IntPointer", - Value: v, +func ProductIntPointer(operator orm.Operator[int]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, int]{ + Field: "IntPointer", + Operator: operator, } } -func ProductFloat(v float64) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "Float", - Value: v, +func ProductFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, float64]{ + Field: "Float", + Operator: operator, } } -func ProductBool(v bool) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "Bool", - Value: v, +func ProductNullFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, float64]{ + Field: "NullFloat", + Operator: operator, } } -func ProductByteArray(v []uint8) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "ByteArray", - Value: v, +func ProductBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, bool]{ + Field: "Bool", + Operator: operator, } } -func ProductMultiString(v models.MultiString) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "MultiString", - Value: v, +func ProductByteArray(operator orm.Operator[[]uint8]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, []uint8]{ + Field: "ByteArray", + Operator: operator, } } -func ProductEmbeddedInt(v int) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ - Field: "EmbeddedInt", - Value: v, +func ProductMultiString(operator orm.Operator[models.MultiString]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, models.MultiString]{ + Field: "MultiString", + Operator: operator, } } -func ProductGormEmbeddedInt(v int) orm.WhereCondition[models.Product] { - return orm.WhereCondition[models.Product]{ +func ProductEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, int]{ + Field: "EmbeddedInt", + Operator: operator, + } +} +func ProductGormEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, int]{ ColumnPrefix: "gorm_embedded_", Field: "Int", - Value: v, + Operator: operator, } } diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go index d4460abe..6e49ebcf 100644 --- a/testintegration/conditions/sale_conditions.go +++ b/testintegration/conditions/sale_conditions.go @@ -4,44 +4,43 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func SaleId(v orm.UUID) orm.WhereCondition[models.Sale] { - return orm.WhereCondition[models.Sale]{ - Field: "ID", - Value: v, +func SaleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, orm.UUID]{ + Field: "ID", + Operator: operator, } } -func SaleCreatedAt(v time.Time) orm.WhereCondition[models.Sale] { - return orm.WhereCondition[models.Sale]{ - Field: "CreatedAt", - Value: v, +func SaleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func SaleUpdatedAt(v time.Time) orm.WhereCondition[models.Sale] { - return orm.WhereCondition[models.Sale]{ - Field: "UpdatedAt", - Value: v, +func SaleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func SaleDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Sale] { - return orm.WhereCondition[models.Sale]{ - Field: "DeletedAt", - Value: v, +func SaleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func SaleCode(v int) orm.WhereCondition[models.Sale] { - return orm.WhereCondition[models.Sale]{ - Field: "Code", - Value: v, +func SaleCode(operator orm.Operator[int]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, int]{ + Field: "Code", + Operator: operator, } } -func SaleDescription(v string) orm.WhereCondition[models.Sale] { - return orm.WhereCondition[models.Sale]{ - Field: "Description", - Value: v, +func SaleDescription(operator orm.Operator[string]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, string]{ + Field: "Description", + Operator: operator, } } func SaleProduct(conditions ...orm.Condition[models.Product]) orm.Condition[models.Sale] { @@ -51,10 +50,10 @@ func SaleProduct(conditions ...orm.Condition[models.Product]) orm.Condition[mode T2Field: "ID", } } -func SaleProductId(v orm.UUID) orm.WhereCondition[models.Sale] { - return orm.WhereCondition[models.Sale]{ - Field: "ProductID", - Value: v, +func SaleProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, orm.UUID]{ + Field: "ProductID", + Operator: operator, } } func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.Condition[models.Sale] { @@ -64,9 +63,9 @@ func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.Condition[models T2Field: "ID", } } -func SaleSellerId(v orm.UUID) orm.WhereCondition[models.Sale] { - return orm.WhereCondition[models.Sale]{ - Field: "SellerID", - Value: v, +func SaleSellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { + return orm.FieldCondition[models.Sale, orm.UUID]{ + Field: "SellerID", + Operator: operator, } } diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go index acb5860d..5390f2d5 100644 --- a/testintegration/conditions/seller_conditions.go +++ b/testintegration/conditions/seller_conditions.go @@ -4,43 +4,42 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" "time" ) -func SellerId(v orm.UUID) orm.WhereCondition[models.Seller] { - return orm.WhereCondition[models.Seller]{ - Field: "ID", - Value: v, +func SellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, orm.UUID]{ + Field: "ID", + Operator: operator, } } -func SellerCreatedAt(v time.Time) orm.WhereCondition[models.Seller] { - return orm.WhereCondition[models.Seller]{ - Field: "CreatedAt", - Value: v, +func SellerCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, time.Time]{ + Field: "CreatedAt", + Operator: operator, } } -func SellerUpdatedAt(v time.Time) orm.WhereCondition[models.Seller] { - return orm.WhereCondition[models.Seller]{ - Field: "UpdatedAt", - Value: v, +func SellerUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, time.Time]{ + Field: "UpdatedAt", + Operator: operator, } } -func SellerDeletedAt(v gorm.DeletedAt) orm.WhereCondition[models.Seller] { - return orm.WhereCondition[models.Seller]{ - Field: "DeletedAt", - Value: v, +func SellerDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, time.Time]{ + Field: "DeletedAt", + Operator: operator, } } -func SellerName(v string) orm.WhereCondition[models.Seller] { - return orm.WhereCondition[models.Seller]{ - Field: "Name", - Value: v, +func SellerName(operator orm.Operator[string]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, string]{ + Field: "Name", + Operator: operator, } } -func SellerCompanyId(v orm.UUID) orm.WhereCondition[models.Seller] { - return orm.WhereCondition[models.Seller]{ - Field: "CompanyID", - Value: v, +func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, orm.UUID]{ + Field: "CompanyID", + Operator: operator, } } diff --git a/testintegration/crudRepository.go b/testintegration/crudRepository.go index ceda4b0f..f81bed76 100644 --- a/testintegration/crudRepository.go +++ b/testintegration/crudRepository.go @@ -55,13 +55,19 @@ func (ts *CRUDRepositoryIntTestSuite) TestGetByIDReturnsEntityIfIDMatch() { func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsErrorIfConditionsDontMatch() { ts.createProduct(0) - _, err := ts.crudProductRepository.QueryOne(ts.db, conditions.ProductInt(1)) + _, err := ts.crudProductRepository.QueryOne( + ts.db, + conditions.ProductInt(orm.Eq(1)), + ) ts.Error(err, gorm.ErrRecordNotFound) } func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsEntityIfConditionsMatch() { product := ts.createProduct(1) - productReturned, err := ts.crudProductRepository.QueryOne(ts.db, conditions.ProductInt(1)) + productReturned, err := ts.crudProductRepository.QueryOne( + ts.db, + conditions.ProductInt(orm.Eq(1)), + ) ts.Nil(err) assert.DeepEqual(ts.T(), product, productReturned) @@ -70,7 +76,10 @@ func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsEntityIfConditionsMatch func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsErrorIfMoreThanOneMatchConditions() { ts.createProduct(0) ts.createProduct(0) - _, err := ts.crudProductRepository.QueryOne(ts.db, conditions.ProductInt(0)) + _, err := ts.crudProductRepository.QueryOne( + ts.db, + conditions.ProductInt(orm.Eq(0)), + ) ts.Error(err, orm.ErrMoreThanOneObjectFound) } diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index 0ce3ef4b..08f92d93 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -43,7 +43,7 @@ func NewJoinConditionsIntTestSuite( } } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsUintBelongsTo() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsUintBelongsTo() { brand1 := ts.createBrand("google") brand2 := ts.createBrand("apple") @@ -52,7 +52,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsUintBelongs entities, err := ts.crudPhoneService.Query( conditions.PhoneBrand( - conditions.BrandName("google"), + conditions.BrandName(orm.Eq("google")), ), ) ts.Nil(err) @@ -60,7 +60,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsUintBelongs EqualList(&ts.Suite, []*models.Phone{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsBelongsTo() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsBelongsTo() { product1 := ts.createProduct("", 1, 0.0, false, nil) product2 := ts.createProduct("", 2, 0.0, false, nil) @@ -69,7 +69,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsBelongsTo() entities, err := ts.crudSaleService.Query( conditions.SaleProduct( - conditions.ProductInt(1), + conditions.ProductInt(orm.Eq(1)), ), ) ts.Nil(err) @@ -77,7 +77,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsBelongsTo() EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsAndFiltersTheMainEntity() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsAndFiltersTheMainEntity() { product1 := ts.createProduct("", 1, 0.0, false, nil) product2 := ts.createProduct("", 2, 0.0, false, nil) @@ -89,9 +89,9 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsAndFiltersT ts.createSale(2, product1, seller2) entities, err := ts.crudSaleService.Query( - conditions.SaleCode(1), + conditions.SaleCode(orm.Eq(1)), conditions.SaleProduct( - conditions.ProductInt(1), + conditions.ProductInt(orm.Eq(1)), ), ) ts.Nil(err) @@ -99,7 +99,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsAndFiltersT EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsHasOneOptional() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsHasOneOptional() { product1 := ts.createProduct("", 1, 0.0, false, nil) product2 := ts.createProduct("", 2, 0.0, false, nil) @@ -111,7 +111,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsHasOneOptio entities, err := ts.crudSaleService.Query( conditions.SaleSeller( - conditions.SellerName("franco"), + conditions.SellerName(orm.Eq("franco")), ), ) ts.Nil(err) @@ -119,7 +119,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsHasOneOptio EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsHasOneSelfReferential() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsHasOneSelfReferential() { boss1 := &models.Employee{ Name: "Xavier", } @@ -132,7 +132,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsHasOneSelfR entities, err := ts.crudEmployeeService.Query( conditions.EmployeeBoss( - conditions.EmployeeName("Xavier"), + conditions.EmployeeName(orm.Eq("Xavier")), ), ) ts.Nil(err) @@ -140,7 +140,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsHasOneSelfR EqualList(&ts.Suite, []*models.Employee{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOneToOne() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOneToOne() { capital1 := models.City{ Name: "Buenos Aires", } @@ -153,7 +153,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOneToOne() entities, err := ts.crudCityService.Query( conditions.CityCountry( - conditions.CountryName("Argentina"), + conditions.CountryName(orm.Eq("Argentina")), ), ) ts.Nil(err) @@ -161,7 +161,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOneToOne() EqualList(&ts.Suite, []*models.City{&capital1}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOneToOneReversed() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOneToOneReversed() { capital1 := models.City{ Name: "Buenos Aires", } @@ -174,7 +174,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOneToOneRev entities, err := ts.crudCountryService.Query( conditions.CountryCapital( - conditions.CityName("Buenos Aires"), + conditions.CityName(orm.Eq("Buenos Aires")), ), ) ts.Nil(err) @@ -182,7 +182,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOneToOneRev EqualList(&ts.Suite, []*models.Country{country1}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsWithEntityThatDefinesTableName() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsWithEntityThatDefinesTableName() { person1 := models.Person{ Name: "franco", } @@ -195,7 +195,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsWithEntityT entities, err := ts.crudBicycleService.Query( conditions.BicycleOwner( - conditions.PersonName("franco"), + conditions.PersonName(orm.Eq("franco")), ), ) ts.Nil(err) @@ -203,7 +203,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsWithEntityT EqualList(&ts.Suite, []*models.Bicycle{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnHasMany() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnHasMany() { company1 := ts.createCompany("ditrit") company2 := ts.createCompany("orness") @@ -212,7 +212,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnHasMany() entities, err := ts.crudSellerService.Query( conditions.SellerCompany( - conditions.CompanyName("ditrit"), + conditions.CompanyName(orm.Eq("ditrit")), ), ) ts.Nil(err) @@ -220,7 +220,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnHasMany() EqualList(&ts.Suite, []*models.Seller{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnDifferentAttributes() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnDifferentAttributes() { product1 := ts.createProduct("match", 1, 0.0, false, nil) product2 := ts.createProduct("match", 2, 0.0, false, nil) @@ -232,8 +232,8 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnDifferent entities, err := ts.crudSaleService.Query( conditions.SaleProduct( - conditions.ProductInt(1), - conditions.ProductString("match"), + conditions.ProductInt(orm.Eq(1)), + conditions.ProductString(orm.Eq("match")), ), ) ts.Nil(err) @@ -241,7 +241,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnDifferent EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsAddsDeletedAtAutomatically() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsAddsDeletedAtAutomatically() { product1 := ts.createProduct("match", 1, 0.0, false, nil) product2 := ts.createProduct("match", 2, 0.0, false, nil) @@ -255,7 +255,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsAddsDeleted entities, err := ts.crudSaleService.Query( conditions.SaleProduct( - conditions.ProductString("match"), + conditions.ProductString(orm.Eq("match")), ), ) ts.Nil(err) @@ -263,7 +263,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsAddsDeleted EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnDeletedAt() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnDeletedAt() { product1 := ts.createProduct("match", 1, 0.0, false, nil) product2 := ts.createProduct("match", 2, 0.0, false, nil) @@ -277,7 +277,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnDeletedAt entities, err := ts.crudSaleService.Query( conditions.SaleProduct( - conditions.ProductDeletedAt(product1.DeletedAt), + conditions.ProductDeletedAt(orm.Eq(product1.DeletedAt.Time)), ), ) ts.Nil(err) @@ -285,7 +285,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsOnDeletedAt EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsDifferentEntities() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsDifferentEntities() { product1 := ts.createProduct("", 1, 0.0, false, nil) product2 := ts.createProduct("", 2, 0.0, false, nil) @@ -299,10 +299,10 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsDifferentEn entities, err := ts.crudSaleService.Query( conditions.SaleProduct( - conditions.ProductInt(1), + conditions.ProductInt(orm.Eq(1)), ), conditions.SaleSeller( - conditions.SellerName("franco"), + conditions.SellerName(orm.Eq("franco")), ), ) ts.Nil(err) @@ -310,7 +310,7 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsDifferentEn EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsMultipleTimes() { +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsMultipleTimes() { product1 := ts.createProduct("", 0, 0.0, false, nil) product2 := ts.createProduct("", 0, 0.0, false, nil) @@ -325,9 +325,9 @@ func (ts *JoinConditionsIntTestSuite) TestQueryWithConditionThatJoinsMultipleTim entities, err := ts.crudSaleService.Query( conditions.SaleSeller( - conditions.SellerName("franco"), + conditions.SellerName(orm.Eq("franco")), conditions.SellerCompany( - conditions.CompanyName("ditrit"), + conditions.CompanyName(orm.Eq("ditrit")), ), ), ) diff --git a/testintegration/models/models.go b/testintegration/models/models.go index 8979faa3..b281f6cb 100644 --- a/testintegration/models/models.go +++ b/testintegration/models/models.go @@ -1,6 +1,7 @@ package models import ( + "database/sql" "database/sql/driver" "fmt" "strings" @@ -69,6 +70,7 @@ type Product struct { Int int IntPointer *int Float float64 + NullFloat sql.NullFloat64 Bool bool ByteArray []byte MultiString MultiString diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go new file mode 100644 index 00000000..11955883 --- /dev/null +++ b/testintegration/operators_test.go @@ -0,0 +1,70 @@ +package testintegration + +import ( + "database/sql" + + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type OperatorsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudProductService orm.CRUDService[models.Product, orm.UUID] +} + +func NewOperatorsIntTestSuite( + db *gorm.DB, + crudProductService orm.CRUDService[models.Product, orm.UUID], +) *OperatorsIntTestSuite { + return &OperatorsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudProductService: crudProductService, + } +} + +func (ts *OperatorsIntTestSuite) TestEqPointers() { + intMatch := 1 + match := ts.createProduct("match", 1, 0, false, &intMatch) + + intNotMatch := 2 + ts.createProduct("match", 3, 0, false, &intNotMatch) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + orm.Eq(1), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestEqNullableType() { + match := ts.createProduct("match", 0, 0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 1.3} + err := ts.db.Save(match).Error + ts.Nil(err) + + notMatch1 := ts.createProduct("not_match", 3, 0, false, nil) + notMatch1.NullFloat = sql.NullFloat64{Valid: true, Float64: 1.2} + err = ts.db.Save(notMatch1).Error + ts.Nil(err) + + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + orm.Eq(1.3), + ), + ) + + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} diff --git a/testintegration/orm_test.go b/testintegration/orm_test.go index fb6e2a84..67953f65 100644 --- a/testintegration/orm_test.go +++ b/testintegration/orm_test.go @@ -63,6 +63,7 @@ func TestBaDaaSORM(t *testing.T) { fx.Provide(NewCRUDRepositoryIntTestSuite), fx.Provide(NewWhereConditionsIntTestSuite), fx.Provide(NewJoinConditionsIntTestSuite), + fx.Provide(NewOperatorsIntTestSuite), // run tests fx.Invoke(runORMTestSuites), @@ -73,12 +74,14 @@ func runORMTestSuites( tsCRUDRepository *CRUDRepositoryIntTestSuite, tsWhereConditions *WhereConditionsIntTestSuite, tsJoinConditions *JoinConditionsIntTestSuite, + tsOperators *OperatorsIntTestSuite, db *gorm.DB, shutdowner fx.Shutdowner, ) { suite.Run(tGlobal, tsCRUDRepository) suite.Run(tGlobal, tsWhereConditions) suite.Run(tGlobal, tsJoinConditions) + suite.Run(tGlobal, tsOperators) shutdowner.Shutdown() } diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index a1abf326..1172f9c9 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -4,8 +4,6 @@ import ( "gorm.io/gorm" "gotest.tools/assert" - "github.com/google/uuid" - "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" @@ -44,7 +42,7 @@ func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsErrorIfNotEntityCreated func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsErrorIfNotEntityMatch() { ts.createProduct("", 0, 0, false, nil) - _, err := ts.crudProductService.GetByID(orm.UUID(uuid.New())) + _, err := ts.crudProductService.GetByID(orm.NewUUID()) ts.Error(err, gorm.ErrRecordNotFound) } @@ -59,14 +57,14 @@ func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsTheEntityIfItIsCreate() // ------------------------- Query -------------------------------- -func (ts *WhereConditionsIntTestSuite) TestQueryWithoutConditionsReturnsEmptyIfNotEntitiesCreated() { +func (ts *WhereConditionsIntTestSuite) TestQueryReturnsEmptyIfNotEntitiesCreated() { entities, err := ts.crudProductService.Query() ts.Nil(err) EqualList(&ts.Suite, []*models.Product{}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithoutConditionsReturnsTheOnlyOneIfOneEntityCreated() { +func (ts *WhereConditionsIntTestSuite) TestQueryReturnsTheOnlyOneIfOneEntityCreated() { match := ts.createProduct("", 0, 0, false, nil) entities, err := ts.crudProductService.Query() @@ -75,7 +73,7 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithoutConditionsReturnsTheOnlyO EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithoutConditionsReturnsTheListWhenMultipleCreated() { +func (ts *WhereConditionsIntTestSuite) TestQueryReturnsTheListWhenMultipleCreated() { match1 := ts.createProduct("", 0, 0, false, nil) match2 := ts.createProduct("", 0, 0, false, nil) match3 := ts.createProduct("", 0, 0, false, nil) @@ -86,88 +84,102 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithoutConditionsReturnsTheListW EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsReturnsEmptyIfNotEntitiesCreated() { +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsEmptyIfNotEntitiesCreated() { entities, err := ts.crudProductService.Query( - conditions.ProductString("not_created"), + conditions.ProductString( + orm.Eq("not_created"), + ), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsReturnsEmptyIfNothingMatch() { +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsEmptyIfNothingMatch() { ts.createProduct("something_else", 0, 0, false, nil) entities, err := ts.crudProductService.Query( - conditions.ProductString("not_match"), + conditions.ProductString( + orm.Eq("not_match"), + ), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsReturnsOneIfOnlyOneMatch() { +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsOneIfOnlyOneMatch() { match := ts.createProduct("match", 0, 0, false, nil) ts.createProduct("not_match", 0, 0, false, nil) entities, err := ts.crudProductService.Query( - conditions.ProductString("match"), + conditions.ProductString( + orm.Eq("match"), + ), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsReturnsMultipleIfMultipleMatch() { +func (ts *WhereConditionsIntTestSuite) TestConditionsReturnsMultipleIfMultipleMatch() { match1 := ts.createProduct("match", 0, 0, false, nil) match2 := ts.createProduct("match", 0, 0, false, nil) ts.createProduct("not_match", 0, 0, false, nil) entities, err := ts.crudProductService.Query( - conditions.ProductString("match"), + conditions.ProductString( + orm.Eq("match"), + ), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfIntType() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfIntType() { match := ts.createProduct("match", 1, 0, false, nil) ts.createProduct("not_match", 2, 0, false, nil) entities, err := ts.crudProductService.Query( - conditions.ProductInt(1), + conditions.ProductInt( + orm.Eq(1), + ), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfFloatType() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfFloatType() { match := ts.createProduct("match", 0, 1.1, false, nil) ts.createProduct("not_match", 0, 2.2, false, nil) entities, err := ts.crudProductService.Query( - conditions.ProductFloat(1.1), + conditions.ProductFloat( + orm.Eq(1.1), + ), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfBoolType() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfBoolType() { match := ts.createProduct("match", 0, 0.0, true, nil) ts.createProduct("not_match", 0, 0.0, false, nil) entities, err := ts.crudProductService.Query( - conditions.ProductBool(true), + conditions.ProductBool( + orm.Eq(true), + ), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithMultipleConditionsOfDifferentTypesWorks() { +func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsOfDifferentTypesWorks() { match1 := ts.createProduct("match", 1, 0.0, true, nil) match2 := ts.createProduct("match", 1, 0.0, true, nil) @@ -175,40 +187,42 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithMultipleConditionsOfDifferen ts.createProduct("match", 2, 0.0, true, nil) entities, err := ts.crudProductService.Query( - conditions.ProductString("match"), - conditions.ProductInt(1), - conditions.ProductBool(true), + conditions.ProductString(orm.Eq("match")), + conditions.ProductInt(orm.Eq(1)), + conditions.ProductBool(orm.Eq(true)), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfID() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfID() { match := ts.createProduct("", 0, 0.0, false, nil) ts.createProduct("", 0, 0.0, false, nil) entities, err := ts.crudProductService.Query( - conditions.ProductId(match.ID), + conditions.ProductId( + orm.Eq(match.ID), + ), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfCreatedAt() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfCreatedAt() { match := ts.createProduct("", 0, 0.0, false, nil) ts.createProduct("", 0, 0.0, false, nil) entities, err := ts.crudProductService.Query( - conditions.ProductCreatedAt(match.CreatedAt), + conditions.ProductCreatedAt(orm.Eq(match.CreatedAt)), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryDeletedAtConditionIsAddedAutomatically() { +func (ts *WhereConditionsIntTestSuite) TestDeletedAtConditionIsAddedAutomatically() { match := ts.createProduct("", 0, 0.0, false, nil) deleted := ts.createProduct("", 0, 0.0, false, nil) @@ -220,55 +234,55 @@ func (ts *WhereConditionsIntTestSuite) TestQueryDeletedAtConditionIsAddedAutomat EqualList(&ts.Suite, []*models.Product{match}, entities) } -// TODO DeletedAt with nil value but not automatic - -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfDeletedAtNotNil() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfDeletedAt() { match := ts.createProduct("", 0, 0.0, false, nil) ts.createProduct("", 0, 0.0, false, nil) ts.Nil(ts.db.Delete(match).Error) entities, err := ts.crudProductService.Query( - conditions.ProductDeletedAt(match.DeletedAt), + conditions.ProductDeletedAt(orm.Eq(match.DeletedAt.Time)), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfEmbedded() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfEmbedded() { match := ts.createProduct("", 0, 0.0, false, nil) ts.createProduct("", 0, 0.0, false, nil) + match.EmbeddedInt = 1 err := ts.db.Save(match).Error ts.Nil(err) entities, err := ts.crudProductService.Query( - conditions.ProductEmbeddedInt(1), + conditions.ProductEmbeddedInt(orm.Eq(1)), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfGormEmbedded() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfGormEmbedded() { match := ts.createProduct("", 0, 0.0, false, nil) ts.createProduct("", 0, 0.0, false, nil) + match.GormEmbedded.Int = 1 err := ts.db.Save(match).Error ts.Nil(err) entities, err := ts.crudProductService.Query( - conditions.ProductGormEmbeddedInt(1), + conditions.ProductGormEmbeddedInt(orm.Eq(1)), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfPointerTypeWithValue() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfPointerTypeWithValue() { intMatch := 1 match := ts.createProduct("match", 1, 0, false, &intMatch) intNotMatch := 2 @@ -276,17 +290,18 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfPointerTypeWithVa ts.createProduct("not_match", 2, 0, false, nil) entities, err := ts.crudProductService.Query( - conditions.ProductIntPointer(intMatch), + conditions.ProductIntPointer(orm.Eq(1)), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfByteArrayWithContent() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfByteArrayWithContent() { match := ts.createProduct("match", 1, 0, false, nil) notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) ts.createProduct("not_match", 2, 0, false, nil) + match.ByteArray = []byte{1, 2} notMatch1.ByteArray = []byte{2, 3} @@ -297,17 +312,18 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfByteArrayWithCont ts.Nil(err) entities, err := ts.crudProductService.Query( - conditions.ProductByteArray([]byte{1, 2}), + conditions.ProductByteArray(orm.Eq([]byte{1, 2})), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfByteArrayEmpty() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfByteArrayEmpty() { match := ts.createProduct("match", 1, 0, false, nil) notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) ts.createProduct("not_match", 2, 0, false, nil) + match.ByteArray = []byte{} notMatch1.ByteArray = []byte{2, 3} @@ -318,7 +334,7 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfByteArrayEmpty() ts.Nil(err) entities, err := ts.crudProductService.Query( - conditions.ProductByteArray([]byte{}), + conditions.ProductByteArray(orm.Eq([]byte{})), ) ts.Nil(err) @@ -329,6 +345,7 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfCustomType() { match := ts.createProduct("match", 1, 0, false, nil) notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) ts.createProduct("not_match", 2, 0, false, nil) + match.MultiString = models.MultiString{"salut", "hola"} notMatch1.MultiString = models.MultiString{"salut", "hola", "hello"} @@ -339,14 +356,14 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfCustomType() { ts.Nil(err) entities, err := ts.crudProductService.Query( - conditions.ProductMultiString(models.MultiString{"salut", "hola"}), + conditions.ProductMultiString(orm.Eq(models.MultiString{"salut", "hola"})), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfRelationType() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationType() { product1 := ts.createProduct("", 0, 0.0, false, nil) product2 := ts.createProduct("", 0, 0.0, false, nil) @@ -357,14 +374,14 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfRelationType() { ts.createSale(0, product2, seller2) entities, err := ts.crudSaleService.Query( - conditions.SaleProductId(product1.ID), + conditions.SaleProductId(orm.Eq(product1.ID)), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfRelationTypeOptionalWithValue() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationTypeOptionalWithValue() { product1 := ts.createProduct("", 0, 0.0, false, nil) product2 := ts.createProduct("", 0, 0.0, false, nil) @@ -375,19 +392,19 @@ func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfRelationTypeOptio ts.createSale(0, product2, seller2) entities, err := ts.crudSaleService.Query( - conditions.SaleSellerId(seller1.ID), + conditions.SaleSellerId(orm.Eq(seller1.ID)), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionsOnUIntModel() { +func (ts *WhereConditionsIntTestSuite) TestConditionsOnUIntModel() { match := ts.createBrand("match") ts.createBrand("not_match") entities, err := ts.crudBrandService.Query( - conditions.BrandName("match"), + conditions.BrandName(orm.Eq("match")), ) ts.Nil(err) From 76ec3adc1292a6478808351fc146af5a0fe84b08 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:09:40 +0200 Subject: [PATCH 32/90] add noteq --- orm/operators.go | 5 +++++ testintegration/operators_test.go | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index bb92d502..18491e34 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -7,3 +7,8 @@ package orm func Eq[T any](value T) Operator[T] { return NewValueOperator[T]("=", value) } + +// NotEqualTo +func NotEq[T any](value T) Operator[T] { + return NewValueOperator[T]("<>", value) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 11955883..0509b78d 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -68,3 +68,18 @@ func (ts *OperatorsIntTestSuite) TestEqNullableType() { EqualList(&ts.Suite, []*models.Product{match}, entities) } + +func (ts *OperatorsIntTestSuite) TestNotEq() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 3, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.NotEq(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From 48acfb09bfcec7a280ee9ee10fc9363f994086ed Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:11:37 +0200 Subject: [PATCH 33/90] add lt --- orm/operators.go | 5 +++++ testintegration/operators_test.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 18491e34..b06ee7ea 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -12,3 +12,8 @@ func Eq[T any](value T) Operator[T] { func NotEq[T any](value T) Operator[T] { return NewValueOperator[T]("<>", value) } + +// LessThan +func Lt[T any](value T) Operator[T] { + return NewValueOperator[T]("<", value) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 0509b78d..7a4d9f62 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -83,3 +83,19 @@ func (ts *OperatorsIntTestSuite) TestNotEq() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestLt() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.Lt(3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From 50c98cb9fcc744e7c526ffdcd70f991b7f88baa6 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:12:29 +0200 Subject: [PATCH 34/90] add ltoreq --- orm/operators.go | 5 +++++ testintegration/operators_test.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index b06ee7ea..7e3f0e66 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -17,3 +17,8 @@ func NotEq[T any](value T) Operator[T] { func Lt[T any](value T) Operator[T] { return NewValueOperator[T]("<", value) } + +// LessThanOrEqualTo +func LtOrEq[T any](value T) Operator[T] { + return NewValueOperator[T]("<=", value) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 7a4d9f62..438c1802 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -99,3 +99,19 @@ func (ts *OperatorsIntTestSuite) TestLt() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestLtOrEq() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.LtOrEq(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From 5a1f2a9ab745a192dd26fa4250d81e3f26a4b6ff Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:13:11 +0200 Subject: [PATCH 35/90] add gt --- orm/operators.go | 5 +++++ testintegration/operators_test.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 7e3f0e66..a9783db1 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -22,3 +22,8 @@ func Lt[T any](value T) Operator[T] { func LtOrEq[T any](value T) Operator[T] { return NewValueOperator[T]("<=", value) } + +// GreaterThan +func Gt[T any](value T) Operator[T] { + return NewValueOperator[T](">", value) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 438c1802..5737c163 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -115,3 +115,19 @@ func (ts *OperatorsIntTestSuite) TestLtOrEq() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestGt() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.Gt(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From 2533e080aa34e89a778809618192da2ab6436eab Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:13:50 +0200 Subject: [PATCH 36/90] add gtoreq --- orm/operators.go | 5 +++++ testintegration/operators_test.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index a9783db1..f8236142 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -27,3 +27,8 @@ func LtOrEq[T any](value T) Operator[T] { func Gt[T any](value T) Operator[T] { return NewValueOperator[T](">", value) } + +// GreaterThanOrEqualTo +func GtOrEq[T any](value T) Operator[T] { + return NewValueOperator[T](">=", value) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 5737c163..eb43c956 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -131,3 +131,19 @@ func (ts *OperatorsIntTestSuite) TestGt() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestGtOrEq() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.GtOrEq(3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From 7561db88b3f70c6692bb0f0be85bd03b90adef1d Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:15:14 +0200 Subject: [PATCH 37/90] add isnull --- orm/operator.go | 23 +++++++++++++++ orm/operators.go | 7 +++++ testintegration/join_conditions_test.go | 18 ++++++++++++ testintegration/operators_test.go | 36 ++++++++++++++++++++++++ testintegration/where_conditions_test.go | 19 ++++++++++++- 5 files changed, 102 insertions(+), 1 deletion(-) diff --git a/orm/operator.go b/orm/operator.go index f00100e2..b2b83789 100644 --- a/orm/operator.go +++ b/orm/operator.go @@ -1,5 +1,7 @@ package orm +import "fmt" + type Operator[T any] interface { // Transform the Operator to a SQL string and a list of values to use in the query // columnName is used by the operator to determine which is the objective column. @@ -59,3 +61,24 @@ func (operator *ValueOperator[T]) AddOperation(sqlOperator string, value any) Va return *operator } + +// Operator that verifies a predicate +// Example: value IS TRUE +type PredicateOperator[T any] struct { + SQLOperator string +} + +func (operator PredicateOperator[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T] +} + +func (operator PredicateOperator[T]) ToSQL(columnName string) (string, []any, error) { + return fmt.Sprintf("%s %s", columnName, operator.SQLOperator), []any{}, nil +} + +func NewPredicateOperator[T any](sqlOperator string) PredicateOperator[T] { + return PredicateOperator[T]{ + SQLOperator: sqlOperator, + } +} diff --git a/orm/operators.go b/orm/operators.go index f8236142..bbe0f28e 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -32,3 +32,10 @@ func Gt[T any](value T) Operator[T] { func GtOrEq[T any](value T) Operator[T] { return NewValueOperator[T](">=", value) } + +// Comparison Predicates +// refs: https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE + +func IsNull[T any]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS NULL") +} diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index 08f92d93..f036e7e4 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -285,6 +285,24 @@ func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsOnDeletedAt() { EqualList(&ts.Suite, []*models.Sale{match}, entities) } +func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsAndFiltersByNil() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + intProduct2 := 2 + product2 := ts.createProduct("", 2, 0.0, false, &intProduct2) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductIntPointer(orm.IsNull[int]()), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsDifferentEntities() { product1 := ts.createProduct("", 1, 0.0, false, nil) product2 := ts.createProduct("", 2, 0.0, false, nil) diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index eb43c956..6209a66a 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -147,3 +147,39 @@ func (ts *OperatorsIntTestSuite) TestGtOrEq() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestIsNullPointers() { + match := ts.createProduct("match", 0, 0, false, nil) + int1 := 1 + int2 := 2 + + ts.createProduct("not_match", 0, 0, false, &int1) + ts.createProduct("not_match", 0, 0, false, &int2) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + orm.IsNull[int](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNullNullableTypes() { + match := ts.createProduct("match", 0, 0, false, nil) + + notMatch := ts.createProduct("not_match", 0, 0, false, nil) + notMatch.NullFloat = sql.NullFloat64{Valid: true, Float64: 6} + err := ts.db.Save(notMatch).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + orm.IsNull[float64](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index 1172f9c9..ee5e307d 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -341,7 +341,7 @@ func (ts *WhereConditionsIntTestSuite) TestConditionOfByteArrayEmpty() { EqualList(&ts.Suite, []*models.Product{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestQueryWithConditionOfCustomType() { +func (ts *WhereConditionsIntTestSuite) TestConditionOfCustomType() { match := ts.createProduct("match", 1, 0, false, nil) notMatch1 := ts.createProduct("not_match", 2, 0, false, nil) ts.createProduct("not_match", 2, 0, false, nil) @@ -399,6 +399,23 @@ func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationTypeOptionalWithVa EqualList(&ts.Suite, []*models.Sale{match}, entities) } +func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationTypeOptionalByNil() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSellerId(orm.IsNull[orm.UUID]()), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + func (ts *WhereConditionsIntTestSuite) TestConditionsOnUIntModel() { match := ts.createBrand("match") ts.createBrand("not_match") From 29104e65df883ac243318b51aeebfaddc4dd7282 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:16:19 +0200 Subject: [PATCH 38/90] add isnotnull --- orm/operators.go | 4 ++++ testintegration/operators_test.go | 34 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index bbe0f28e..cfbbe3bf 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -39,3 +39,7 @@ func GtOrEq[T any](value T) Operator[T] { func IsNull[T any]() PredicateOperator[T] { return NewPredicateOperator[T]("IS NULL") } + +func IsNotNull[T any]() PredicateOperator[T] { + return NewPredicateOperator[T]("IS NOT NULL") +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 6209a66a..9e56f193 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -183,3 +183,37 @@ func (ts *OperatorsIntTestSuite) TestIsNullNullableTypes() { EqualList(&ts.Suite, []*models.Product{match}, entities) } + +func (ts *OperatorsIntTestSuite) TestIsNotNullPointers() { + int1 := 1 + match := ts.createProduct("match", 0, 0, false, &int1) + ts.createProduct("not_match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductIntPointer( + orm.IsNotNull[int](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestIsNotNullNullableTypes() { + match := ts.createProduct("match", 0, 0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 6} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullFloat( + orm.IsNotNull[float64](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} From 4a7507689f38962eb4d08122385de02af633d3de Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:30:30 +0200 Subject: [PATCH 39/90] add is true --- orm/operators.go | 6 ++++++ testintegration/operators_test.go | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index cfbbe3bf..08f9d524 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -43,3 +43,9 @@ func IsNull[T any]() PredicateOperator[T] { func IsNotNull[T any]() PredicateOperator[T] { return NewPredicateOperator[T]("IS NOT NULL") } + +// Boolean Comparison Predicates + +func IsTrue() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS TRUE") +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 9e56f193..6ce981e6 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -217,3 +217,18 @@ func (ts *OperatorsIntTestSuite) TestIsNotNullNullableTypes() { EqualList(&ts.Suite, []*models.Product{match}, entities) } + +func (ts *OperatorsIntTestSuite) TestIsTrue() { + match := ts.createProduct("match", 0, 0, true, nil) + ts.createProduct("not_match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductBool( + orm.IsTrue(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} From 82e91580896aea47edf5040d3bf822b20a58f2b9 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:31:11 +0200 Subject: [PATCH 40/90] add is not true --- orm/operators.go | 4 ++++ .../conditions/product_conditions.go | 6 +++++ testintegration/models/models.go | 1 + testintegration/operators_test.go | 22 +++++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 08f9d524..5b108d67 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -49,3 +49,7 @@ func IsNotNull[T any]() PredicateOperator[T] { func IsTrue() PredicateOperator[bool] { return NewPredicateOperator[bool]("IS TRUE") } + +func IsNotTrue() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS NOT TRUE") +} diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go index ebf9e75f..55f044fa 100644 --- a/testintegration/conditions/product_conditions.go +++ b/testintegration/conditions/product_conditions.go @@ -67,6 +67,12 @@ func ProductBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] Operator: operator, } } +func ProductNullBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { + return orm.FieldCondition[models.Product, bool]{ + Field: "NullBool", + Operator: operator, + } +} func ProductByteArray(operator orm.Operator[[]uint8]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, []uint8]{ Field: "ByteArray", diff --git a/testintegration/models/models.go b/testintegration/models/models.go index b281f6cb..a7d3f60b 100644 --- a/testintegration/models/models.go +++ b/testintegration/models/models.go @@ -72,6 +72,7 @@ type Product struct { Float float64 NullFloat sql.NullFloat64 Bool bool + NullBool sql.NullBool ByteArray []byte MultiString MultiString ToBeEmbedded diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 6ce981e6..c72d4bb1 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -232,3 +232,25 @@ func (ts *OperatorsIntTestSuite) TestIsTrue() { EqualList(&ts.Suite, []*models.Product{match}, entities) } + +func (ts *OperatorsIntTestSuite) TestIsNotTrue() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + match2.NullBool = sql.NullBool{Valid: true, Bool: false} + err := ts.db.Save(match2).Error + ts.Nil(err) + + notMatch := ts.createProduct("not_match", 0, 0, false, nil) + notMatch.NullBool = sql.NullBool{Valid: true, Bool: true} + err = ts.db.Save(notMatch).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + orm.IsNotTrue(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From 0a60e91a23477866b3e9ac4f2319c54e0fade762 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:32:06 +0200 Subject: [PATCH 41/90] add is false --- orm/operators.go | 4 ++++ testintegration/operators_test.go | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 5b108d67..fba24089 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -53,3 +53,7 @@ func IsTrue() PredicateOperator[bool] { func IsNotTrue() PredicateOperator[bool] { return NewPredicateOperator[bool]("IS NOT TRUE") } + +func IsFalse() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS FALSE") +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index c72d4bb1..9f43d64e 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -233,6 +233,21 @@ func (ts *OperatorsIntTestSuite) TestIsTrue() { EqualList(&ts.Suite, []*models.Product{match}, entities) } +func (ts *OperatorsIntTestSuite) TestIsFalse() { + match := ts.createProduct("match", 0, 0, false, nil) + ts.createProduct("not_match", 0, 0, true, nil) + ts.createProduct("not_match", 0, 0, true, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductBool( + orm.IsFalse(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + func (ts *OperatorsIntTestSuite) TestIsNotTrue() { match1 := ts.createProduct("match", 0, 0, false, nil) match2 := ts.createProduct("match", 0, 0, false, nil) From 9b9395c892562e4a8fdfc165b8f4909eb5e1041c Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:32:58 +0200 Subject: [PATCH 42/90] add is not false --- orm/operators.go | 4 ++++ testintegration/operators_test.go | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index fba24089..29bed790 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -57,3 +57,7 @@ func IsNotTrue() PredicateOperator[bool] { func IsFalse() PredicateOperator[bool] { return NewPredicateOperator[bool]("IS FALSE") } + +func IsNotFalse() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS NOT FALSE") +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 9f43d64e..ac00d794 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -269,3 +269,25 @@ func (ts *OperatorsIntTestSuite) TestIsNotTrue() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestIsNotFalse() { + match1 := ts.createProduct("match", 0, 0, false, nil) + match2 := ts.createProduct("match", 0, 0, false, nil) + match2.NullBool = sql.NullBool{Valid: true, Bool: true} + err := ts.db.Save(match2).Error + ts.Nil(err) + + notMatch := ts.createProduct("not_match", 0, 0, false, nil) + notMatch.NullBool = sql.NullBool{Valid: true, Bool: false} + err = ts.db.Save(notMatch).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + orm.IsNotFalse(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From e277f57a792aedec18e1b9e15b69be64437cbaa6 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:33:28 +0200 Subject: [PATCH 43/90] add is unknown --- orm/operators.go | 4 ++++ testintegration/operators_test.go | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 29bed790..efb1d637 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -61,3 +61,7 @@ func IsFalse() PredicateOperator[bool] { func IsNotFalse() PredicateOperator[bool] { return NewPredicateOperator[bool]("IS NOT FALSE") } + +func IsUnknown() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS UNKNOWN") +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index ac00d794..fd8b96dd 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -291,3 +291,26 @@ func (ts *OperatorsIntTestSuite) TestIsNotFalse() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestIsUnknown() { + match := ts.createProduct("match", 0, 0, false, nil) + + notMatch1 := ts.createProduct("match", 0, 0, false, nil) + notMatch1.NullBool = sql.NullBool{Valid: true, Bool: true} + err := ts.db.Save(notMatch1).Error + ts.Nil(err) + + notMatch2 := ts.createProduct("not_match", 0, 0, false, nil) + notMatch2.NullBool = sql.NullBool{Valid: true, Bool: false} + err = ts.db.Save(notMatch2).Error + ts.Nil(err) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + orm.IsUnknown(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} From 1b94fbb91b9742ada889cd109ced637bd1bddd7b Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:33:47 +0200 Subject: [PATCH 44/90] add is not unknown --- orm/operators.go | 4 ++++ testintegration/operators_test.go | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index efb1d637..9effc5a5 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -65,3 +65,7 @@ func IsNotFalse() PredicateOperator[bool] { func IsUnknown() PredicateOperator[bool] { return NewPredicateOperator[bool]("IS UNKNOWN") } + +func IsNotUnknown() PredicateOperator[bool] { + return NewPredicateOperator[bool]("IS NOT UNKNOWN") +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index fd8b96dd..70b04fa2 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -314,3 +314,26 @@ func (ts *OperatorsIntTestSuite) TestIsUnknown() { EqualList(&ts.Suite, []*models.Product{match}, entities) } + +func (ts *OperatorsIntTestSuite) TestIsNotUnknown() { + match1 := ts.createProduct("", 0, 0, false, nil) + match1.NullBool = sql.NullBool{Valid: true, Bool: true} + err := ts.db.Save(match1).Error + ts.Nil(err) + + match2 := ts.createProduct("", 0, 0, false, nil) + match2.NullBool = sql.NullBool{Valid: true, Bool: false} + err = ts.db.Save(match2).Error + ts.Nil(err) + + ts.createProduct("", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductNullBool( + orm.IsNotUnknown(), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From a3e17cfcc4eadc9f9c89fd971970ee628554c5cd Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:40:45 +0200 Subject: [PATCH 45/90] add is distict --- orm/operators.go | 5 +++++ testintegration/operators_test.go | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 9effc5a5..9395b83a 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -9,6 +9,7 @@ func Eq[T any](value T) Operator[T] { } // NotEqualTo +// IsDistinct must be used in cases where value can be NULL func NotEq[T any](value T) Operator[T] { return NewValueOperator[T]("<>", value) } @@ -69,3 +70,7 @@ func IsUnknown() PredicateOperator[bool] { func IsNotUnknown() PredicateOperator[bool] { return NewPredicateOperator[bool]("IS NOT UNKNOWN") } + +func IsDistinct[T any](value T) ValueOperator[T] { + return NewValueOperator[T]("IS DISTINCT FROM", value) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 70b04fa2..a9fe44db 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -337,3 +337,18 @@ func (ts *OperatorsIntTestSuite) TestIsNotUnknown() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestIsDistinct() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.IsDistinct(2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From cc33e8db1e44302c005a626b1f69d52380451a15 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:46:33 +0200 Subject: [PATCH 46/90] add is not distict --- orm/operators.go | 5 +++++ testintegration/operators_test.go | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 9395b83a..33405cbe 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -4,6 +4,7 @@ package orm // ref: https://www.postgresql.org/docs/current/functions-comparison.html // EqualTo +// IsNotDistinct must be used in cases where value can be NULL func Eq[T any](value T) Operator[T] { return NewValueOperator[T]("=", value) } @@ -74,3 +75,7 @@ func IsNotUnknown() PredicateOperator[bool] { func IsDistinct[T any](value T) ValueOperator[T] { return NewValueOperator[T]("IS DISTINCT FROM", value) } + +func IsNotDistinct[T any](value T) ValueOperator[T] { + return NewValueOperator[T]("IS NOT DISTINCT FROM", value) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index a9fe44db..bc89c15d 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -352,3 +352,18 @@ func (ts *OperatorsIntTestSuite) TestIsDistinct() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestIsNotDistinct() { + match := ts.createProduct("match", 3, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.IsNotDistinct(3), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} From 6fbe12a70e2b3130fa908951ddcb9e3648cb1078 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:47:28 +0200 Subject: [PATCH 47/90] add array in --- orm/operators.go | 6 ++++++ testintegration/operators_test.go | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 33405cbe..c9fffe83 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -79,3 +79,9 @@ func IsDistinct[T any](value T) ValueOperator[T] { func IsNotDistinct[T any](value T) ValueOperator[T] { return NewValueOperator[T]("IS NOT DISTINCT FROM", value) } + +// Row and Array Comparisons + +func ArrayIn[T any](values ...T) ValueOperator[T] { + return NewValueOperator[T]("IN", values) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index bc89c15d..597d80bb 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -367,3 +367,20 @@ func (ts *OperatorsIntTestSuite) TestIsNotDistinct() { EqualList(&ts.Suite, []*models.Product{match}, entities) } + +func (ts *OperatorsIntTestSuite) TestArrayIn() { + match1 := ts.createProduct("s1", 0, 0, false, nil) + match2 := ts.createProduct("s2", 0, 0, false, nil) + + ts.createProduct("ns1", 0, 0, false, nil) + ts.createProduct("ns2", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.ArrayIn("s1", "s2", "s3"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From a9e92b5ff09471e718e99a02c2ace7c6a6818d7f Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:47:53 +0200 Subject: [PATCH 48/90] add array not in --- orm/operators.go | 4 ++++ testintegration/operators_test.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index c9fffe83..5e0edcb9 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -85,3 +85,7 @@ func IsNotDistinct[T any](value T) ValueOperator[T] { func ArrayIn[T any](values ...T) ValueOperator[T] { return NewValueOperator[T]("IN", values) } + +func ArrayNotIn[T any](values ...T) ValueOperator[T] { + return NewValueOperator[T]("NOT IN", values) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 597d80bb..d92b2623 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -384,3 +384,20 @@ func (ts *OperatorsIntTestSuite) TestArrayIn() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestArrayNotIn() { + match1 := ts.createProduct("s1", 0, 0, false, nil) + match2 := ts.createProduct("s2", 0, 0, false, nil) + + ts.createProduct("ns1", 0, 0, false, nil) + ts.createProduct("ns2", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.ArrayNotIn("ns1", "ns2"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From d05e0ad81c33388ae611167d8f16f2688d63e655 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:48:43 +0200 Subject: [PATCH 49/90] add like --- orm/operators.go | 25 +++++++++++++++++++++++ testintegration/operators_test.go | 34 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 5e0edcb9..1ee0f38e 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -89,3 +89,28 @@ func ArrayIn[T any](values ...T) ValueOperator[T] { func ArrayNotIn[T any](values ...T) ValueOperator[T] { return NewValueOperator[T]("NOT IN", values) } + +// Pattern Matching + +type LikeOperator struct { + ValueOperator[string] +} + +func NewLikeOperator(sqlOperator string, pattern string) LikeOperator { + return LikeOperator{ + ValueOperator: NewValueOperator[string](sqlOperator, pattern), + } +} + +func (operator LikeOperator) Escape(escape rune) ValueOperator[string] { + return operator.AddOperation("ESCAPE", string(escape)) +} + +// Patterns: +// - An underscore (_) in pattern stands for (matches) any single character. +// - A percent sign (%) matches any sequence of zero or more characters. +// +// ref: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE +func Like(pattern string) LikeOperator { + return NewLikeOperator("LIKE", pattern) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index d92b2623..964ca529 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -401,3 +401,37 @@ func (ts *OperatorsIntTestSuite) TestArrayNotIn() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestLike() { + match1 := ts.createProduct("basd", 0, 0, false, nil) + match2 := ts.createProduct("cape", 0, 0, false, nil) + + ts.createProduct("bbsd", 0, 0, false, nil) + ts.createProduct("bbasd", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.Like("_a%"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *OperatorsIntTestSuite) TestLikeEscape() { + match1 := ts.createProduct("ba_sd", 0, 0, false, nil) + match2 := ts.createProduct("ca_pe", 0, 0, false, nil) + + ts.createProduct("bb_sd", 0, 0, false, nil) + ts.createProduct("bba_sd", 0, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + orm.Like("_a!_%").Escape('!'), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From 70954a3267cedfb7d0963077c80ea462154f2fbf Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:28:54 +0200 Subject: [PATCH 50/90] add between --- orm/operators.go | 10 ++++++++++ testintegration/operators_test.go | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 1ee0f38e..096d509d 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -38,6 +38,16 @@ func GtOrEq[T any](value T) Operator[T] { // Comparison Predicates // refs: https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE +// Equivalent to v1 < value < v2 +func Between[T any](v1 T, v2 T) Operator[T] { + return newBetweenOperator("BETWEEN", v1, v2) +} + +func newBetweenOperator[T any](sqlOperator string, v1 T, v2 T) Operator[T] { + operator := NewValueOperator[T](sqlOperator, v1) + return operator.AddOperation("AND", v2) +} + func IsNull[T any]() PredicateOperator[T] { return NewPredicateOperator[T]("IS NULL") } diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 964ca529..2560fbc4 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -148,6 +148,22 @@ func (ts *OperatorsIntTestSuite) TestGtOrEq() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } +func (ts *OperatorsIntTestSuite) TestBetween() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 6, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.Between(3, 5), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + func (ts *OperatorsIntTestSuite) TestIsNullPointers() { match := ts.createProduct("match", 0, 0, false, nil) int1 := 1 From 26b9a0a730f4e249b1a92612a1dec7609982cb68 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:29:43 +0200 Subject: [PATCH 51/90] add not between --- orm/operators.go | 5 +++++ testintegration/operators_test.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/orm/operators.go b/orm/operators.go index 096d509d..a8093515 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -43,6 +43,11 @@ func Between[T any](v1 T, v2 T) Operator[T] { return newBetweenOperator("BETWEEN", v1, v2) } +// Equivalent to NOT (v1 < value < v2) +func NotBetween[T any](v1 T, v2 T) Operator[T] { + return newBetweenOperator("NOT BETWEEN", v1, v2) +} + func newBetweenOperator[T any](sqlOperator string, v1 T, v2 T) Operator[T] { operator := NewValueOperator[T](sqlOperator, v1) return operator.AddOperation("AND", v2) diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 2560fbc4..b7d733eb 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -164,6 +164,22 @@ func (ts *OperatorsIntTestSuite) TestBetween() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } +func (ts *OperatorsIntTestSuite) TestNotBetween() { + match1 := ts.createProduct("match", 3, 0, false, nil) + match2 := ts.createProduct("match", 4, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + orm.NotBetween(0, 2), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + func (ts *OperatorsIntTestSuite) TestIsNullPointers() { match := ts.createProduct("match", 0, 0, false, nil) int1 := 1 From db62750d45a83d684d9471f5697bca92387dd655 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:54:03 +0200 Subject: [PATCH 52/90] add and --- orm/condition.go | 91 +++++++++++++++++++----- testintegration/join_conditions_test.go | 17 +++++ testintegration/where_conditions_test.go | 45 ++++++++++++ 3 files changed, 135 insertions(+), 18 deletions(-) diff --git a/orm/condition.go b/orm/condition.go index 0632cea6..9559ba4e 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -2,7 +2,9 @@ package orm import ( "fmt" + "strings" + "github.com/elliotchance/pie/v2" "gorm.io/gorm" ) @@ -34,6 +36,57 @@ type WhereCondition[T any] interface { affectsDeletedAt() bool } +// Condition that connects multiple conditions. +// Example: condition1 AND condition2 +type ConnectionCondition[T any] struct { + Connector string + Conditions []WhereCondition[T] +} + +//nolint:unused // see inside +func (condition ConnectionCondition[T]) interfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition ConnectionCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + return applyWhereCondition[T](condition, query, tableName) +} + +func (condition ConnectionCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []any, error) { + sqlStrings := []string{} + values := []any{} + + for _, internalCondition := range condition.Conditions { + internalSQLString, internalValues, err := internalCondition.GetSQL(query, tableName) + if err != nil { + return "", nil, err + } + + sqlStrings = append(sqlStrings, internalSQLString) + + values = append(values, internalValues...) + } + + return strings.Join(sqlStrings, " "+condition.Connector+" "), values, nil +} + +//nolint:unused // is used +func (condition ConnectionCondition[T]) affectsDeletedAt() bool { + return pie.Any(condition.Conditions, func(internalCondition WhereCondition[T]) bool { + return internalCondition.affectsDeletedAt() + }) +} + +// Condition that connects multiple conditions. +// Example: condition1 AND condition2 +func NewConnectionCondition[T any](connector string, conditions ...WhereCondition[T]) WhereCondition[T] { + return ConnectionCondition[T]{ + Connector: connector, + Conditions: conditions, + } +} + // Condition that verifies the value of a field, // using the Operator type FieldCondition[TObject any, TAtribute any] struct { @@ -120,23 +173,18 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, previousTableName whereConditions, joinConditions := divideConditionsByType(condition.Conditions) // apply WhereConditions to join in "on" clause - conditionsValues := []any{} - isDeletedAtConditionPresent := false - for _, condition := range whereConditions { - if condition.affectsDeletedAt() { - isDeletedAtConditionPresent = true - } + connectionCondition := And(whereConditions...) - sql, values, err := condition.GetSQL(query, nextTableName) - if err != nil { - return nil, err - } + onQuery, onValues, err := connectionCondition.GetSQL(query, nextTableName) + if err != nil { + return nil, err + } - joinQuery += " AND " + sql - conditionsValues = append(conditionsValues, values...) + if onQuery != "" { + joinQuery += " AND " + onQuery } - if !isDeletedAtConditionPresent { + if !connectionCondition.affectsDeletedAt() { joinQuery += fmt.Sprintf( " AND %s.deleted_at IS NULL", nextTableName, @@ -144,7 +192,7 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, previousTableName } // add the join to the query - query = query.Joins(joinQuery, conditionsValues...) + query = query.Joins(joinQuery, onValues...) // apply nested joins for _, joinCondition := range joinConditions { @@ -177,13 +225,20 @@ func divideConditionsByType[T any]( conditions []Condition[T], ) (thisEntityConditions []WhereCondition[T], joinConditions []Condition[T]) { for _, condition := range conditions { - switch typedCondition := condition.(type) { - case WhereCondition[T]: + typedCondition, ok := condition.(WhereCondition[T]) + if ok { thisEntityConditions = append(thisEntityConditions, typedCondition) - default: - joinConditions = append(joinConditions, typedCondition) + } else { + joinConditions = append(joinConditions, condition) } } return } + +// Logical Operators +// ref: https://www.postgresql.org/docs/current/functions-logical.html + +func And[T any](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewConnectionCondition("AND", conditions...) +} diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index f036e7e4..9f2c5b1a 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -353,3 +353,20 @@ func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsMultipleTimes() { EqualList(&ts.Suite, []*models.Sale{match}, entities) } + +func (ts *WhereConditionsIntTestSuite) TestJoinWithEmptyConnectionConditionMakesNothing() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + match1 := ts.createSale(0, product1, nil) + match2 := ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + orm.And[models.Product](), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match1, match2}, entities) +} diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index ee5e307d..7971edc8 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -427,3 +427,48 @@ func (ts *WhereConditionsIntTestSuite) TestConditionsOnUIntModel() { EqualList(&ts.Suite, []*models.Brand{match}, entities) } + +func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsAreConnectedByAnd() { + match := ts.createProduct("match", 3, 0, false, nil) + ts.createProduct("not_match", 5, 0, false, nil) + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt(orm.GtOrEq(3)), + conditions.ProductInt(orm.LtOrEq(4)), + conditions.ProductString(orm.Eq("match")), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsDifferentOperators() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 1, 0.0, true, nil) + ts.createProduct("match", 2, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString(orm.Eq("match")), + conditions.ProductInt(orm.Lt(2)), + conditions.ProductBool(orm.NotEq(false)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestEmptyConnectionConditionMakesNothing() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + orm.And[models.Product](), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} From d91f4461ce6c9fded1cdc19adba35ccaf7d96be1 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:55:47 +0200 Subject: [PATCH 53/90] add or --- orm/condition.go | 4 ++++ testintegration/where_conditions_test.go | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/orm/condition.go b/orm/condition.go index 9559ba4e..6e6334ff 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -242,3 +242,7 @@ func divideConditionsByType[T any]( func And[T any](conditions ...WhereCondition[T]) WhereCondition[T] { return NewConnectionCondition("AND", conditions...) } + +func Or[T any](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewConnectionCondition("OR", conditions...) +} diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index 7971edc8..2f752eab 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -444,6 +444,26 @@ func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsAreConnectedByAnd() EqualList(&ts.Suite, []*models.Product{match}, entities) } +func (ts *WhereConditionsIntTestSuite) TestOr() { + match1 := ts.createProduct("match", 2, 0, false, nil) + match2 := ts.createProduct("match", 3, 0, false, nil) + match3 := ts.createProduct("match_3", 3, 0, false, nil) + + ts.createProduct("not_match", 1, 0, false, nil) + ts.createProduct("not_match", 4, 0, false, nil) + + entities, err := ts.crudProductService.Query( + orm.Or( + conditions.ProductInt(orm.Eq(2)), + conditions.ProductInt(orm.Eq(3)), + conditions.ProductString(orm.Eq("match_3")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsDifferentOperators() { match1 := ts.createProduct("match", 1, 0.0, true, nil) match2 := ts.createProduct("match", 1, 0.0, true, nil) From b1778ebe5d25e1b3776c3b2070ee952bef4764a4 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:58:15 +0200 Subject: [PATCH 54/90] add not --- orm/condition.go | 84 ++++++++++++++++++++++++ testintegration/join_conditions_test.go | 9 +++ testintegration/where_conditions_test.go | 61 +++++++++++++++++ 3 files changed, 154 insertions(+) diff --git a/orm/condition.go b/orm/condition.go index 6e6334ff..1d4128fc 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -1,6 +1,7 @@ package orm import ( + "errors" "fmt" "strings" @@ -10,6 +11,8 @@ import ( const DeletedAtField = "DeletedAt" +var ErrEmptyConditions = errors.New("condition must have at least one inner condition") + type Condition[T any] interface { // Applies the condition to the "query" // using the "tableName" as name for the table holding @@ -36,6 +39,52 @@ type WhereCondition[T any] interface { affectsDeletedAt() bool } +// Condition that contains a internal condition. +// Example: NOT (internal condition) +type ContainerCondition[T any] struct { + ConnectionCondition WhereCondition[T] + Prefix string +} + +//nolint:unused // see inside +func (condition ContainerCondition[T]) interfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition ContainerCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + return applyWhereCondition[T](condition, query, tableName) +} + +func (condition ContainerCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []any, error) { + sqlString, values, err := condition.ConnectionCondition.GetSQL(query, tableName) + if err != nil { + return "", nil, err + } + + sqlString = condition.Prefix + " (" + sqlString + ")" + + return sqlString, values, nil +} + +//nolint:unused // is used +func (condition ContainerCondition[T]) affectsDeletedAt() bool { + return condition.ConnectionCondition.affectsDeletedAt() +} + +// Condition that contains a internal condition. +// Example: NOT (internal condition) +func NewContainerCondition[T any](prefix string, conditions ...WhereCondition[T]) WhereCondition[T] { + if len(conditions) == 0 { + return NewInvalidCondition[T](ErrEmptyConditions) + } + + return ContainerCondition[T]{ + Prefix: prefix, + ConnectionCondition: And(conditions...), + } +} + // Condition that connects multiple conditions. // Example: condition1 AND condition2 type ConnectionCondition[T any] struct { @@ -236,6 +285,37 @@ func divideConditionsByType[T any]( return } +// Condition used to returns an error when the query is executed +type InvalidCondition[T any] struct { + Err error +} + +//nolint:unused // see inside +func (condition InvalidCondition[T]) interfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition InvalidCondition[T]) ApplyTo(_ *gorm.DB, _ string) (*gorm.DB, error) { + return nil, condition.Err +} + +func (condition InvalidCondition[T]) GetSQL(_ *gorm.DB, _ string) (string, []any, error) { + return "", nil, condition.Err +} + +//nolint:unused // is used +func (condition InvalidCondition[T]) affectsDeletedAt() bool { + return false +} + +// Condition used to returns an error when the query is executed +func NewInvalidCondition[T any](err error) InvalidCondition[T] { + return InvalidCondition[T]{ + Err: err, + } +} + // Logical Operators // ref: https://www.postgresql.org/docs/current/functions-logical.html @@ -246,3 +326,7 @@ func And[T any](conditions ...WhereCondition[T]) WhereCondition[T] { func Or[T any](conditions ...WhereCondition[T]) WhereCondition[T] { return NewConnectionCondition("OR", conditions...) } + +func Not[T any](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewContainerCondition("NOT", conditions...) +} diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index 9f2c5b1a..c7b033f7 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -370,3 +370,12 @@ func (ts *WhereConditionsIntTestSuite) TestJoinWithEmptyConnectionConditionMakes EqualList(&ts.Suite, []*models.Sale{match1, match2}, entities) } + +func (ts *WhereConditionsIntTestSuite) TestJoinWithEmptyContainerConditionReturnsError() { + _, err := ts.crudSaleService.Query( + conditions.SaleProduct( + orm.Not[models.Product](), + ), + ) + ts.ErrorIs(err, orm.ErrEmptyConditions) +} diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index 2f752eab..69378367 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -444,6 +444,39 @@ func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsAreConnectedByAnd() EqualList(&ts.Suite, []*models.Product{match}, entities) } +func (ts *WhereConditionsIntTestSuite) TestNot() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 3, 0, false, nil) + + ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 2, 0, false, nil) + + entities, err := ts.crudProductService.Query( + orm.Not(conditions.ProductInt(orm.Eq(2))), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + +func (ts *WhereConditionsIntTestSuite) TestNotWithMultipleConditionsAreConnectedByAnd() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 5, 0, false, nil) + + ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + orm.Not( + conditions.ProductInt(orm.Gt(1)), + conditions.ProductInt(orm.Lt(4)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + func (ts *WhereConditionsIntTestSuite) TestOr() { match1 := ts.createProduct("match", 2, 0, false, nil) match2 := ts.createProduct("match", 3, 0, false, nil) @@ -464,6 +497,27 @@ func (ts *WhereConditionsIntTestSuite) TestOr() { EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) } +func (ts *WhereConditionsIntTestSuite) TestNotOr() { + match1 := ts.createProduct("match", 1, 0, false, nil) + match2 := ts.createProduct("match", 5, 0, false, nil) + match3 := ts.createProduct("match", 4, 0, false, nil) + + ts.createProduct("not_match", 2, 0, false, nil) + ts.createProduct("not_match_string", 3, 0, false, nil) + + entities, err := ts.crudProductService.Query( + orm.Not[models.Product]( + orm.Or( + conditions.ProductInt(orm.Eq(2)), + conditions.ProductString(orm.Eq("not_match_string")), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities) +} + func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsDifferentOperators() { match1 := ts.createProduct("match", 1, 0.0, true, nil) match2 := ts.createProduct("match", 1, 0.0, true, nil) @@ -492,3 +546,10 @@ func (ts *WhereConditionsIntTestSuite) TestEmptyConnectionConditionMakesNothing( EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *WhereConditionsIntTestSuite) TestEmptyContainerConditionReturnsError() { + _, err := ts.crudProductService.Query( + orm.Not[models.Product](), + ) + ts.ErrorIs(err, orm.ErrEmptyConditions) +} From 4215ea39d7af06e8473f2bf17d5cef3b50a0ff50 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 10:59:22 +0200 Subject: [PATCH 55/90] add unsafe conditions --- orm/condition.go | 38 ++++++++++++++++++++++++ testintegration/join_conditions_test.go | 25 ++++++++++++++++ testintegration/where_conditions_test.go | 14 +++++++++ 3 files changed, 77 insertions(+) diff --git a/orm/condition.go b/orm/condition.go index 1d4128fc..bb67a773 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -285,6 +285,44 @@ func divideConditionsByType[T any]( return } +// Condition that can be used to express conditions that are not supported (yet?) by BaDORM +// Example: table1.columnX = table2.columnY +type UnsafeCondition[T any] struct { + SQLCondition string + Values []any +} + +//nolint:unused // see inside +func (condition UnsafeCondition[T]) interfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition UnsafeCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + return applyWhereCondition[T](condition, query, tableName) +} + +func (condition UnsafeCondition[T]) GetSQL(_ *gorm.DB, tableName string) (string, []any, error) { + return fmt.Sprintf( + condition.SQLCondition, + tableName, + ), condition.Values, nil +} + +//nolint:unused // is used +func (condition UnsafeCondition[T]) affectsDeletedAt() bool { + return false +} + +// Condition that can be used to express conditions that are not supported (yet?) by BaDORM +// Example: table1.columnX = table2.columnY +func NewUnsafeCondition[T any](condition string, values []any) UnsafeCondition[T] { + return UnsafeCondition[T]{ + SQLCondition: condition, + Values: values, + } +} + // Condition used to returns an error when the query is executed type InvalidCondition[T any] struct { Err error diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index c7b033f7..71d21038 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -354,6 +354,31 @@ func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsMultipleTimes() { EqualList(&ts.Suite, []*models.Sale{match}, entities) } +func (ts *WhereConditionsIntTestSuite) TestJoinWithUnsafeCondition() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("ditrit", company1) + seller2 := ts.createSeller("agustin", company2) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerCompany( + orm.NewUnsafeCondition[models.Company]("%s.name = sellers_sales.name", []any{}), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + func (ts *WhereConditionsIntTestSuite) TestJoinWithEmptyConnectionConditionMakesNothing() { product1 := ts.createProduct("", 1, 0.0, false, nil) product2 := ts.createProduct("", 2, 0.0, false, nil) diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index 69378367..59888ce0 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -535,6 +535,20 @@ func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsDifferentOperators( EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } +func (ts *WhereConditionsIntTestSuite) TestUnsafeCondition() { + match1 := ts.createProduct("match", 1, 0.0, true, nil) + match2 := ts.createProduct("match", 1, 0.0, true, nil) + + ts.createProduct("not_match", 2, 0.0, true, nil) + + entities, err := ts.crudProductService.Query( + orm.NewUnsafeCondition[models.Product]("%s.int = ?", []any{1}), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) +} + func (ts *WhereConditionsIntTestSuite) TestEmptyConnectionConditionMakesNothing() { match1 := ts.createProduct("match", 1, 0.0, true, nil) match2 := ts.createProduct("match", 1, 0.0, true, nil) From 28f4d7910548ca4470cbe24a58de32500f0da039 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 11:00:37 +0200 Subject: [PATCH 56/90] use all coverage files in sonar --- .github/workflows/CI.yml | 14 ++++---------- sonar-project.properties | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ae24c197..5d8a6213 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -57,7 +57,7 @@ jobs: run: gotestsum --junitfile unit-tests.xml $(go list ./... | grep -v testintegration) -coverpkg=./... -coverprofile=coverage_unit.out - uses: actions/upload-artifact@v3 with: - name: coverage_unit + name: coverage path: coverage_unit.out - name: Test Report uses: dorny/test-reporter@v1 @@ -94,7 +94,7 @@ jobs: reporter: java-junit # Format of test results - uses: actions/upload-artifact@v3 with: - name: coverage_int + name: coverage path: coverage_int.out - name: Stop containers run: docker stop badaas-test-db @@ -139,16 +139,10 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Download unit tests line coverage report + - name: Download line coverage reports uses: actions/download-artifact@v3 with: - name: coverage_unit - path: coverage_unit.out - - name: Download int tests line coverage report - uses: actions/download-artifact@v3 - with: - name: coverage_int - path: coverage_int.out + name: coverage - name: SonarCloud Scan uses: sonarsource/sonarcloud-github-action@master env: diff --git a/sonar-project.properties b/sonar-project.properties index b6014bb3..aac1e397 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,4 +6,4 @@ sonar.sources=. sonar.exclusions=**/*_test.go,mocks/***,vendor/*** sonar.tests=. sonar.test.inclusions=**/*_test.go,testintegration/***,test_e2e/*** -sonar.go.coverage.reportPaths=coverage_unit.out/coverage_unit.out,coverage_int.out/coverage_int.out +sonar.go.coverage.reportPaths=*.out From 23ea895ebd82869dde259bc1c1014462f2505ac3 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 11:00:59 +0200 Subject: [PATCH 57/90] ignore all tests results --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e333f3ae..4210c0fd 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +*-tests*.xml # Dependency directories (remove the comment below to include it) vendor/ From e7e8f4519c3ceaa7319540f1d0111c2bb8e0382b Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 11:06:41 +0200 Subject: [PATCH 58/90] update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 5d771516..24def010 100644 --- a/changelog.md +++ b/changelog.md @@ -32,5 +32,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update verdeter to version v0.4.0. - Transform BadAas into a library. - Add badaas-orm with the compilable query system. +- Add operators support [unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased From 9107f1744be19629ed2a1f589bab7a4f91538105 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 2 Aug 2023 16:37:22 +0200 Subject: [PATCH 59/90] create mocks for new types --- mocks/orm/Operator.go | 63 +++++++++++++++++++++ mocks/orm/WhereCondition.go | 106 ++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 mocks/orm/Operator.go create mode 100644 mocks/orm/WhereCondition.go diff --git a/mocks/orm/Operator.go b/mocks/orm/Operator.go new file mode 100644 index 00000000..e2ae8183 --- /dev/null +++ b/mocks/orm/Operator.go @@ -0,0 +1,63 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// Operator is an autogenerated mock type for the Operator type +type Operator[T interface{}] struct { + mock.Mock +} + +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *Operator[T]) InterfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +// ToSQL provides a mock function with given fields: columnName +func (_m *Operator[T]) ToSQL(columnName string) (string, []interface{}, error) { + ret := _m.Called(columnName) + + var r0 string + var r1 []interface{} + var r2 error + if rf, ok := ret.Get(0).(func(string) (string, []interface{}, error)); ok { + return rf(columnName) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(columnName) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) []interface{}); ok { + r1 = rf(columnName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + if rf, ok := ret.Get(2).(func(string) error); ok { + r2 = rf(columnName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type mockConstructorTestingTNewOperator interface { + mock.TestingT + Cleanup(func()) +} + +// NewOperator creates a new instance of Operator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewOperator[T interface{}](t mockConstructorTestingTNewOperator) *Operator[T] { + mock := &Operator[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/WhereCondition.go b/mocks/orm/WhereCondition.go new file mode 100644 index 00000000..725d3ef3 --- /dev/null +++ b/mocks/orm/WhereCondition.go @@ -0,0 +1,106 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + gorm "gorm.io/gorm" +) + +// WhereCondition is an autogenerated mock type for the WhereCondition type +type WhereCondition[T interface{}] struct { + mock.Mock +} + +// ApplyTo provides a mock function with given fields: query, tableName +func (_m *WhereCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { + ret := _m.Called(query, tableName) + + var r0 *gorm.DB + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, string) (*gorm.DB, error)); ok { + return rf(query, tableName) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, string) *gorm.DB); ok { + r0 = rf(query, tableName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gorm.DB) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, string) error); ok { + r1 = rf(query, tableName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetSQL provides a mock function with given fields: query, tableName +func (_m *WhereCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []interface{}, error) { + ret := _m.Called(query, tableName) + + var r0 string + var r1 []interface{} + var r2 error + if rf, ok := ret.Get(0).(func(*gorm.DB, string) (string, []interface{}, error)); ok { + return rf(query, tableName) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, string) string); ok { + r0 = rf(query, tableName) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, string) []interface{}); ok { + r1 = rf(query, tableName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + if rf, ok := ret.Get(2).(func(*gorm.DB, string) error); ok { + r2 = rf(query, tableName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// affectsDeletedAt provides a mock function with given fields: +func (_m *WhereCondition[T]) affectsDeletedAt() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// interfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *WhereCondition[T]) interfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +type mockConstructorTestingTNewWhereCondition interface { + mock.TestingT + Cleanup(func()) +} + +// NewWhereCondition creates a new instance of WhereCondition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewWhereCondition[T interface{}](t mockConstructorTestingTNewWhereCondition) *WhereCondition[T] { + mock := &WhereCondition[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From 6c6d117165dc495ed8135fd1461224929f1e41f0 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 3 Aug 2023 16:45:44 +0200 Subject: [PATCH 60/90] replace inverse join generation by inverse reference in models --- testintegration/conditions/city_conditions.go | 7 +++++++ testintegration/conditions/company_conditions.go | 7 ------- testintegration/conditions/country_conditions.go | 7 ------- testintegration/conditions/seller_conditions.go | 7 +++++++ testintegration/models/models.go | 6 ++++++ 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go index 1f0218ff..322f6c78 100644 --- a/testintegration/conditions/city_conditions.go +++ b/testintegration/conditions/city_conditions.go @@ -37,6 +37,13 @@ func CityName(operator orm.Operator[string]) orm.WhereCondition[models.City] { Operator: operator, } } +func CityCountry(conditions ...orm.Condition[models.Country]) orm.Condition[models.City] { + return orm.JoinCondition[models.City, models.Country]{ + Conditions: conditions, + T1Field: "CountryID", + T2Field: "ID", + } +} func CityCountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, orm.UUID]{ Field: "CountryID", diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go index b87db9b2..3c7c40cd 100644 --- a/testintegration/conditions/company_conditions.go +++ b/testintegration/conditions/company_conditions.go @@ -37,10 +37,3 @@ func CompanyName(operator orm.Operator[string]) orm.WhereCondition[models.Compan Operator: operator, } } -func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[models.Seller] { - return orm.JoinCondition[models.Seller, models.Company]{ - Conditions: conditions, - T1Field: "CompanyID", - T2Field: "ID", - } -} diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go index f00fddf4..1ecc4f98 100644 --- a/testintegration/conditions/country_conditions.go +++ b/testintegration/conditions/country_conditions.go @@ -44,10 +44,3 @@ func CountryCapital(conditions ...orm.Condition[models.City]) orm.Condition[mode T2Field: "CountryID", } } -func CityCountry(conditions ...orm.Condition[models.Country]) orm.Condition[models.City] { - return orm.JoinCondition[models.City, models.Country]{ - Conditions: conditions, - T1Field: "CountryID", - T2Field: "ID", - } -} diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go index 5390f2d5..a42cf23a 100644 --- a/testintegration/conditions/seller_conditions.go +++ b/testintegration/conditions/seller_conditions.go @@ -37,6 +37,13 @@ func SellerName(operator orm.Operator[string]) orm.WhereCondition[models.Seller] Operator: operator, } } +func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[models.Seller] { + return orm.JoinCondition[models.Seller, models.Company]{ + Conditions: conditions, + T1Field: "CompanyID", + T2Field: "ID", + } +} func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, orm.UUID]{ Field: "CompanyID", diff --git a/testintegration/models/models.go b/testintegration/models/models.go index a7d3f60b..70badc96 100644 --- a/testintegration/models/models.go +++ b/testintegration/models/models.go @@ -28,6 +28,10 @@ type Company struct { Sellers []Seller // Company HasMany Sellers (Company 0..1 -> 0..* Seller) } +func (m Company) Equal(other Company) bool { + return m.ID == other.ID +} + type MultiString []string func (s *MultiString) Scan(src interface{}) error { @@ -87,6 +91,7 @@ type Seller struct { orm.UUIDModel Name string + Company *Company CompanyID *orm.UUID // Company HasMany Sellers (Company 0..1 -> 0..* Seller) } @@ -124,6 +129,7 @@ type City struct { orm.UUIDModel Name string + Country *Country CountryID orm.UUID // Country HasOne City (Country 1 -> 1 City) } From 18b87e56fc9715b7b8df248005ddcdb5567e8c51 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 3 Aug 2023 09:42:52 +0200 Subject: [PATCH 61/90] table class in place of strings for table name --- orm/condition.go | 131 +++++++++++------- orm/crudRepository.go | 6 +- .../conditions/bicycle_conditions.go | 7 +- testintegration/conditions/city_conditions.go | 7 +- .../conditions/country_conditions.go | 7 +- .../conditions/employee_conditions.go | 7 +- .../conditions/phone_conditions.go | 7 +- testintegration/conditions/sale_conditions.go | 14 +- .../conditions/seller_conditions.go | 7 +- testintegration/join_conditions_test.go | 2 +- 10 files changed, 122 insertions(+), 73 deletions(-) diff --git a/orm/condition.go b/orm/condition.go index bb67a773..c75076d6 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -13,11 +13,44 @@ const DeletedAtField = "DeletedAt" var ErrEmptyConditions = errors.New("condition must have at least one inner condition") +type Table struct { + Name string + Alias string + Initial bool +} + +// Returns true if the Table is the initial table in a query +func (table Table) IsInitial() bool { + return table.Initial +} + +// Returns the related Table corresponding to the model +func (table Table) DeliverTable(query *gorm.DB, model any, relationName string) (Table, error) { + // get the name of the table for the model + tableName, err := getTableName(query, model) + if err != nil { + return Table{}, err + } + + // add a suffix to avoid tables with the same name when joining + // the same table more than once + tableAlias := relationName + if !table.IsInitial() { + tableAlias = table.Alias + "__" + relationName + } + + return Table{ + Name: tableName, + Alias: tableAlias, + Initial: false, + }, nil +} + type Condition[T any] interface { // Applies the condition to the "query" // using the "tableName" as name for the table holding // the data for object of type T - ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) + ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) // This method is necessary to get the compiler to verify // that an object is of type Condition[T], @@ -32,7 +65,7 @@ type WhereCondition[T any] interface { Condition[T] // Get the sql string and values to use in the query - GetSQL(query *gorm.DB, tableName string) (string, []any, error) + GetSQL(query *gorm.DB, table Table) (string, []any, error) // Returns true if the DeletedAt column if affected by the condition // If no condition affects the DeletedAt, the verification that it's null will be added automatically @@ -52,12 +85,12 @@ func (condition ContainerCondition[T]) interfaceVerificationMethod(_ T) { // that an object is of type Condition[T] } -func (condition ContainerCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { - return applyWhereCondition[T](condition, query, tableName) +func (condition ContainerCondition[T]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { + return applyWhereCondition[T](condition, query, table) } -func (condition ContainerCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []any, error) { - sqlString, values, err := condition.ConnectionCondition.GetSQL(query, tableName) +func (condition ContainerCondition[T]) GetSQL(query *gorm.DB, table Table) (string, []any, error) { + sqlString, values, err := condition.ConnectionCondition.GetSQL(query, table) if err != nil { return "", nil, err } @@ -98,16 +131,16 @@ func (condition ConnectionCondition[T]) interfaceVerificationMethod(_ T) { // that an object is of type Condition[T] } -func (condition ConnectionCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { - return applyWhereCondition[T](condition, query, tableName) +func (condition ConnectionCondition[T]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { + return applyWhereCondition[T](condition, query, table) } -func (condition ConnectionCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []any, error) { +func (condition ConnectionCondition[T]) GetSQL(query *gorm.DB, table Table) (string, []any, error) { sqlStrings := []string{} values := []any{} for _, internalCondition := range condition.Conditions { - internalSQLString, internalValues, err := internalCondition.GetSQL(query, tableName) + internalSQLString, internalValues, err := internalCondition.GetSQL(query, table) if err != nil { return "", nil, err } @@ -153,12 +186,12 @@ func (condition FieldCondition[TObject, TAtribute]) interfaceVerificationMethod( // Returns a gorm Where condition that can be used // to filter that the Field as a value of Value -func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { - return applyWhereCondition[TObject](condition, query, tableName) +func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { + return applyWhereCondition[TObject](condition, query, table) } -func applyWhereCondition[T any](condition WhereCondition[T], query *gorm.DB, tableName string) (*gorm.DB, error) { - sql, values, err := condition.GetSQL(query, tableName) +func applyWhereCondition[T any](condition WhereCondition[T], query *gorm.DB, table Table) (*gorm.DB, error) { + sql, values, err := condition.GetSQL(query, table) if err != nil { return nil, err } @@ -178,23 +211,24 @@ func (condition FieldCondition[TObject, TAtribute]) affectsDeletedAt() bool { return condition.Field == DeletedAtField } -func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *gorm.DB, tableName string) (string, []any, error) { +func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *gorm.DB, table Table) (string, []any, error) { columnName := condition.Column if columnName == "" { - columnName = query.NamingStrategy.ColumnName(tableName, condition.Field) + columnName = query.NamingStrategy.ColumnName(table.Name, condition.Field) } // add column prefix and table name once we know the column name - columnName = tableName + "." + condition.ColumnPrefix + columnName + columnName = table.Alias + "." + condition.ColumnPrefix + columnName return condition.Operator.ToSQL(columnName) } // Condition that joins with other table type JoinCondition[T1 any, T2 any] struct { - T1Field string - T2Field string - Conditions []Condition[T2] + T1Field string + T2Field string + RelationField string + Conditions []Condition[T2] } func (condition JoinCondition[T1, T2]) interfaceVerificationMethod(t T1) { @@ -205,26 +239,25 @@ func (condition JoinCondition[T1, T2]) interfaceVerificationMethod(t T1) { // Applies a join between the tables of T1 and T2 // previousTableName is the name of the table of T1 // It also applies the nested conditions -func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, previousTableName string) (*gorm.DB, error) { - // get the name of the table for T2 - toBeJoinedTableName, err := getTableName(query, *new(T2)) +func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (*gorm.DB, error) { + whereConditions, joinConditions := divideConditionsByType(condition.Conditions) + + // get the sql to do the join with T2 + t2Table, err := t1Table.DeliverTable(query, *new(T2), condition.RelationField) if err != nil { return nil, err } - // add a suffix to avoid tables with the same name when joining - // the same table more than once - nextTableName := toBeJoinedTableName + "_" + previousTableName - - // get the sql to do the join with T2 - joinQuery := condition.getSQLJoin(query, toBeJoinedTableName, nextTableName, previousTableName) - - whereConditions, joinConditions := divideConditionsByType(condition.Conditions) + joinQuery := condition.getSQLJoin( + query, + t1Table, + t2Table, + ) - // apply WhereConditions to join in "on" clause + // apply WhereConditions to the join in the "on" clause connectionCondition := And(whereConditions...) - onQuery, onValues, err := connectionCondition.GetSQL(query, nextTableName) + onQuery, onValues, err := connectionCondition.GetSQL(query, t2Table) if err != nil { return nil, err } @@ -236,7 +269,7 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, previousTableName if !connectionCondition.affectsDeletedAt() { joinQuery += fmt.Sprintf( " AND %s.deleted_at IS NULL", - nextTableName, + t2Table.Alias, ) } @@ -245,7 +278,7 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, previousTableName // apply nested joins for _, joinCondition := range joinConditions { - query, err = joinCondition.ApplyTo(query, nextTableName) + query, err = joinCondition.ApplyTo(query, t2Table) if err != nil { return nil, err } @@ -257,15 +290,19 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, previousTableName // Returns the SQL string to do a join between T1 and T2 // taking into account that the ID attribute necessary to do it // can be either in T1's or T2's table. -func (condition JoinCondition[T1, T2]) getSQLJoin(query *gorm.DB, toBeJoinedTableName, nextTableName, previousTableName string) string { +func (condition JoinCondition[T1, T2]) getSQLJoin( + query *gorm.DB, + t1Table Table, + t2Table Table, +) string { return fmt.Sprintf( `JOIN %[1]s %[2]s ON %[2]s.%[3]s = %[4]s.%[5]s `, - toBeJoinedTableName, - nextTableName, - query.NamingStrategy.ColumnName(nextTableName, condition.T2Field), - previousTableName, - query.NamingStrategy.ColumnName(previousTableName, condition.T1Field), + t2Table.Name, + t2Table.Alias, + query.NamingStrategy.ColumnName(t2Table.Name, condition.T2Field), + t1Table.Alias, + query.NamingStrategy.ColumnName(t1Table.Name, condition.T1Field), ) } @@ -298,14 +335,14 @@ func (condition UnsafeCondition[T]) interfaceVerificationMethod(_ T) { // that an object is of type Condition[T] } -func (condition UnsafeCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { - return applyWhereCondition[T](condition, query, tableName) +func (condition UnsafeCondition[T]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { + return applyWhereCondition[T](condition, query, table) } -func (condition UnsafeCondition[T]) GetSQL(_ *gorm.DB, tableName string) (string, []any, error) { +func (condition UnsafeCondition[T]) GetSQL(_ *gorm.DB, table Table) (string, []any, error) { return fmt.Sprintf( condition.SQLCondition, - tableName, + table.Alias, ), condition.Values, nil } @@ -334,11 +371,11 @@ func (condition InvalidCondition[T]) interfaceVerificationMethod(_ T) { // that an object is of type Condition[T] } -func (condition InvalidCondition[T]) ApplyTo(_ *gorm.DB, _ string) (*gorm.DB, error) { +func (condition InvalidCondition[T]) ApplyTo(_ *gorm.DB, _ Table) (*gorm.DB, error) { return nil, condition.Err } -func (condition InvalidCondition[T]) GetSQL(_ *gorm.DB, _ string) (string, []any, error) { +func (condition InvalidCondition[T]) GetSQL(_ *gorm.DB, _ Table) (string, []any, error) { return "", nil, condition.Err } diff --git a/orm/crudRepository.go b/orm/crudRepository.go index b1d7883b..fba453ff 100644 --- a/orm/crudRepository.go +++ b/orm/crudRepository.go @@ -101,7 +101,11 @@ func (repository *CRUDRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...Co query := tx for _, condition := range conditions { - query, err = condition.ApplyTo(query, initialTableName) + query, err = condition.ApplyTo(query, Table{ + Name: initialTableName, + Alias: initialTableName, + Initial: true, + }) if err != nil { return nil, err } diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go index b16e5773..b026ea12 100644 --- a/testintegration/conditions/bicycle_conditions.go +++ b/testintegration/conditions/bicycle_conditions.go @@ -39,9 +39,10 @@ func BicycleName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycl } func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.Condition[models.Bicycle] { return orm.JoinCondition[models.Bicycle, models.Person]{ - Conditions: conditions, - T1Field: "OwnerName", - T2Field: "Name", + Conditions: conditions, + RelationField: "Owner", + T1Field: "OwnerName", + T2Field: "Name", } } func BicycleOwnerName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go index 322f6c78..d7f54be0 100644 --- a/testintegration/conditions/city_conditions.go +++ b/testintegration/conditions/city_conditions.go @@ -39,9 +39,10 @@ func CityName(operator orm.Operator[string]) orm.WhereCondition[models.City] { } func CityCountry(conditions ...orm.Condition[models.Country]) orm.Condition[models.City] { return orm.JoinCondition[models.City, models.Country]{ - Conditions: conditions, - T1Field: "CountryID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Country", + T1Field: "CountryID", + T2Field: "ID", } } func CityCountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go index 1ecc4f98..4db0dc33 100644 --- a/testintegration/conditions/country_conditions.go +++ b/testintegration/conditions/country_conditions.go @@ -39,8 +39,9 @@ func CountryName(operator orm.Operator[string]) orm.WhereCondition[models.Countr } func CountryCapital(conditions ...orm.Condition[models.City]) orm.Condition[models.Country] { return orm.JoinCondition[models.Country, models.City]{ - Conditions: conditions, - T1Field: "ID", - T2Field: "CountryID", + Conditions: conditions, + RelationField: "Capital", + T1Field: "ID", + T2Field: "CountryID", } } diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go index 77860234..cc466f58 100644 --- a/testintegration/conditions/employee_conditions.go +++ b/testintegration/conditions/employee_conditions.go @@ -39,9 +39,10 @@ func EmployeeName(operator orm.Operator[string]) orm.WhereCondition[models.Emplo } func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.Condition[models.Employee] { return orm.JoinCondition[models.Employee, models.Employee]{ - Conditions: conditions, - T1Field: "BossID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Boss", + T1Field: "BossID", + T2Field: "ID", } } func EmployeeBossId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go index 7766e086..4d62c62f 100644 --- a/testintegration/conditions/phone_conditions.go +++ b/testintegration/conditions/phone_conditions.go @@ -39,9 +39,10 @@ func PhoneName(operator orm.Operator[string]) orm.WhereCondition[models.Phone] { } func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.Condition[models.Phone] { return orm.JoinCondition[models.Phone, models.Brand]{ - Conditions: conditions, - T1Field: "BrandID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Brand", + T1Field: "BrandID", + T2Field: "ID", } } func PhoneBrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go index 6e49ebcf..65dc6486 100644 --- a/testintegration/conditions/sale_conditions.go +++ b/testintegration/conditions/sale_conditions.go @@ -45,9 +45,10 @@ func SaleDescription(operator orm.Operator[string]) orm.WhereCondition[models.Sa } func SaleProduct(conditions ...orm.Condition[models.Product]) orm.Condition[models.Sale] { return orm.JoinCondition[models.Sale, models.Product]{ - Conditions: conditions, - T1Field: "ProductID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Product", + T1Field: "ProductID", + T2Field: "ID", } } func SaleProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { @@ -58,9 +59,10 @@ func SaleProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sa } func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.Condition[models.Sale] { return orm.JoinCondition[models.Sale, models.Seller]{ - Conditions: conditions, - T1Field: "SellerID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Seller", + T1Field: "SellerID", + T2Field: "ID", } } func SaleSellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go index a42cf23a..0bb82534 100644 --- a/testintegration/conditions/seller_conditions.go +++ b/testintegration/conditions/seller_conditions.go @@ -39,9 +39,10 @@ func SellerName(operator orm.Operator[string]) orm.WhereCondition[models.Seller] } func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[models.Seller] { return orm.JoinCondition[models.Seller, models.Company]{ - Conditions: conditions, - T1Field: "CompanyID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Company", + T1Field: "CompanyID", + T2Field: "ID", } } func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index 71d21038..482706e4 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -370,7 +370,7 @@ func (ts *WhereConditionsIntTestSuite) TestJoinWithUnsafeCondition() { entities, err := ts.crudSaleService.Query( conditions.SaleSeller( conditions.SellerCompany( - orm.NewUnsafeCondition[models.Company]("%s.name = sellers_sales.name", []any{}), + orm.NewUnsafeCondition[models.Company]("%s.name = Seller.name", []any{}), ), ), ) From 32b8501e47646c7c6c919d9ffbfe97ebc86c4fa4 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 3 Aug 2023 16:46:24 +0200 Subject: [PATCH 62/90] update to conditions generation that fix embedded names --- testintegration/conditions/product_conditions.go | 2 +- testintegration/where_conditions_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go index 55f044fa..c5c3c8d0 100644 --- a/testintegration/conditions/product_conditions.go +++ b/testintegration/conditions/product_conditions.go @@ -85,7 +85,7 @@ func ProductMultiString(operator orm.Operator[models.MultiString]) orm.WhereCond Operator: operator, } } -func ProductEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { +func ProductToBeEmbeddedEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, int]{ Field: "EmbeddedInt", Operator: operator, diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index 59888ce0..ac6ca873 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -258,7 +258,7 @@ func (ts *WhereConditionsIntTestSuite) TestConditionOfEmbedded() { ts.Nil(err) entities, err := ts.crudProductService.Query( - conditions.ProductEmbeddedInt(orm.Eq(1)), + conditions.ProductToBeEmbeddedEmbeddedInt(orm.Eq(1)), ) ts.Nil(err) From 3def06b3734cd1f3910faa823d4ed10ea9ac447d Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 3 Aug 2023 10:10:49 +0200 Subject: [PATCH 63/90] refactor: add field identifier --- orm/condition.go | 42 +++++--- persistence/models/User.go | 4 +- testintegration/asserts.go | 1 + .../conditions/bicycle_conditions.go | 30 +++--- .../conditions/brand_conditions.go | 23 +++-- testintegration/conditions/city_conditions.go | 30 +++--- .../conditions/company_conditions.go | 23 +++-- .../conditions/country_conditions.go | 23 +++-- .../conditions/employee_conditions.go | 30 +++--- .../conditions/person_conditions.go | 23 +++-- .../conditions/phone_conditions.go | 30 +++--- .../conditions/product_conditions.go | 97 +++++++++++++------ testintegration/conditions/sale_conditions.go | 44 ++++++--- .../conditions/seller_conditions.go | 30 +++--- 14 files changed, 268 insertions(+), 162 deletions(-) diff --git a/orm/condition.go b/orm/condition.go index c75076d6..80047ab7 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -9,7 +9,14 @@ import ( "gorm.io/gorm" ) -const DeletedAtField = "DeletedAt" +const deletedAtField = "DeletedAt" + +var ( + IDFieldID = FieldIdentifier{Field: "ID"} + CreatedAtFieldID = FieldIdentifier{Field: "CreatedAt"} + UpdatedAtFieldID = FieldIdentifier{Field: "UpdatedAt"} + DeletedAtFieldID = FieldIdentifier{Field: deletedAtField} +) var ErrEmptyConditions = errors.New("condition must have at least one inner condition") @@ -169,13 +176,27 @@ func NewConnectionCondition[T any](connector string, conditions ...WhereConditio } } +type FieldIdentifier struct { + Column string + Field string + ColumnPrefix string +} + +func (columnID FieldIdentifier) ColumnName(db *gorm.DB, table Table) string { + columnName := columnID.Column + if columnName == "" { + columnName = db.NamingStrategy.ColumnName(table.Name, columnID.Field) + } + + // add column prefix and table name once we know the column name + return columnID.ColumnPrefix + columnName +} + // Condition that verifies the value of a field, // using the Operator type FieldCondition[TObject any, TAtribute any] struct { - Field string - Column string - ColumnPrefix string - Operator Operator[TAtribute] + FieldIdentifier FieldIdentifier + Operator Operator[TAtribute] } //nolint:unused // see inside @@ -208,18 +229,11 @@ func applyWhereCondition[T any](condition WhereCondition[T], query *gorm.DB, tab //nolint:unused // is used func (condition FieldCondition[TObject, TAtribute]) affectsDeletedAt() bool { - return condition.Field == DeletedAtField + return condition.FieldIdentifier.Field == deletedAtField } func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *gorm.DB, table Table) (string, []any, error) { - columnName := condition.Column - if columnName == "" { - columnName = query.NamingStrategy.ColumnName(table.Name, condition.Field) - } - - // add column prefix and table name once we know the column name - columnName = table.Alias + "." + condition.ColumnPrefix + columnName - + columnName := table.Alias + "." + condition.FieldIdentifier.ColumnName(query, table) return condition.Operator.ToSQL(columnName) } diff --git a/persistence/models/User.go b/persistence/models/User.go index 0a38a6ed..3a588a42 100644 --- a/persistence/models/User.go +++ b/persistence/models/User.go @@ -14,7 +14,9 @@ type User struct { func UserEmailCondition(operator orm.Operator[string]) orm.WhereCondition[User] { return orm.FieldCondition[User, string]{ + FieldIdentifier: orm.FieldIdentifier{ + Field: "Email", + }, Operator: operator, - Field: "Email", } } diff --git a/testintegration/asserts.go b/testintegration/asserts.go index b0c58e98..56c8677f 100644 --- a/testintegration/asserts.go +++ b/testintegration/asserts.go @@ -24,6 +24,7 @@ func EqualList[T any](ts *suite.Suite, expectedList, actualList []T) { } if j == expectedLen { ts.Fail("Lists not equal", "element %v not in list %v", expectedList[i], actualList) + for _, element := range actualList { log.Println(element) } diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go index b026ea12..dcedcb90 100644 --- a/testintegration/conditions/bicycle_conditions.go +++ b/testintegration/conditions/bicycle_conditions.go @@ -9,32 +9,35 @@ import ( func BicycleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, orm.UUID]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func BicycleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func BicycleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func BicycleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var bicycleNameFieldID = orm.FieldIdentifier{Field: "Name"} + func BicycleName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, string]{ - Field: "Name", - Operator: operator, + FieldIdentifier: bicycleNameFieldID, + Operator: operator, } } func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.Condition[models.Bicycle] { @@ -45,9 +48,12 @@ func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.Condition[mode T2Field: "Name", } } + +var bicycleOwnerNameFieldID = orm.FieldIdentifier{Field: "OwnerName"} + func BicycleOwnerName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, string]{ - Field: "OwnerName", - Operator: operator, + FieldIdentifier: bicycleOwnerNameFieldID, + Operator: operator, } } diff --git a/testintegration/conditions/brand_conditions.go b/testintegration/conditions/brand_conditions.go index 9d3421ab..f4f1ab03 100644 --- a/testintegration/conditions/brand_conditions.go +++ b/testintegration/conditions/brand_conditions.go @@ -9,31 +9,34 @@ import ( func BrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, uint]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func BrandCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func BrandUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func BrandDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var brandNameFieldID = orm.FieldIdentifier{Field: "Name"} + func BrandName(operator orm.Operator[string]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, string]{ - Field: "Name", - Operator: operator, + FieldIdentifier: brandNameFieldID, + Operator: operator, } } diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go index d7f54be0..d9e9e069 100644 --- a/testintegration/conditions/city_conditions.go +++ b/testintegration/conditions/city_conditions.go @@ -9,32 +9,35 @@ import ( func CityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, orm.UUID]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func CityCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func CityUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func CityDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var cityNameFieldID = orm.FieldIdentifier{Field: "Name"} + func CityName(operator orm.Operator[string]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, string]{ - Field: "Name", - Operator: operator, + FieldIdentifier: cityNameFieldID, + Operator: operator, } } func CityCountry(conditions ...orm.Condition[models.Country]) orm.Condition[models.City] { @@ -45,9 +48,12 @@ func CityCountry(conditions ...orm.Condition[models.Country]) orm.Condition[mode T2Field: "ID", } } + +var cityCountryIdFieldID = orm.FieldIdentifier{Field: "CountryID"} + func CityCountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, orm.UUID]{ - Field: "CountryID", - Operator: operator, + FieldIdentifier: cityCountryIdFieldID, + Operator: operator, } } diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go index 3c7c40cd..3ab78530 100644 --- a/testintegration/conditions/company_conditions.go +++ b/testintegration/conditions/company_conditions.go @@ -9,31 +9,34 @@ import ( func CompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, orm.UUID]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func CompanyCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func CompanyUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func CompanyDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var companyNameFieldID = orm.FieldIdentifier{Field: "Name"} + func CompanyName(operator orm.Operator[string]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, string]{ - Field: "Name", - Operator: operator, + FieldIdentifier: companyNameFieldID, + Operator: operator, } } diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go index 4db0dc33..e28a3674 100644 --- a/testintegration/conditions/country_conditions.go +++ b/testintegration/conditions/country_conditions.go @@ -9,32 +9,35 @@ import ( func CountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, orm.UUID]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func CountryCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func CountryUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func CountryDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var countryNameFieldID = orm.FieldIdentifier{Field: "Name"} + func CountryName(operator orm.Operator[string]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, string]{ - Field: "Name", - Operator: operator, + FieldIdentifier: countryNameFieldID, + Operator: operator, } } func CountryCapital(conditions ...orm.Condition[models.City]) orm.Condition[models.Country] { diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go index cc466f58..de09003b 100644 --- a/testintegration/conditions/employee_conditions.go +++ b/testintegration/conditions/employee_conditions.go @@ -9,32 +9,35 @@ import ( func EmployeeId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, orm.UUID]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func EmployeeCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func EmployeeUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func EmployeeDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var employeeNameFieldID = orm.FieldIdentifier{Field: "Name"} + func EmployeeName(operator orm.Operator[string]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, string]{ - Field: "Name", - Operator: operator, + FieldIdentifier: employeeNameFieldID, + Operator: operator, } } func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.Condition[models.Employee] { @@ -45,9 +48,12 @@ func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.Condition[mo T2Field: "ID", } } + +var employeeBossIdFieldID = orm.FieldIdentifier{Field: "BossID"} + func EmployeeBossId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, orm.UUID]{ - Field: "BossID", - Operator: operator, + FieldIdentifier: employeeBossIdFieldID, + Operator: operator, } } diff --git a/testintegration/conditions/person_conditions.go b/testintegration/conditions/person_conditions.go index c378abe7..b789d2b1 100644 --- a/testintegration/conditions/person_conditions.go +++ b/testintegration/conditions/person_conditions.go @@ -9,31 +9,34 @@ import ( func PersonId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, orm.UUID]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func PersonCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func PersonUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func PersonDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var personNameFieldID = orm.FieldIdentifier{Field: "Name"} + func PersonName(operator orm.Operator[string]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, string]{ - Field: "Name", - Operator: operator, + FieldIdentifier: personNameFieldID, + Operator: operator, } } diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go index 4d62c62f..328b52e4 100644 --- a/testintegration/conditions/phone_conditions.go +++ b/testintegration/conditions/phone_conditions.go @@ -9,32 +9,35 @@ import ( func PhoneId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, uint]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func PhoneCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func PhoneUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func PhoneDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var phoneNameFieldID = orm.FieldIdentifier{Field: "Name"} + func PhoneName(operator orm.Operator[string]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, string]{ - Field: "Name", - Operator: operator, + FieldIdentifier: phoneNameFieldID, + Operator: operator, } } func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.Condition[models.Phone] { @@ -45,9 +48,12 @@ func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.Condition[models. T2Field: "ID", } } + +var phoneBrandIdFieldID = orm.FieldIdentifier{Field: "BrandID"} + func PhoneBrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, uint]{ - Field: "BrandID", - Operator: operator, + FieldIdentifier: phoneBrandIdFieldID, + Operator: operator, } } diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go index c5c3c8d0..6755b5b6 100644 --- a/testintegration/conditions/product_conditions.go +++ b/testintegration/conditions/product_conditions.go @@ -9,92 +9,127 @@ import ( func ProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, orm.UUID]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func ProductCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func ProductUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func ProductDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var productStringFieldID = orm.FieldIdentifier{Column: "string_something_else"} + func ProductString(operator orm.Operator[string]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, string]{ - Column: "string_something_else", - Operator: operator, + FieldIdentifier: productStringFieldID, + Operator: operator, } } + +var productIntFieldID = orm.FieldIdentifier{Field: "Int"} + func ProductInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, int]{ - Field: "Int", - Operator: operator, + FieldIdentifier: productIntFieldID, + Operator: operator, } } + +var productIntPointerFieldID = orm.FieldIdentifier{Field: "IntPointer"} + func ProductIntPointer(operator orm.Operator[int]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, int]{ - Field: "IntPointer", - Operator: operator, + FieldIdentifier: productIntPointerFieldID, + Operator: operator, } } + +var productFloatFieldID = orm.FieldIdentifier{Field: "Float"} + func ProductFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, float64]{ - Field: "Float", - Operator: operator, + FieldIdentifier: productFloatFieldID, + Operator: operator, } } + +var productNullFloatFieldID = orm.FieldIdentifier{Field: "NullFloat"} + func ProductNullFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, float64]{ - Field: "NullFloat", - Operator: operator, + FieldIdentifier: productNullFloatFieldID, + Operator: operator, } } + +var productBoolFieldID = orm.FieldIdentifier{Field: "Bool"} + func ProductBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, bool]{ - Field: "Bool", - Operator: operator, + FieldIdentifier: productBoolFieldID, + Operator: operator, } } + +var productNullBoolFieldID = orm.FieldIdentifier{Field: "NullBool"} + func ProductNullBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, bool]{ - Field: "NullBool", - Operator: operator, + FieldIdentifier: productNullBoolFieldID, + Operator: operator, } } + +var productByteArrayFieldID = orm.FieldIdentifier{Field: "ByteArray"} + func ProductByteArray(operator orm.Operator[[]uint8]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, []uint8]{ - Field: "ByteArray", - Operator: operator, + FieldIdentifier: productByteArrayFieldID, + Operator: operator, } } + +var productMultiStringFieldID = orm.FieldIdentifier{Field: "MultiString"} + func ProductMultiString(operator orm.Operator[models.MultiString]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, models.MultiString]{ - Field: "MultiString", - Operator: operator, + FieldIdentifier: productMultiStringFieldID, + Operator: operator, } } + +var productToBeEmbeddedEmbeddedIntFieldID = orm.FieldIdentifier{Field: "EmbeddedInt"} + func ProductToBeEmbeddedEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, int]{ - Field: "EmbeddedInt", - Operator: operator, + FieldIdentifier: productToBeEmbeddedEmbeddedIntFieldID, + Operator: operator, } } + +var productGormEmbeddedIntFieldID = orm.FieldIdentifier{ + ColumnPrefix: "gorm_embedded_", + Field: "Int", +} + func ProductGormEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, int]{ - ColumnPrefix: "gorm_embedded_", - Field: "Int", - Operator: operator, + FieldIdentifier: productGormEmbeddedIntFieldID, + Operator: operator, } } diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go index 65dc6486..0d2db96f 100644 --- a/testintegration/conditions/sale_conditions.go +++ b/testintegration/conditions/sale_conditions.go @@ -9,38 +9,44 @@ import ( func SaleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, orm.UUID]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func SaleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func SaleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func SaleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var saleCodeFieldID = orm.FieldIdentifier{Field: "Code"} + func SaleCode(operator orm.Operator[int]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, int]{ - Field: "Code", - Operator: operator, + FieldIdentifier: saleCodeFieldID, + Operator: operator, } } + +var saleDescriptionFieldID = orm.FieldIdentifier{Field: "Description"} + func SaleDescription(operator orm.Operator[string]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, string]{ - Field: "Description", - Operator: operator, + FieldIdentifier: saleDescriptionFieldID, + Operator: operator, } } func SaleProduct(conditions ...orm.Condition[models.Product]) orm.Condition[models.Sale] { @@ -51,10 +57,13 @@ func SaleProduct(conditions ...orm.Condition[models.Product]) orm.Condition[mode T2Field: "ID", } } + +var saleProductIdFieldID = orm.FieldIdentifier{Field: "ProductID"} + func SaleProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, orm.UUID]{ - Field: "ProductID", - Operator: operator, + FieldIdentifier: saleProductIdFieldID, + Operator: operator, } } func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.Condition[models.Sale] { @@ -65,9 +74,12 @@ func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.Condition[models T2Field: "ID", } } + +var saleSellerIdFieldID = orm.FieldIdentifier{Field: "SellerID"} + func SaleSellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, orm.UUID]{ - Field: "SellerID", - Operator: operator, + FieldIdentifier: saleSellerIdFieldID, + Operator: operator, } } diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go index 0bb82534..dd501565 100644 --- a/testintegration/conditions/seller_conditions.go +++ b/testintegration/conditions/seller_conditions.go @@ -9,32 +9,35 @@ import ( func SellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, orm.UUID]{ - Field: "ID", - Operator: operator, + FieldIdentifier: orm.IDFieldID, + Operator: operator, } } func SellerCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, time.Time]{ - Field: "CreatedAt", - Operator: operator, + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, } } func SellerUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, time.Time]{ - Field: "UpdatedAt", - Operator: operator, + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, } } func SellerDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, time.Time]{ - Field: "DeletedAt", - Operator: operator, + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, } } + +var sellerNameFieldID = orm.FieldIdentifier{Field: "Name"} + func SellerName(operator orm.Operator[string]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, string]{ - Field: "Name", - Operator: operator, + FieldIdentifier: sellerNameFieldID, + Operator: operator, } } func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[models.Seller] { @@ -45,9 +48,12 @@ func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[mo T2Field: "ID", } } + +var sellerCompanyIdFieldID = orm.FieldIdentifier{Field: "CompanyID"} + func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, orm.UUID]{ - Field: "CompanyID", - Operator: operator, + FieldIdentifier: sellerCompanyIdFieldID, + Operator: operator, } } From 84460ccfe0c435db31df3f1ca81984ea5ed13437 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 3 Aug 2023 16:46:40 +0200 Subject: [PATCH 64/90] add possibility to do preload --- orm/condition.go | 78 +++++- orm/crudRepository.go | 2 +- .../conditions/bicycle_conditions.go | 4 + .../conditions/brand_conditions.go | 2 + testintegration/conditions/city_conditions.go | 4 + .../conditions/company_conditions.go | 2 + .../conditions/country_conditions.go | 4 + .../conditions/employee_conditions.go | 4 + .../conditions/person_conditions.go | 2 + .../conditions/phone_conditions.go | 4 + .../conditions/product_conditions.go | 2 + testintegration/conditions/sale_conditions.go | 5 + .../conditions/seller_conditions.go | 4 + testintegration/orm_test.go | 3 + testintegration/preload_conditions_test.go | 237 ++++++++++++++++++ 15 files changed, 350 insertions(+), 7 deletions(-) create mode 100644 testintegration/preload_conditions_test.go diff --git a/orm/condition.go b/orm/condition.go index 80047ab7..916684fe 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -192,6 +192,48 @@ func (columnID FieldIdentifier) ColumnName(db *gorm.DB, table Table) string { return columnID.ColumnPrefix + columnName } +// Condition used to the preload the attributes of a model +type PreloadCondition[T any] struct { + Fields []FieldIdentifier +} + +//nolint:unused // see inside +func (condition PreloadCondition[T]) interfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition PreloadCondition[T]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { + for _, fieldID := range condition.Fields { + columnName := fieldID.ColumnName(query, table) + + query.Statement.Selects = append( + query.Statement.Selects, + fmt.Sprintf( + "%[1]s.%[2]s AS \"%[1]s__%[2]s\"", // name used by gorm to load the fields inside the models + table.Alias, + columnName, + ), + ) + } + + return query, nil +} + +// Condition used to the preload the attributes of a model +func NewPreloadCondition[T any](fields ...FieldIdentifier) PreloadCondition[T] { + return PreloadCondition[T]{ + Fields: append( + fields, + // base model fields + IDFieldID, + CreatedAtFieldID, + UpdatedAtFieldID, + DeletedAtFieldID, + ), + } +} + // Condition that verifies the value of a field, // using the Operator type FieldCondition[TObject any, TAtribute any] struct { @@ -254,7 +296,7 @@ func (condition JoinCondition[T1, T2]) interfaceVerificationMethod(t T1) { // previousTableName is the name of the table of T1 // It also applies the nested conditions func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (*gorm.DB, error) { - whereConditions, joinConditions := divideConditionsByType(condition.Conditions) + whereConditions, joinConditions, preloadCondition := divideConditionsByType(condition.Conditions) // get the sql to do the join with T2 t2Table, err := t1Table.DeliverTable(query, *new(T2), condition.RelationField) @@ -262,10 +304,14 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (* return nil, err } + // get the sql to do the join with T2 + // if it's only a preload use a left join + isLeftJoin := len(whereConditions) == 0 && preloadCondition != nil joinQuery := condition.getSQLJoin( query, t1Table, t2Table, + isLeftJoin, ) // apply WhereConditions to the join in the "on" clause @@ -290,6 +336,14 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (* // add the join to the query query = query.Joins(joinQuery, onValues...) + // apply preload condition + if preloadCondition != nil { + query, err = preloadCondition.ApplyTo(query, t2Table) + if err != nil { + return nil, err + } + } + // apply nested joins for _, joinCondition := range joinConditions { query, err = joinCondition.ApplyTo(query, t2Table) @@ -308,28 +362,40 @@ func (condition JoinCondition[T1, T2]) getSQLJoin( query *gorm.DB, t1Table Table, t2Table Table, + isLeftJoin bool, ) string { + joinString := "INNER JOIN" + if isLeftJoin { + joinString = "LEFT JOIN" + } + return fmt.Sprintf( - `JOIN %[1]s %[2]s ON %[2]s.%[3]s = %[4]s.%[5]s + `%[6]s %[1]s %[2]s ON %[2]s.%[3]s = %[4]s.%[5]s `, t2Table.Name, t2Table.Alias, query.NamingStrategy.ColumnName(t2Table.Name, condition.T2Field), t1Table.Alias, query.NamingStrategy.ColumnName(t1Table.Name, condition.T1Field), + joinString, ) } // Divides a list of conditions by its type: WhereConditions and JoinConditions func divideConditionsByType[T any]( conditions []Condition[T], -) (thisEntityConditions []WhereCondition[T], joinConditions []Condition[T]) { +) (whereConditions []WhereCondition[T], joinConditions []Condition[T], preloadCondition *PreloadCondition[T]) { for _, condition := range conditions { - typedCondition, ok := condition.(WhereCondition[T]) + whereCondition, ok := condition.(WhereCondition[T]) if ok { - thisEntityConditions = append(thisEntityConditions, typedCondition) + whereConditions = append(whereConditions, whereCondition) } else { - joinConditions = append(joinConditions, condition) + possiblePreloadCondition, ok := condition.(PreloadCondition[T]) + if ok { + preloadCondition = &possiblePreloadCondition + } else { + joinConditions = append(joinConditions, condition) + } } } diff --git a/orm/crudRepository.go b/orm/crudRepository.go index fba453ff..912efdae 100644 --- a/orm/crudRepository.go +++ b/orm/crudRepository.go @@ -99,7 +99,7 @@ func (repository *CRUDRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...Co return nil, err } - query := tx + query := tx.Select(initialTableName + ".*") for _, condition := range conditions { query, err = condition.ApplyTo(query, Table{ Name: initialTableName, diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go index dcedcb90..4cf9da7b 100644 --- a/testintegration/conditions/bicycle_conditions.go +++ b/testintegration/conditions/bicycle_conditions.go @@ -49,6 +49,7 @@ func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.Condition[mode } } +var BicyclePreloadOwner = BicycleOwner(PersonPreloadAttributes) var bicycleOwnerNameFieldID = orm.FieldIdentifier{Field: "OwnerName"} func BicycleOwnerName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { @@ -57,3 +58,6 @@ func BicycleOwnerName(operator orm.Operator[string]) orm.WhereCondition[models.B Operator: operator, } } + +var BicyclePreloadAttributes = orm.NewPreloadCondition[models.Bicycle](bicycleNameFieldID, bicycleOwnerNameFieldID) +var BicyclePreloadRelations = []orm.Condition[models.Bicycle]{BicyclePreloadOwner} diff --git a/testintegration/conditions/brand_conditions.go b/testintegration/conditions/brand_conditions.go index f4f1ab03..a2e42afa 100644 --- a/testintegration/conditions/brand_conditions.go +++ b/testintegration/conditions/brand_conditions.go @@ -40,3 +40,5 @@ func BrandName(operator orm.Operator[string]) orm.WhereCondition[models.Brand] { Operator: operator, } } + +var BrandPreloadAttributes = orm.NewPreloadCondition[models.Brand](brandNameFieldID) diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go index d9e9e069..999a5382 100644 --- a/testintegration/conditions/city_conditions.go +++ b/testintegration/conditions/city_conditions.go @@ -49,6 +49,7 @@ func CityCountry(conditions ...orm.Condition[models.Country]) orm.Condition[mode } } +var CityPreloadCountry = CityCountry(CountryPreloadAttributes) var cityCountryIdFieldID = orm.FieldIdentifier{Field: "CountryID"} func CityCountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { @@ -57,3 +58,6 @@ func CityCountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Ci Operator: operator, } } + +var CityPreloadAttributes = orm.NewPreloadCondition[models.City](cityNameFieldID, cityCountryIdFieldID) +var CityPreloadRelations = []orm.Condition[models.City]{CityPreloadCountry} diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go index 3ab78530..408ec915 100644 --- a/testintegration/conditions/company_conditions.go +++ b/testintegration/conditions/company_conditions.go @@ -40,3 +40,5 @@ func CompanyName(operator orm.Operator[string]) orm.WhereCondition[models.Compan Operator: operator, } } + +var CompanyPreloadAttributes = orm.NewPreloadCondition[models.Company](companyNameFieldID) diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go index e28a3674..447579f9 100644 --- a/testintegration/conditions/country_conditions.go +++ b/testintegration/conditions/country_conditions.go @@ -48,3 +48,7 @@ func CountryCapital(conditions ...orm.Condition[models.City]) orm.Condition[mode T2Field: "CountryID", } } + +var CountryPreloadCapital = CountryCapital(CityPreloadAttributes) +var CountryPreloadAttributes = orm.NewPreloadCondition[models.Country](countryNameFieldID) +var CountryPreloadRelations = []orm.Condition[models.Country]{CountryPreloadCapital} diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go index de09003b..775be6ec 100644 --- a/testintegration/conditions/employee_conditions.go +++ b/testintegration/conditions/employee_conditions.go @@ -49,6 +49,7 @@ func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.Condition[mo } } +var EmployeePreloadBoss = EmployeeBoss(EmployeePreloadAttributes) var employeeBossIdFieldID = orm.FieldIdentifier{Field: "BossID"} func EmployeeBossId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { @@ -57,3 +58,6 @@ func EmployeeBossId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.E Operator: operator, } } + +var EmployeePreloadAttributes = orm.NewPreloadCondition[models.Employee](employeeNameFieldID, employeeBossIdFieldID) +var EmployeePreloadRelations = []orm.Condition[models.Employee]{EmployeePreloadBoss} diff --git a/testintegration/conditions/person_conditions.go b/testintegration/conditions/person_conditions.go index b789d2b1..6a674425 100644 --- a/testintegration/conditions/person_conditions.go +++ b/testintegration/conditions/person_conditions.go @@ -40,3 +40,5 @@ func PersonName(operator orm.Operator[string]) orm.WhereCondition[models.Person] Operator: operator, } } + +var PersonPreloadAttributes = orm.NewPreloadCondition[models.Person](personNameFieldID) diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go index 328b52e4..1aa282d8 100644 --- a/testintegration/conditions/phone_conditions.go +++ b/testintegration/conditions/phone_conditions.go @@ -49,6 +49,7 @@ func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.Condition[models. } } +var PhonePreloadBrand = PhoneBrand(BrandPreloadAttributes) var phoneBrandIdFieldID = orm.FieldIdentifier{Field: "BrandID"} func PhoneBrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { @@ -57,3 +58,6 @@ func PhoneBrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] Operator: operator, } } + +var PhonePreloadAttributes = orm.NewPreloadCondition[models.Phone](phoneNameFieldID, phoneBrandIdFieldID) +var PhonePreloadRelations = []orm.Condition[models.Phone]{PhonePreloadBrand} diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go index 6755b5b6..cb794a50 100644 --- a/testintegration/conditions/product_conditions.go +++ b/testintegration/conditions/product_conditions.go @@ -133,3 +133,5 @@ func ProductGormEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[model Operator: operator, } } + +var ProductPreloadAttributes = orm.NewPreloadCondition[models.Product](productStringFieldID, productIntFieldID, productIntPointerFieldID, productFloatFieldID, productNullFloatFieldID, productBoolFieldID, productNullBoolFieldID, productByteArrayFieldID, productMultiStringFieldID, productToBeEmbeddedEmbeddedIntFieldID, productGormEmbeddedIntFieldID) diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go index 0d2db96f..e8c6ff4a 100644 --- a/testintegration/conditions/sale_conditions.go +++ b/testintegration/conditions/sale_conditions.go @@ -58,6 +58,7 @@ func SaleProduct(conditions ...orm.Condition[models.Product]) orm.Condition[mode } } +var SalePreloadProduct = SaleProduct(ProductPreloadAttributes) var saleProductIdFieldID = orm.FieldIdentifier{Field: "ProductID"} func SaleProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { @@ -75,6 +76,7 @@ func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.Condition[models } } +var SalePreloadSeller = SaleSeller(SellerPreloadAttributes) var saleSellerIdFieldID = orm.FieldIdentifier{Field: "SellerID"} func SaleSellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { @@ -83,3 +85,6 @@ func SaleSellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sal Operator: operator, } } + +var SalePreloadAttributes = orm.NewPreloadCondition[models.Sale](saleCodeFieldID, saleDescriptionFieldID, saleProductIdFieldID, saleSellerIdFieldID) +var SalePreloadRelations = []orm.Condition[models.Sale]{SalePreloadProduct, SalePreloadSeller} diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go index dd501565..1dd4d569 100644 --- a/testintegration/conditions/seller_conditions.go +++ b/testintegration/conditions/seller_conditions.go @@ -49,6 +49,7 @@ func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[mo } } +var SellerPreloadCompany = SellerCompany(CompanyPreloadAttributes) var sellerCompanyIdFieldID = orm.FieldIdentifier{Field: "CompanyID"} func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { @@ -57,3 +58,6 @@ func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models. Operator: operator, } } + +var SellerPreloadAttributes = orm.NewPreloadCondition[models.Seller](sellerNameFieldID, sellerCompanyIdFieldID) +var SellerPreloadRelations = []orm.Condition[models.Seller]{SellerPreloadCompany} diff --git a/testintegration/orm_test.go b/testintegration/orm_test.go index 67953f65..cda6045f 100644 --- a/testintegration/orm_test.go +++ b/testintegration/orm_test.go @@ -63,6 +63,7 @@ func TestBaDaaSORM(t *testing.T) { fx.Provide(NewCRUDRepositoryIntTestSuite), fx.Provide(NewWhereConditionsIntTestSuite), fx.Provide(NewJoinConditionsIntTestSuite), + fx.Provide(NewPreloadConditionsIntTestSuite), fx.Provide(NewOperatorsIntTestSuite), // run tests @@ -74,6 +75,7 @@ func runORMTestSuites( tsCRUDRepository *CRUDRepositoryIntTestSuite, tsWhereConditions *WhereConditionsIntTestSuite, tsJoinConditions *JoinConditionsIntTestSuite, + tsPreloadConditions *PreloadConditionsIntTestSuite, tsOperators *OperatorsIntTestSuite, db *gorm.DB, shutdowner fx.Shutdowner, @@ -81,6 +83,7 @@ func runORMTestSuites( suite.Run(tGlobal, tsCRUDRepository) suite.Run(tGlobal, tsWhereConditions) suite.Run(tGlobal, tsJoinConditions) + suite.Run(tGlobal, tsPreloadConditions) suite.Run(tGlobal, tsOperators) shutdowner.Shutdown() diff --git a/testintegration/preload_conditions_test.go b/testintegration/preload_conditions_test.go new file mode 100644 index 00000000..5bd12522 --- /dev/null +++ b/testintegration/preload_conditions_test.go @@ -0,0 +1,237 @@ +package testintegration + +import ( + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" + "gotest.tools/assert" + + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/testintegration/conditions" + "github.com/ditrit/badaas/testintegration/models" +) + +type PreloadConditionsIntTestSuite struct { + CRUDServiceCommonIntTestSuite + crudSaleService orm.CRUDService[models.Sale, orm.UUID] + crudSellerService orm.CRUDService[models.Seller, orm.UUID] + crudCountryService orm.CRUDService[models.Country, orm.UUID] + crudCityService orm.CRUDService[models.City, orm.UUID] + crudEmployeeService orm.CRUDService[models.Employee, orm.UUID] + crudPhoneService orm.CRUDService[models.Phone, uint] +} + +func NewPreloadConditionsIntTestSuite( + db *gorm.DB, + crudSaleService orm.CRUDService[models.Sale, orm.UUID], + crudSellerService orm.CRUDService[models.Seller, orm.UUID], + crudCountryService orm.CRUDService[models.Country, orm.UUID], + crudCityService orm.CRUDService[models.City, orm.UUID], + crudEmployeeService orm.CRUDService[models.Employee, orm.UUID], + crudPhoneService orm.CRUDService[models.Phone, uint], +) *PreloadConditionsIntTestSuite { + return &PreloadConditionsIntTestSuite{ + CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ + db: db, + }, + crudSaleService: crudSaleService, + crudSellerService: crudSellerService, + crudCountryService: crudCountryService, + crudCityService: crudCityService, + crudEmployeeService: crudEmployeeService, + crudPhoneService: crudPhoneService, + } +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadWithoutWhereConditionDoesNotFilter() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + + withSeller := ts.createSale(0, product1, seller1) + withoutSeller := ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SalePreloadSeller, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{withSeller, withoutSeller}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + return sale.Seller.Equal(*seller1) + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + return sale.Seller == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadUIntModel() { + brand1 := ts.createBrand("google") + brand2 := ts.createBrand("apple") + + phone1 := ts.createPhone("pixel", *brand1) + phone2 := ts.createPhone("iphone", *brand2) + + entities, err := ts.crudPhoneService.Query( + conditions.PhonePreloadBrand, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Phone{phone1, phone2}, entities) + ts.True(pie.Any(entities, func(phone *models.Phone) bool { + return phone.Brand.Equal(*brand1) + })) + ts.True(pie.Any(entities, func(phone *models.Phone) bool { + return phone.Brand.Equal(*brand2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadWithWhereConditionFilters() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product1.EmbeddedInt = 1 + product1.GormEmbedded.Int = 2 + err := ts.db.Save(product1).Error + ts.Nil(err) + + product2 := ts.createProduct("", 2, 0.0, false, nil) + + match := ts.createSale(0, product1, nil) + ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductPreloadAttributes, + conditions.ProductInt(orm.Eq(1)), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) + assert.DeepEqual(ts.T(), *product1, entities[0].Product) + ts.Equal("a_string", entities[0].Product.String) + ts.Equal(1, entities[0].Product.EmbeddedInt) + ts.Equal(2, entities[0].Product.GormEmbedded.Int) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadOneToOne() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + country1 := ts.createCountry("Argentina", capital1) + country2 := ts.createCountry("France", capital2) + + entities, err := ts.crudCityService.Query( + conditions.CityPreloadCountry, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.City{&capital1, &capital2}, entities) + ts.True(pie.Any(entities, func(city *models.City) bool { + return city.Country.Equal(*country1) + })) + ts.True(pie.Any(entities, func(city *models.City) bool { + return city.Country.Equal(*country2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadHasMany() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("franco", company1) + seller2 := ts.createSeller("agustin", company2) + + entities, err := ts.crudSellerService.Query( + conditions.SellerPreloadCompany, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Seller{seller1, seller2}, entities) + ts.True(pie.Any(entities, func(seller *models.Seller) bool { + return seller.Company.Equal(*company1) + })) + ts.True(pie.Any(entities, func(seller *models.Seller) bool { + return seller.Company.Equal(*company2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadOneToOneReversed() { + capital1 := models.City{ + Name: "Buenos Aires", + } + capital2 := models.City{ + Name: "Paris", + } + + country1 := ts.createCountry("Argentina", capital1) + country2 := ts.createCountry("France", capital2) + + entities, err := ts.crudCountryService.Query( + conditions.CountryPreloadCapital, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Country{country1, country2}, entities) + ts.True(pie.Any(entities, func(country *models.Country) bool { + return country.Capital.Equal(capital1) + })) + ts.True(pie.Any(entities, func(country *models.Country) bool { + return country.Capital.Equal(capital2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadSelfReferential() { + boss1 := &models.Employee{ + Name: "Xavier", + } + + employee1 := ts.createEmployee("franco", boss1) + employee2 := ts.createEmployee("pierre", nil) + + entities, err := ts.crudEmployeeService.Query( + conditions.EmployeePreloadBoss, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Employee{boss1, employee1, employee2}, entities) + + ts.True(pie.Any(entities, func(employee *models.Employee) bool { + return employee.Boss != nil && employee.Boss.Equal(*boss1) + })) + ts.True(pie.Any(entities, func(employee *models.Employee) bool { + return employee.Boss == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadDifferentEntitiesWithConditions() { + product1 := ts.createProduct("", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + seller2 := ts.createSeller("agustin", nil) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + ts.createSale(0, product1, seller2) + ts.createSale(0, product2, seller1) + + entities, err := ts.crudSaleService.Query( + conditions.SaleProduct( + conditions.ProductPreloadAttributes, + conditions.ProductInt(orm.Eq(1)), + ), + conditions.SaleSeller( + conditions.SellerPreloadAttributes, + conditions.SellerName(orm.Eq("franco")), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) + assert.DeepEqual(ts.T(), *product1, entities[0].Product) + assert.DeepEqual(ts.T(), seller1, entities[0].Seller) +} From b353437cfb8677485eadc8f1a6a6ffdfeda2567a Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 3 Aug 2023 10:02:41 +0200 Subject: [PATCH 65/90] support nested preloads --- orm/condition.go | 71 ++++-- .../conditions/bicycle_conditions.go | 11 +- .../conditions/child_conditions.go | 75 +++++++ testintegration/conditions/city_conditions.go | 11 +- .../conditions/country_conditions.go | 11 +- .../conditions/employee_conditions.go | 11 +- .../conditions/parent1_conditions.go | 56 +++++ .../conditions/parent2_conditions.go | 56 +++++ .../conditions/parent_parent_conditions.go | 45 ++++ .../conditions/phone_conditions.go | 11 +- testintegration/conditions/sale_conditions.go | 22 +- .../conditions/seller_conditions.go | 11 +- testintegration/db_models.go | 4 + testintegration/models/models.go | 46 ++++ testintegration/orm_test.go | 1 + testintegration/preload_conditions_test.go | 204 ++++++++++++++++++ 16 files changed, 588 insertions(+), 58 deletions(-) create mode 100644 testintegration/conditions/child_conditions.go create mode 100644 testintegration/conditions/parent1_conditions.go create mode 100644 testintegration/conditions/parent2_conditions.go create mode 100644 testintegration/conditions/parent_parent_conditions.go diff --git a/orm/condition.go b/orm/condition.go index 916684fe..2b35377c 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -279,12 +279,22 @@ func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *gorm.DB, table return condition.Operator.ToSQL(columnName) } +// Interface of a join condition that joins T with any other model +type IJoinCondition[T any] interface { + Condition[T] + + // Returns true if this condition or any nested condition makes a preload + makesPreload() bool +} + // Condition that joins with other table type JoinCondition[T1 any, T2 any] struct { T1Field string T2Field string RelationField string Conditions []Condition[T2] + // condition to preload T1 in case T2 any nested object is preloaded by user + T1PreloadCondition PreloadCondition[T1] } func (condition JoinCondition[T1, T2]) interfaceVerificationMethod(t T1) { @@ -292,11 +302,20 @@ func (condition JoinCondition[T1, T2]) interfaceVerificationMethod(t T1) { // that an object is of type Condition[T] } +// Returns true if this condition or any nested condition makes a preload +func (condition JoinCondition[T1, T2]) makesPreload() bool { + _, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) + + return t2PreloadCondition != nil || pie.Any(joinConditions, func(cond IJoinCondition[T2]) bool { + return cond.makesPreload() + }) +} + // Applies a join between the tables of T1 and T2 // previousTableName is the name of the table of T1 // It also applies the nested conditions func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (*gorm.DB, error) { - whereConditions, joinConditions, preloadCondition := divideConditionsByType(condition.Conditions) + whereConditions, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) // get the sql to do the join with T2 t2Table, err := t1Table.DeliverTable(query, *new(T2), condition.RelationField) @@ -304,14 +323,12 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (* return nil, err } - // get the sql to do the join with T2 - // if it's only a preload use a left join - isLeftJoin := len(whereConditions) == 0 && preloadCondition != nil + makesPreload := condition.makesPreload() joinQuery := condition.getSQLJoin( query, t1Table, t2Table, - isLeftJoin, + len(whereConditions) == 0 && makesPreload, ) // apply WhereConditions to the join in the "on" clause @@ -336,9 +353,21 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (* // add the join to the query query = query.Joins(joinQuery, onValues...) - // apply preload condition - if preloadCondition != nil { - query, err = preloadCondition.ApplyTo(query, t2Table) + // apply T1 preload condition + // if this condition has a T2 preload condition + // or any nested join condition has a preload condition + // and this is not first level (T1 is the type of the repository) + // because T1 is always loaded in that case + if makesPreload && !t1Table.IsInitial() { + query, err = condition.T1PreloadCondition.ApplyTo(query, t1Table) + if err != nil { + return nil, err + } + } + + // apply T2 preload condition + if t2PreloadCondition != nil { + query, err = t2PreloadCondition.ApplyTo(query, t2Table) if err != nil { return nil, err } @@ -384,18 +413,24 @@ func (condition JoinCondition[T1, T2]) getSQLJoin( // Divides a list of conditions by its type: WhereConditions and JoinConditions func divideConditionsByType[T any]( conditions []Condition[T], -) (whereConditions []WhereCondition[T], joinConditions []Condition[T], preloadCondition *PreloadCondition[T]) { +) (whereConditions []WhereCondition[T], joinConditions []IJoinCondition[T], preloadCondition *PreloadCondition[T]) { for _, condition := range conditions { - whereCondition, ok := condition.(WhereCondition[T]) + possibleWhereCondition, ok := condition.(WhereCondition[T]) + if ok { + whereConditions = append(whereConditions, possibleWhereCondition) + continue + } + + possiblePreloadCondition, ok := condition.(PreloadCondition[T]) + if ok { + preloadCondition = &possiblePreloadCondition + continue + } + + possibleJoinCondition, ok := condition.(IJoinCondition[T]) if ok { - whereConditions = append(whereConditions, whereCondition) - } else { - possiblePreloadCondition, ok := condition.(PreloadCondition[T]) - if ok { - preloadCondition = &possiblePreloadCondition - } else { - joinConditions = append(joinConditions, condition) - } + joinConditions = append(joinConditions, possibleJoinCondition) + continue } } diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go index 4cf9da7b..d8e7fa9a 100644 --- a/testintegration/conditions/bicycle_conditions.go +++ b/testintegration/conditions/bicycle_conditions.go @@ -40,12 +40,13 @@ func BicycleName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycl Operator: operator, } } -func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.Condition[models.Bicycle] { +func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.IJoinCondition[models.Bicycle] { return orm.JoinCondition[models.Bicycle, models.Person]{ - Conditions: conditions, - RelationField: "Owner", - T1Field: "OwnerName", - T2Field: "Name", + Conditions: conditions, + RelationField: "Owner", + T1Field: "OwnerName", + T1PreloadCondition: BicyclePreloadAttributes, + T2Field: "Name", } } diff --git a/testintegration/conditions/child_conditions.go b/testintegration/conditions/child_conditions.go new file mode 100644 index 00000000..8b856349 --- /dev/null +++ b/testintegration/conditions/child_conditions.go @@ -0,0 +1,75 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func ChildId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Child] { + return orm.FieldCondition[models.Child, orm.UUID]{ + FieldIdentifier: orm.IDFieldID, + Operator: operator, + } +} +func ChildCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Child] { + return orm.FieldCondition[models.Child, time.Time]{ + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, + } +} +func ChildUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Child] { + return orm.FieldCondition[models.Child, time.Time]{ + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, + } +} +func ChildDeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.Child] { + return orm.FieldCondition[models.Child, gorm.DeletedAt]{ + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, + } +} +func ChildParent1(conditions ...orm.Condition[models.Parent1]) orm.IJoinCondition[models.Child] { + return orm.JoinCondition[models.Child, models.Parent1]{ + Conditions: conditions, + RelationField: "Parent1", + T1Field: "Parent1ID", + T1PreloadCondition: ChildPreloadAttributes, + T2Field: "ID", + } +} + +var ChildPreloadParent1 = ChildParent1(Parent1PreloadAttributes) +var childParent1IdFieldID = orm.FieldIdentifier{Field: "Parent1ID"} + +func ChildParent1Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Child] { + return orm.FieldCondition[models.Child, orm.UUID]{ + FieldIdentifier: childParent1IdFieldID, + Operator: operator, + } +} +func ChildParent2(conditions ...orm.Condition[models.Parent2]) orm.IJoinCondition[models.Child] { + return orm.JoinCondition[models.Child, models.Parent2]{ + Conditions: conditions, + RelationField: "Parent2", + T1Field: "Parent2ID", + T1PreloadCondition: ChildPreloadAttributes, + T2Field: "ID", + } +} + +var ChildPreloadParent2 = ChildParent2(Parent2PreloadAttributes) +var childParent2IdFieldID = orm.FieldIdentifier{Field: "Parent2ID"} + +func ChildParent2Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Child] { + return orm.FieldCondition[models.Child, orm.UUID]{ + FieldIdentifier: childParent2IdFieldID, + Operator: operator, + } +} + +var ChildPreloadAttributes = orm.NewPreloadCondition[models.Child](childParent1IdFieldID, childParent2IdFieldID) +var ChildPreloadRelations = []orm.Condition[models.Child]{ChildPreloadParent1, ChildPreloadParent2} diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go index 999a5382..458b8784 100644 --- a/testintegration/conditions/city_conditions.go +++ b/testintegration/conditions/city_conditions.go @@ -40,12 +40,13 @@ func CityName(operator orm.Operator[string]) orm.WhereCondition[models.City] { Operator: operator, } } -func CityCountry(conditions ...orm.Condition[models.Country]) orm.Condition[models.City] { +func CityCountry(conditions ...orm.Condition[models.Country]) orm.IJoinCondition[models.City] { return orm.JoinCondition[models.City, models.Country]{ - Conditions: conditions, - RelationField: "Country", - T1Field: "CountryID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Country", + T1Field: "CountryID", + T1PreloadCondition: CityPreloadAttributes, + T2Field: "ID", } } diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go index 447579f9..14dcaeeb 100644 --- a/testintegration/conditions/country_conditions.go +++ b/testintegration/conditions/country_conditions.go @@ -40,12 +40,13 @@ func CountryName(operator orm.Operator[string]) orm.WhereCondition[models.Countr Operator: operator, } } -func CountryCapital(conditions ...orm.Condition[models.City]) orm.Condition[models.Country] { +func CountryCapital(conditions ...orm.Condition[models.City]) orm.IJoinCondition[models.Country] { return orm.JoinCondition[models.Country, models.City]{ - Conditions: conditions, - RelationField: "Capital", - T1Field: "ID", - T2Field: "CountryID", + Conditions: conditions, + RelationField: "Capital", + T1Field: "ID", + T1PreloadCondition: CountryPreloadAttributes, + T2Field: "CountryID", } } diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go index 775be6ec..b2f9f0d4 100644 --- a/testintegration/conditions/employee_conditions.go +++ b/testintegration/conditions/employee_conditions.go @@ -40,12 +40,13 @@ func EmployeeName(operator orm.Operator[string]) orm.WhereCondition[models.Emplo Operator: operator, } } -func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.Condition[models.Employee] { +func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.IJoinCondition[models.Employee] { return orm.JoinCondition[models.Employee, models.Employee]{ - Conditions: conditions, - RelationField: "Boss", - T1Field: "BossID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Boss", + T1Field: "BossID", + T1PreloadCondition: EmployeePreloadAttributes, + T2Field: "ID", } } diff --git a/testintegration/conditions/parent1_conditions.go b/testintegration/conditions/parent1_conditions.go new file mode 100644 index 00000000..6907024e --- /dev/null +++ b/testintegration/conditions/parent1_conditions.go @@ -0,0 +1,56 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func Parent1Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent1] { + return orm.FieldCondition[models.Parent1, orm.UUID]{ + FieldIdentifier: orm.IDFieldID, + Operator: operator, + } +} +func Parent1CreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent1] { + return orm.FieldCondition[models.Parent1, time.Time]{ + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, + } +} +func Parent1UpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent1] { + return orm.FieldCondition[models.Parent1, time.Time]{ + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, + } +} +func Parent1DeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.Parent1] { + return orm.FieldCondition[models.Parent1, gorm.DeletedAt]{ + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, + } +} +func Parent1ParentParent(conditions ...orm.Condition[models.ParentParent]) orm.IJoinCondition[models.Parent1] { + return orm.JoinCondition[models.Parent1, models.ParentParent]{ + Conditions: conditions, + RelationField: "ParentParent", + T1Field: "ParentParentID", + T1PreloadCondition: Parent1PreloadAttributes, + T2Field: "ID", + } +} + +var Parent1PreloadParentParent = Parent1ParentParent(ParentParentPreloadAttributes) +var parent1ParentParentIdFieldID = orm.FieldIdentifier{Field: "ParentParentID"} + +func Parent1ParentParentId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent1] { + return orm.FieldCondition[models.Parent1, orm.UUID]{ + FieldIdentifier: parent1ParentParentIdFieldID, + Operator: operator, + } +} + +var Parent1PreloadAttributes = orm.NewPreloadCondition[models.Parent1](parent1ParentParentIdFieldID) +var Parent1PreloadRelations = []orm.Condition[models.Parent1]{Parent1PreloadParentParent} diff --git a/testintegration/conditions/parent2_conditions.go b/testintegration/conditions/parent2_conditions.go new file mode 100644 index 00000000..96ed18dd --- /dev/null +++ b/testintegration/conditions/parent2_conditions.go @@ -0,0 +1,56 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func Parent2Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent2] { + return orm.FieldCondition[models.Parent2, orm.UUID]{ + FieldIdentifier: orm.IDFieldID, + Operator: operator, + } +} +func Parent2CreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent2] { + return orm.FieldCondition[models.Parent2, time.Time]{ + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, + } +} +func Parent2UpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent2] { + return orm.FieldCondition[models.Parent2, time.Time]{ + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, + } +} +func Parent2DeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.Parent2] { + return orm.FieldCondition[models.Parent2, gorm.DeletedAt]{ + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, + } +} +func Parent2ParentParent(conditions ...orm.Condition[models.ParentParent]) orm.IJoinCondition[models.Parent2] { + return orm.JoinCondition[models.Parent2, models.ParentParent]{ + Conditions: conditions, + RelationField: "ParentParent", + T1Field: "ParentParentID", + T1PreloadCondition: Parent2PreloadAttributes, + T2Field: "ID", + } +} + +var Parent2PreloadParentParent = Parent2ParentParent(ParentParentPreloadAttributes) +var parent2ParentParentIdFieldID = orm.FieldIdentifier{Field: "ParentParentID"} + +func Parent2ParentParentId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent2] { + return orm.FieldCondition[models.Parent2, orm.UUID]{ + FieldIdentifier: parent2ParentParentIdFieldID, + Operator: operator, + } +} + +var Parent2PreloadAttributes = orm.NewPreloadCondition[models.Parent2](parent2ParentParentIdFieldID) +var Parent2PreloadRelations = []orm.Condition[models.Parent2]{Parent2PreloadParentParent} diff --git a/testintegration/conditions/parent_parent_conditions.go b/testintegration/conditions/parent_parent_conditions.go new file mode 100644 index 00000000..613881cc --- /dev/null +++ b/testintegration/conditions/parent_parent_conditions.go @@ -0,0 +1,45 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func ParentParentId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.ParentParent] { + return orm.FieldCondition[models.ParentParent, orm.UUID]{ + FieldIdentifier: orm.IDFieldID, + Operator: operator, + } +} +func ParentParentCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.ParentParent] { + return orm.FieldCondition[models.ParentParent, time.Time]{ + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, + } +} +func ParentParentUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.ParentParent] { + return orm.FieldCondition[models.ParentParent, time.Time]{ + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, + } +} +func ParentParentDeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.ParentParent] { + return orm.FieldCondition[models.ParentParent, gorm.DeletedAt]{ + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, + } +} + +var parentParentNameFieldID = orm.FieldIdentifier{Field: "Name"} + +func ParentParentName(operator orm.Operator[string]) orm.WhereCondition[models.ParentParent] { + return orm.FieldCondition[models.ParentParent, string]{ + FieldIdentifier: parentParentNameFieldID, + Operator: operator, + } +} + +var ParentParentPreloadAttributes = orm.NewPreloadCondition[models.ParentParent](parentParentNameFieldID) diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go index 1aa282d8..4f0c5da9 100644 --- a/testintegration/conditions/phone_conditions.go +++ b/testintegration/conditions/phone_conditions.go @@ -40,12 +40,13 @@ func PhoneName(operator orm.Operator[string]) orm.WhereCondition[models.Phone] { Operator: operator, } } -func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.Condition[models.Phone] { +func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.IJoinCondition[models.Phone] { return orm.JoinCondition[models.Phone, models.Brand]{ - Conditions: conditions, - RelationField: "Brand", - T1Field: "BrandID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Brand", + T1Field: "BrandID", + T1PreloadCondition: PhonePreloadAttributes, + T2Field: "ID", } } diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go index e8c6ff4a..5fb11c46 100644 --- a/testintegration/conditions/sale_conditions.go +++ b/testintegration/conditions/sale_conditions.go @@ -49,12 +49,13 @@ func SaleDescription(operator orm.Operator[string]) orm.WhereCondition[models.Sa Operator: operator, } } -func SaleProduct(conditions ...orm.Condition[models.Product]) orm.Condition[models.Sale] { +func SaleProduct(conditions ...orm.Condition[models.Product]) orm.IJoinCondition[models.Sale] { return orm.JoinCondition[models.Sale, models.Product]{ - Conditions: conditions, - RelationField: "Product", - T1Field: "ProductID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Product", + T1Field: "ProductID", + T1PreloadCondition: SalePreloadAttributes, + T2Field: "ID", } } @@ -67,12 +68,13 @@ func SaleProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sa Operator: operator, } } -func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.Condition[models.Sale] { +func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.IJoinCondition[models.Sale] { return orm.JoinCondition[models.Sale, models.Seller]{ - Conditions: conditions, - RelationField: "Seller", - T1Field: "SellerID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Seller", + T1Field: "SellerID", + T1PreloadCondition: SalePreloadAttributes, + T2Field: "ID", } } diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go index 1dd4d569..f321579b 100644 --- a/testintegration/conditions/seller_conditions.go +++ b/testintegration/conditions/seller_conditions.go @@ -40,12 +40,13 @@ func SellerName(operator orm.Operator[string]) orm.WhereCondition[models.Seller] Operator: operator, } } -func SellerCompany(conditions ...orm.Condition[models.Company]) orm.Condition[models.Seller] { +func SellerCompany(conditions ...orm.Condition[models.Company]) orm.IJoinCondition[models.Seller] { return orm.JoinCondition[models.Seller, models.Company]{ - Conditions: conditions, - RelationField: "Company", - T1Field: "CompanyID", - T2Field: "ID", + Conditions: conditions, + RelationField: "Company", + T1Field: "CompanyID", + T1PreloadCondition: SellerPreloadAttributes, + T2Field: "ID", } } diff --git a/testintegration/db_models.go b/testintegration/db_models.go index 2c24b637..fdbb11e2 100644 --- a/testintegration/db_models.go +++ b/testintegration/db_models.go @@ -22,6 +22,10 @@ var ListOfTables = []any{ models.Bicycle{}, models.Brand{}, models.Phone{}, + models.ParentParent{}, + models.Parent1{}, + models.Parent2{}, + models.Child{}, } func GetModels() orm.GetModelsResult { diff --git a/testintegration/models/models.go b/testintegration/models/models.go index 70badc96..b2084129 100644 --- a/testintegration/models/models.go +++ b/testintegration/models/models.go @@ -186,3 +186,49 @@ type Phone struct { func (m Phone) Equal(other Phone) bool { return m.Name == other.Name } + +type ParentParent struct { + orm.UUIDModel + + Name string +} + +func (m ParentParent) Equal(other ParentParent) bool { + return m.ID == other.ID +} + +type Parent1 struct { + orm.UUIDModel + + ParentParent ParentParent + ParentParentID orm.UUID +} + +func (m Parent1) Equal(other Parent1) bool { + return m.ID == other.ID +} + +type Parent2 struct { + orm.UUIDModel + + ParentParent ParentParent + ParentParentID orm.UUID +} + +func (m Parent2) Equal(other Parent2) bool { + return m.ID == other.ID +} + +type Child struct { + orm.UUIDModel + + Parent1 Parent1 + Parent1ID orm.UUID + + Parent2 Parent2 + Parent2ID orm.UUID +} + +func (m Child) Equal(other Child) bool { + return m.ID == other.ID +} diff --git a/testintegration/orm_test.go b/testintegration/orm_test.go index cda6045f..f8447cd5 100644 --- a/testintegration/orm_test.go +++ b/testintegration/orm_test.go @@ -58,6 +58,7 @@ func TestBaDaaSORM(t *testing.T) { orm.GetCRUDServiceModule[models.Bicycle](), orm.GetCRUDServiceModule[models.Phone](), orm.GetCRUDServiceModule[models.Brand](), + orm.GetCRUDServiceModule[models.Child](), // create test suites fx.Provide(NewCRUDRepositoryIntTestSuite), diff --git a/testintegration/preload_conditions_test.go b/testintegration/preload_conditions_test.go index 5bd12522..dd97dc59 100644 --- a/testintegration/preload_conditions_test.go +++ b/testintegration/preload_conditions_test.go @@ -18,6 +18,7 @@ type PreloadConditionsIntTestSuite struct { crudCityService orm.CRUDService[models.City, orm.UUID] crudEmployeeService orm.CRUDService[models.Employee, orm.UUID] crudPhoneService orm.CRUDService[models.Phone, uint] + crudChildService orm.CRUDService[models.Child, orm.UUID] } func NewPreloadConditionsIntTestSuite( @@ -28,6 +29,7 @@ func NewPreloadConditionsIntTestSuite( crudCityService orm.CRUDService[models.City, orm.UUID], crudEmployeeService orm.CRUDService[models.Employee, orm.UUID], crudPhoneService orm.CRUDService[models.Phone, uint], + crudChildService orm.CRUDService[models.Child, orm.UUID], ) *PreloadConditionsIntTestSuite { return &PreloadConditionsIntTestSuite{ CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ @@ -39,6 +41,7 @@ func NewPreloadConditionsIntTestSuite( crudCityService: crudCityService, crudEmployeeService: crudEmployeeService, crudPhoneService: crudPhoneService, + crudChildService: crudChildService, } } @@ -207,6 +210,35 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadSelfReferential() { })) } +func (ts *PreloadConditionsIntTestSuite) TestPreloadSelfReferentialAtSecondLevel() { + bossBoss := &models.Employee{ + Name: "Xavier", + } + boss := &models.Employee{ + Name: "Vincent", + Boss: bossBoss, + } + employee := ts.createEmployee("franco", boss) + + entities, err := ts.crudEmployeeService.Query( + conditions.EmployeeBoss( + conditions.EmployeeBoss( + conditions.EmployeePreloadAttributes, + ), + ), + conditions.EmployeeName(orm.Eq("franco")), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Employee{employee}, entities) + + bossLoaded := entities[0].Boss + ts.True(bossLoaded.Equal(*boss)) + + bossBossLoaded := bossLoaded.Boss + ts.True(bossBossLoaded.Equal(*bossBoss)) +} + func (ts *PreloadConditionsIntTestSuite) TestPreloadDifferentEntitiesWithConditions() { product1 := ts.createProduct("", 1, 0.0, false, nil) product2 := ts.createProduct("", 2, 0.0, false, nil) @@ -235,3 +267,175 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadDifferentEntitiesWithConditi assert.DeepEqual(ts.T(), *product1, entities[0].Product) assert.DeepEqual(ts.T(), seller1, entities[0].Seller) } + +func (ts *PreloadConditionsIntTestSuite) TestPreloadDifferentEntitiesWithoutConditions() { + parentParent := &models.ParentParent{} + err := ts.db.Create(parentParent).Error + ts.Nil(err) + + parent1 := &models.Parent1{ParentParent: *parentParent} + err = ts.db.Create(parent1).Error + ts.Nil(err) + + parent2 := &models.Parent2{ParentParent: *parentParent} + err = ts.db.Create(parent2).Error + ts.Nil(err) + + child := &models.Child{Parent1: *parent1, Parent2: *parent2} + err = ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildPreloadParent1, + conditions.ChildPreloadParent2, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) + assert.DeepEqual(ts.T(), *parent1, entities[0].Parent1) + assert.DeepEqual(ts.T(), *parent2, entities[0].Parent2) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadRelations() { + parentParent := &models.ParentParent{} + err := ts.db.Create(parentParent).Error + ts.Nil(err) + + parent1 := &models.Parent1{ParentParent: *parentParent} + err = ts.db.Create(parent1).Error + ts.Nil(err) + + parent2 := &models.Parent2{ParentParent: *parentParent} + err = ts.db.Create(parent2).Error + ts.Nil(err) + + child := &models.Child{Parent1: *parent1, Parent2: *parent2} + err = ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildPreloadRelations..., + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) + assert.DeepEqual(ts.T(), *parent1, entities[0].Parent1) + assert.DeepEqual(ts.T(), *parent2, entities[0].Parent2) +} + +func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadWithoutCondition() { + parentParent := &models.ParentParent{} + err := ts.db.Create(parentParent).Error + ts.Nil(err) + + parent1 := &models.Parent1{ParentParent: *parentParent} + err = ts.db.Create(parent1).Error + ts.Nil(err) + + parent2 := &models.Parent2{ParentParent: *parentParent} + err = ts.db.Create(parent2).Error + ts.Nil(err) + + child := &models.Child{Parent1: *parent1, Parent2: *parent2} + err = ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1PreloadAttributes, + conditions.Parent1PreloadParentParent, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) + assert.DeepEqual(ts.T(), *parent1, entities[0].Parent1) + assert.DeepEqual(ts.T(), *parentParent, entities[0].Parent1.ParentParent) +} + +func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadWithCondition() { + parentParent1 := &models.ParentParent{ + Name: "parentParent1", + } + err := ts.db.Create(parentParent1).Error + ts.Nil(err) + + parent11 := &models.Parent1{ParentParent: *parentParent1} + err = ts.db.Create(parent11).Error + ts.Nil(err) + + parent21 := &models.Parent2{ParentParent: *parentParent1} + err = ts.db.Create(parent21).Error + ts.Nil(err) + + child1 := &models.Child{Parent1: *parent11, Parent2: *parent21} + err = ts.db.Create(child1).Error + ts.Nil(err) + + parentParent2 := &models.ParentParent{} + err = ts.db.Create(parentParent2).Error + ts.Nil(err) + + parent12 := &models.Parent1{ParentParent: *parentParent2} + err = ts.db.Create(parent12).Error + ts.Nil(err) + + parent22 := &models.Parent2{ParentParent: *parentParent2} + err = ts.db.Create(parent22).Error + ts.Nil(err) + + child2 := &models.Child{Parent1: *parent12, Parent2: *parent22} + err = ts.db.Create(child2).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1PreloadAttributes, + conditions.Parent1ParentParent( + conditions.ParentParentPreloadAttributes, + conditions.ParentParentName(orm.Eq("parentParent1")), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child1}, entities) + assert.DeepEqual(ts.T(), *parent11, entities[0].Parent1) + assert.DeepEqual(ts.T(), *parentParent1, entities[0].Parent1.ParentParent) +} + +func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadDiamond() { + parentParent := &models.ParentParent{} + err := ts.db.Create(parentParent).Error + ts.Nil(err) + + parent1 := &models.Parent1{ParentParent: *parentParent} + err = ts.db.Create(parent1).Error + ts.Nil(err) + + parent2 := &models.Parent2{ParentParent: *parentParent} + err = ts.db.Create(parent2).Error + ts.Nil(err) + + child := &models.Child{Parent1: *parent1, Parent2: *parent2} + err = ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1PreloadAttributes, + conditions.Parent1PreloadParentParent, + ), + conditions.ChildParent2( + conditions.Parent2PreloadAttributes, + conditions.Parent2PreloadParentParent, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) + assert.DeepEqual(ts.T(), *parent1, entities[0].Parent1) + assert.DeepEqual(ts.T(), *parent2, entities[0].Parent2) + assert.DeepEqual(ts.T(), *parentParent, entities[0].Parent1.ParentParent) + assert.DeepEqual(ts.T(), *parentParent, entities[0].Parent2.ParentParent) +} From 7fa6b187d190d4387e62ff4aee8833021efeb057 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 3 Aug 2023 17:17:08 +0200 Subject: [PATCH 66/90] add a way to know if a relation was loaded or not --- orm/ModuleFx.go | 8 +- orm/baseModels.go | 41 +- orm/condition.go | 38 +- orm/crudRepository.go | 6 +- orm/crudService.go | 6 +- orm/orm.go | 2 +- orm/preload.go | 33 ++ orm/uuid.go | 6 +- testintegration/asserts.go | 4 +- .../conditions/brand_conditions.go | 4 +- .../conditions/phone_conditions.go | 4 +- testintegration/join_conditions_test.go | 10 +- testintegration/models/badaas-orm.go | 41 ++ testintegration/models/models.go | 2 +- testintegration/preload_conditions_test.go | 368 +++++++++++++++--- testintegration/where_conditions_test.go | 4 +- 16 files changed, 476 insertions(+), 101 deletions(-) create mode 100644 orm/preload.go create mode 100644 testintegration/models/badaas-orm.go diff --git a/orm/ModuleFx.go b/orm/ModuleFx.go index a55b9bfa..fe1cf33b 100644 --- a/orm/ModuleFx.go +++ b/orm/ModuleFx.go @@ -24,7 +24,7 @@ var AutoMigrate = fx.Module( ), ) -func GetCRUDServiceModule[T any]() fx.Option { +func GetCRUDServiceModule[T Model]() fx.Option { entity := *new(T) moduleName := fmt.Sprintf( @@ -46,9 +46,9 @@ func GetCRUDServiceModule[T any]() fx.Option { return fx.Module( moduleName, // repository - fx.Provide(NewCRUDRepository[T, uint]), + fx.Provide(NewCRUDRepository[T, UIntID]), // service - fx.Provide(NewCRUDService[T, uint]), + fx.Provide(NewCRUDService[T, UIntID]), ) default: log.Printf("type %T is not a BaDaaS model\n", entity) @@ -68,7 +68,7 @@ const ( KindNotModel ) -func getModelKind(entity any) modelKind { +func getModelKind(entity Model) modelKind { entityType := getEntityType(entity) _, isUUIDModel := entityType.FieldByName("UUIDModel") diff --git a/orm/baseModels.go b/orm/baseModels.go index 3a0b4816..c6349ae3 100644 --- a/orm/baseModels.go +++ b/orm/baseModels.go @@ -3,14 +3,18 @@ package orm import ( "time" - "github.com/google/uuid" - "gorm.io/gorm" ) // supported types for model identifier -type BadaasID interface { - uint | UUID +type ModelID interface { + UIntID | UUID + + IsNil() bool +} + +type Model interface { + IsLoaded() bool } // Base Model for gorm @@ -24,11 +28,32 @@ type UUIDModel struct { DeletedAt gorm.DeletedAt `gorm:"index"` } -func (model *UUIDModel) BeforeCreate(tx *gorm.DB) (err error) { - if model.ID == UUID(uuid.Nil) { - model.ID = UUID(uuid.New()) +func (model UUIDModel) IsLoaded() bool { + return !model.ID.IsNil() +} + +func (model *UUIDModel) BeforeCreate(_ *gorm.DB) (err error) { + if model.ID == NilUUID { + model.ID = NewUUID() } return nil } -type UIntModel gorm.Model +type UIntID uint + +const NilUIntID = 0 + +func (id UIntID) IsNil() bool { + return id == NilUIntID +} + +type UIntModel struct { + ID UIntID `gorm:"primarykey;not null"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` +} + +func (model UIntModel) IsLoaded() bool { + return !model.ID.IsNil() +} diff --git a/orm/condition.go b/orm/condition.go index 2b35377c..a763b926 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -32,7 +32,7 @@ func (table Table) IsInitial() bool { } // Returns the related Table corresponding to the model -func (table Table) DeliverTable(query *gorm.DB, model any, relationName string) (Table, error) { +func (table Table) DeliverTable(query *gorm.DB, model Model, relationName string) (Table, error) { // get the name of the table for the model tableName, err := getTableName(query, model) if err != nil { @@ -53,7 +53,7 @@ func (table Table) DeliverTable(query *gorm.DB, model any, relationName string) }, nil } -type Condition[T any] interface { +type Condition[T Model] interface { // Applies the condition to the "query" // using the "tableName" as name for the table holding // the data for object of type T @@ -68,7 +68,7 @@ type Condition[T any] interface { // Conditions that can be used in a where clause // (or in a on of a join) -type WhereCondition[T any] interface { +type WhereCondition[T Model] interface { Condition[T] // Get the sql string and values to use in the query @@ -81,7 +81,7 @@ type WhereCondition[T any] interface { // Condition that contains a internal condition. // Example: NOT (internal condition) -type ContainerCondition[T any] struct { +type ContainerCondition[T Model] struct { ConnectionCondition WhereCondition[T] Prefix string } @@ -114,7 +114,7 @@ func (condition ContainerCondition[T]) affectsDeletedAt() bool { // Condition that contains a internal condition. // Example: NOT (internal condition) -func NewContainerCondition[T any](prefix string, conditions ...WhereCondition[T]) WhereCondition[T] { +func NewContainerCondition[T Model](prefix string, conditions ...WhereCondition[T]) WhereCondition[T] { if len(conditions) == 0 { return NewInvalidCondition[T](ErrEmptyConditions) } @@ -127,7 +127,7 @@ func NewContainerCondition[T any](prefix string, conditions ...WhereCondition[T] // Condition that connects multiple conditions. // Example: condition1 AND condition2 -type ConnectionCondition[T any] struct { +type ConnectionCondition[T Model] struct { Connector string Conditions []WhereCondition[T] } @@ -169,7 +169,7 @@ func (condition ConnectionCondition[T]) affectsDeletedAt() bool { // Condition that connects multiple conditions. // Example: condition1 AND condition2 -func NewConnectionCondition[T any](connector string, conditions ...WhereCondition[T]) WhereCondition[T] { +func NewConnectionCondition[T Model](connector string, conditions ...WhereCondition[T]) WhereCondition[T] { return ConnectionCondition[T]{ Connector: connector, Conditions: conditions, @@ -193,7 +193,7 @@ func (columnID FieldIdentifier) ColumnName(db *gorm.DB, table Table) string { } // Condition used to the preload the attributes of a model -type PreloadCondition[T any] struct { +type PreloadCondition[T Model] struct { Fields []FieldIdentifier } @@ -221,7 +221,7 @@ func (condition PreloadCondition[T]) ApplyTo(query *gorm.DB, table Table) (*gorm } // Condition used to the preload the attributes of a model -func NewPreloadCondition[T any](fields ...FieldIdentifier) PreloadCondition[T] { +func NewPreloadCondition[T Model](fields ...FieldIdentifier) PreloadCondition[T] { return PreloadCondition[T]{ Fields: append( fields, @@ -236,7 +236,7 @@ func NewPreloadCondition[T any](fields ...FieldIdentifier) PreloadCondition[T] { // Condition that verifies the value of a field, // using the Operator -type FieldCondition[TObject any, TAtribute any] struct { +type FieldCondition[TObject Model, TAtribute any] struct { FieldIdentifier FieldIdentifier Operator Operator[TAtribute] } @@ -253,7 +253,7 @@ func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *gorm.DB, tabl return applyWhereCondition[TObject](condition, query, table) } -func applyWhereCondition[T any](condition WhereCondition[T], query *gorm.DB, table Table) (*gorm.DB, error) { +func applyWhereCondition[T Model](condition WhereCondition[T], query *gorm.DB, table Table) (*gorm.DB, error) { sql, values, err := condition.GetSQL(query, table) if err != nil { return nil, err @@ -280,7 +280,7 @@ func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *gorm.DB, table } // Interface of a join condition that joins T with any other model -type IJoinCondition[T any] interface { +type IJoinCondition[T Model] interface { Condition[T] // Returns true if this condition or any nested condition makes a preload @@ -288,7 +288,7 @@ type IJoinCondition[T any] interface { } // Condition that joins with other table -type JoinCondition[T1 any, T2 any] struct { +type JoinCondition[T1 Model, T2 Model] struct { T1Field string T2Field string RelationField string @@ -411,7 +411,7 @@ func (condition JoinCondition[T1, T2]) getSQLJoin( } // Divides a list of conditions by its type: WhereConditions and JoinConditions -func divideConditionsByType[T any]( +func divideConditionsByType[T Model]( conditions []Condition[T], ) (whereConditions []WhereCondition[T], joinConditions []IJoinCondition[T], preloadCondition *PreloadCondition[T]) { for _, condition := range conditions { @@ -439,7 +439,7 @@ func divideConditionsByType[T any]( // Condition that can be used to express conditions that are not supported (yet?) by BaDORM // Example: table1.columnX = table2.columnY -type UnsafeCondition[T any] struct { +type UnsafeCondition[T Model] struct { SQLCondition string Values []any } @@ -468,7 +468,7 @@ func (condition UnsafeCondition[T]) affectsDeletedAt() bool { // Condition that can be used to express conditions that are not supported (yet?) by BaDORM // Example: table1.columnX = table2.columnY -func NewUnsafeCondition[T any](condition string, values []any) UnsafeCondition[T] { +func NewUnsafeCondition[T Model](condition string, values []any) UnsafeCondition[T] { return UnsafeCondition[T]{ SQLCondition: condition, Values: values, @@ -509,14 +509,14 @@ func NewInvalidCondition[T any](err error) InvalidCondition[T] { // Logical Operators // ref: https://www.postgresql.org/docs/current/functions-logical.html -func And[T any](conditions ...WhereCondition[T]) WhereCondition[T] { +func And[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { return NewConnectionCondition("AND", conditions...) } -func Or[T any](conditions ...WhereCondition[T]) WhereCondition[T] { +func Or[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { return NewConnectionCondition("OR", conditions...) } -func Not[T any](conditions ...WhereCondition[T]) WhereCondition[T] { +func Not[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { return NewContainerCondition("NOT", conditions...) } diff --git a/orm/crudRepository.go b/orm/crudRepository.go index 912efdae..b12013cb 100644 --- a/orm/crudRepository.go +++ b/orm/crudRepository.go @@ -10,7 +10,7 @@ import ( // Generic CRUD Repository // T can be any model whose identifier attribute is of type ID -type CRUDRepository[T any, ID BadaasID] interface { +type CRUDRepository[T Model, ID ModelID] interface { // Create model "model" inside transaction "tx" Create(tx *gorm.DB, entity *T) error @@ -38,12 +38,12 @@ var ( ) // Implementation of the Generic CRUD Repository -type CRUDRepositoryImpl[T any, ID BadaasID] struct { +type CRUDRepositoryImpl[T Model, ID ModelID] struct { CRUDRepository[T, ID] } // Constructor of the Generic CRUD Repository -func NewCRUDRepository[T any, ID BadaasID]() CRUDRepository[T, ID] { +func NewCRUDRepository[T Model, ID ModelID]() CRUDRepository[T, ID] { return &CRUDRepositoryImpl[T, ID]{} } diff --git a/orm/crudService.go b/orm/crudService.go index eeb409bc..579cc44f 100644 --- a/orm/crudService.go +++ b/orm/crudService.go @@ -5,7 +5,7 @@ import ( ) // T can be any model whose identifier attribute is of type ID -type CRUDService[T any, ID BadaasID] interface { +type CRUDService[T Model, ID ModelID] interface { // Get the model of type T that has the "id" GetByID(id ID) (*T, error) @@ -21,13 +21,13 @@ type CRUDService[T any, ID BadaasID] interface { var _ CRUDService[UUIDModel, UUID] = (*crudServiceImpl[UUIDModel, UUID])(nil) // Implementation of the CRUD Service -type crudServiceImpl[T any, ID BadaasID] struct { +type crudServiceImpl[T Model, ID ModelID] struct { CRUDService[T, ID] db *gorm.DB repository CRUDRepository[T, ID] } -func NewCRUDService[T any, ID BadaasID]( +func NewCRUDService[T Model, ID ModelID]( db *gorm.DB, repository CRUDRepository[T, ID], ) CRUDService[T, ID] { diff --git a/orm/orm.go b/orm/orm.go index 0f525279..638330ef 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -5,7 +5,7 @@ import ( "gorm.io/gorm" ) -func GetCRUD[T any, ID BadaasID](db *gorm.DB) (CRUDService[T, ID], CRUDRepository[T, ID]) { +func GetCRUD[T Model, ID ModelID](db *gorm.DB) (CRUDService[T, ID], CRUDRepository[T, ID]) { repository := NewCRUDRepository[T, ID]() return NewCRUDService(db, repository), repository } diff --git a/orm/preload.go b/orm/preload.go new file mode 100644 index 00000000..7600ae6d --- /dev/null +++ b/orm/preload.go @@ -0,0 +1,33 @@ +package orm + +import "errors" + +var ErrRelationNotLoaded = errors.New("relation not loaded") + +func VerifyStructLoaded[T Model](toVerify *T) (*T, error) { + if toVerify == nil || !(*toVerify).IsLoaded() { + return nil, ErrRelationNotLoaded + } + + return toVerify, nil +} + +func VerifyPointerLoaded[TModel Model, TID ModelID](id *TID, toVerify *TModel) (*TModel, error) { + // when the pointer to the object is nil + // but the id pointer indicates that the relation is not nil + if id != nil && toVerify == nil { + return nil, ErrRelationNotLoaded + } + + return toVerify, nil +} + +func VerifyPointerWithIDLoaded[TModel Model, TID ModelID](id TID, toVerify *TModel) (*TModel, error) { + // when the pointer to the object is nil + // but the id indicates that the relation is not nil + if !id.IsNil() && toVerify == nil { + return nil, ErrRelationNotLoaded + } + + return toVerify, nil +} diff --git a/orm/uuid.go b/orm/uuid.go index 6b6ca817..54b0c5c1 100644 --- a/orm/uuid.go +++ b/orm/uuid.go @@ -54,8 +54,12 @@ func (id *UUID) Scan(src interface{}) error { return (*uuid.UUID)(id).Scan(src) } +func (id UUID) IsNil() bool { + return id == NilUUID +} + func (id UUID) GormValue(_ context.Context, _ *gorm.DB) clause.Expr { - if len(id) == 0 { + if id == NilUUID { return gorm.Expr("NULL") } diff --git a/testintegration/asserts.go b/testintegration/asserts.go index 56c8677f..7f717628 100644 --- a/testintegration/asserts.go +++ b/testintegration/asserts.go @@ -23,11 +23,11 @@ func EqualList[T any](ts *suite.Suite, expectedList, actualList []T) { } } if j == expectedLen { - ts.Fail("Lists not equal", "element %v not in list %v", expectedList[i], actualList) - for _, element := range actualList { log.Println(element) } + + ts.FailNow("Lists not equal", "element %v not in list %v", expectedList[i], actualList) } } } diff --git a/testintegration/conditions/brand_conditions.go b/testintegration/conditions/brand_conditions.go index a2e42afa..38238144 100644 --- a/testintegration/conditions/brand_conditions.go +++ b/testintegration/conditions/brand_conditions.go @@ -7,8 +7,8 @@ import ( "time" ) -func BrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Brand] { - return orm.FieldCondition[models.Brand, uint]{ +func BrandId(operator orm.Operator[orm.UIntID]) orm.WhereCondition[models.Brand] { + return orm.FieldCondition[models.Brand, orm.UIntID]{ FieldIdentifier: orm.IDFieldID, Operator: operator, } diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go index 4f0c5da9..93dac6ab 100644 --- a/testintegration/conditions/phone_conditions.go +++ b/testintegration/conditions/phone_conditions.go @@ -7,8 +7,8 @@ import ( "time" ) -func PhoneId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { - return orm.FieldCondition[models.Phone, uint]{ +func PhoneId(operator orm.Operator[orm.UIntID]) orm.WhereCondition[models.Phone] { + return orm.FieldCondition[models.Phone, orm.UIntID]{ FieldIdentifier: orm.IDFieldID, Operator: operator, } diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index 482706e4..b7833399 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -16,7 +16,7 @@ type JoinConditionsIntTestSuite struct { crudCityService orm.CRUDService[models.City, orm.UUID] crudEmployeeService orm.CRUDService[models.Employee, orm.UUID] crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID] - crudPhoneService orm.CRUDService[models.Phone, uint] + crudPhoneService orm.CRUDService[models.Phone, orm.UIntID] } func NewJoinConditionsIntTestSuite( @@ -27,7 +27,7 @@ func NewJoinConditionsIntTestSuite( crudCityService orm.CRUDService[models.City, orm.UUID], crudEmployeeService orm.CRUDService[models.Employee, orm.UUID], crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID], - crudPhoneService orm.CRUDService[models.Phone, uint], + crudPhoneService orm.CRUDService[models.Phone, orm.UIntID], ) *JoinConditionsIntTestSuite { return &JoinConditionsIntTestSuite{ CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ @@ -354,7 +354,7 @@ func (ts *JoinConditionsIntTestSuite) TestConditionThatJoinsMultipleTimes() { EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestJoinWithUnsafeCondition() { +func (ts *JoinConditionsIntTestSuite) TestJoinWithUnsafeCondition() { product1 := ts.createProduct("", 0, 0.0, false, nil) product2 := ts.createProduct("", 0, 0.0, false, nil) @@ -379,7 +379,7 @@ func (ts *WhereConditionsIntTestSuite) TestJoinWithUnsafeCondition() { EqualList(&ts.Suite, []*models.Sale{match}, entities) } -func (ts *WhereConditionsIntTestSuite) TestJoinWithEmptyConnectionConditionMakesNothing() { +func (ts *JoinConditionsIntTestSuite) TestJoinWithEmptyConnectionConditionMakesNothing() { product1 := ts.createProduct("", 1, 0.0, false, nil) product2 := ts.createProduct("", 2, 0.0, false, nil) @@ -396,7 +396,7 @@ func (ts *WhereConditionsIntTestSuite) TestJoinWithEmptyConnectionConditionMakes EqualList(&ts.Suite, []*models.Sale{match1, match2}, entities) } -func (ts *WhereConditionsIntTestSuite) TestJoinWithEmptyContainerConditionReturnsError() { +func (ts *JoinConditionsIntTestSuite) TestJoinWithEmptyContainerConditionReturnsError() { _, err := ts.crudSaleService.Query( conditions.SaleProduct( orm.Not[models.Product](), diff --git a/testintegration/models/badaas-orm.go b/testintegration/models/badaas-orm.go new file mode 100644 index 00000000..052ee6fc --- /dev/null +++ b/testintegration/models/badaas-orm.go @@ -0,0 +1,41 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package models + +import orm "github.com/ditrit/badaas/orm" + +func (m Bicycle) GetOwner() (*Person, error) { + return orm.VerifyStructLoaded[Person](&m.Owner) +} +func (m Child) GetParent1() (*Parent1, error) { + return orm.VerifyStructLoaded[Parent1](&m.Parent1) +} +func (m Child) GetParent2() (*Parent2, error) { + return orm.VerifyStructLoaded[Parent2](&m.Parent2) +} +func (m City) GetCountry() (*Country, error) { + return orm.VerifyPointerWithIDLoaded[Country](m.CountryID, m.Country) +} +func (m Country) GetCapital() (*City, error) { + return orm.VerifyStructLoaded[City](&m.Capital) +} +func (m Employee) GetBoss() (*Employee, error) { + return orm.VerifyPointerLoaded[Employee](m.BossID, m.Boss) +} +func (m Parent1) GetParentParent() (*ParentParent, error) { + return orm.VerifyStructLoaded[ParentParent](&m.ParentParent) +} +func (m Parent2) GetParentParent() (*ParentParent, error) { + return orm.VerifyStructLoaded[ParentParent](&m.ParentParent) +} +func (m Phone) GetBrand() (*Brand, error) { + return orm.VerifyStructLoaded[Brand](&m.Brand) +} +func (m Sale) GetProduct() (*Product, error) { + return orm.VerifyStructLoaded[Product](&m.Product) +} +func (m Sale) GetSeller() (*Seller, error) { + return orm.VerifyPointerLoaded[Seller](m.SellerID, m.Seller) +} +func (m Seller) GetCompany() (*Company, error) { + return orm.VerifyPointerLoaded[Company](m.CompanyID, m.Company) +} diff --git a/testintegration/models/models.go b/testintegration/models/models.go index b2084129..2a06c396 100644 --- a/testintegration/models/models.go +++ b/testintegration/models/models.go @@ -105,7 +105,7 @@ type Sale struct { Product Product ProductID orm.UUID - // Sale HasOne Seller (Sale 0..* -> 0..1 Seller) + // Sale belongsTo Seller (Sale 0..* -> 0..1 Seller) Seller *Seller SellerID *orm.UUID } diff --git a/testintegration/preload_conditions_test.go b/testintegration/preload_conditions_test.go index dd97dc59..e56d4921 100644 --- a/testintegration/preload_conditions_test.go +++ b/testintegration/preload_conditions_test.go @@ -1,6 +1,8 @@ package testintegration import ( + "errors" + "github.com/elliotchance/pie/v2" "gorm.io/gorm" "gotest.tools/assert" @@ -17,7 +19,7 @@ type PreloadConditionsIntTestSuite struct { crudCountryService orm.CRUDService[models.Country, orm.UUID] crudCityService orm.CRUDService[models.City, orm.UUID] crudEmployeeService orm.CRUDService[models.Employee, orm.UUID] - crudPhoneService orm.CRUDService[models.Phone, uint] + crudPhoneService orm.CRUDService[models.Phone, orm.UIntID] crudChildService orm.CRUDService[models.Child, orm.UUID] } @@ -28,7 +30,7 @@ func NewPreloadConditionsIntTestSuite( crudCountryService orm.CRUDService[models.Country, orm.UUID], crudCityService orm.CRUDService[models.City, orm.UUID], crudEmployeeService orm.CRUDService[models.Employee, orm.UUID], - crudPhoneService orm.CRUDService[models.Phone, uint], + crudPhoneService orm.CRUDService[models.Phone, orm.UIntID], crudChildService orm.CRUDService[models.Child, orm.UUID], ) *PreloadConditionsIntTestSuite { return &PreloadConditionsIntTestSuite{ @@ -45,6 +47,48 @@ func NewPreloadConditionsIntTestSuite( } } +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadReturnsErrorOnGetRelation() { + product := ts.createProduct("a_string", 1, 0.0, false, nil) + seller := ts.createSeller("franco", nil) + sale := ts.createSale(0, product, seller) + + entities, err := ts.crudSaleService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale}, entities) + + saleLoaded := entities[0] + + ts.False(saleLoaded.Product.IsLoaded()) + _, err = saleLoaded.GetProduct() + ts.ErrorIs(err, orm.ErrRelationNotLoaded) + + ts.Nil(saleLoaded.Seller) // is nil but we cant determine why directly (not loaded or really null) + _, err = saleLoaded.GetSeller() // GetSeller give us that information + ts.ErrorIs(err, orm.ErrRelationNotLoaded) +} + +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadWhenItsNullKnowsItsReallyNull() { + product := ts.createProduct("a_string", 1, 0.0, false, nil) + sale := ts.createSale(10, product, nil) + + entities, err := ts.crudSaleService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale}, entities) + + saleLoaded := entities[0] + + ts.False(saleLoaded.Product.IsLoaded()) + _, err = saleLoaded.GetProduct() + ts.ErrorIs(err, orm.ErrRelationNotLoaded) + + ts.Nil(saleLoaded.Seller) // is nil but we cant determine why directly (not loaded or really null) + saleSeller, err := saleLoaded.GetSeller() // GetSeller give us that information + ts.Nil(err) + ts.Nil(saleSeller) +} + func (ts *PreloadConditionsIntTestSuite) TestPreloadWithoutWhereConditionDoesNotFilter() { product1 := ts.createProduct("a_string", 1, 0.0, false, nil) product2 := ts.createProduct("", 2, 0.0, false, nil) @@ -61,13 +105,170 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadWithoutWhereConditionDoesNot EqualList(&ts.Suite, []*models.Sale{withSeller, withoutSeller}, entities) ts.True(pie.Any(entities, func(sale *models.Sale) bool { - return sale.Seller.Equal(*seller1) + saleSeller, err := sale.GetSeller() + return err == nil && saleSeller != nil && saleSeller.Equal(*seller1) })) ts.True(pie.Any(entities, func(sale *models.Sale) bool { return sale.Seller == nil })) } +func (ts *PreloadConditionsIntTestSuite) TestPreloadNullableAtSecondLevel() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + company := ts.createCompany("ditrit") + + withCompany := ts.createSeller("with", company) + withoutCompany := ts.createSeller("without", nil) + + sale1 := ts.createSale(0, product1, withoutCompany) + sale2 := ts.createSale(0, product2, withCompany) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerPreloadCompany, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale1, sale2}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + return err == nil && saleSeller.Name == "with" && sellerCompany != nil && sellerCompany.Equal(*company) + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + return err == nil && saleSeller.Name == "without" && sellerCompany == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadAtSecondLevelWorksWithManualPreload() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + company := ts.createCompany("ditrit") + + withCompany := ts.createSeller("with", company) + withoutCompany := ts.createSeller("without", nil) + + sale1 := ts.createSale(0, product1, withoutCompany) + sale2 := ts.createSale(0, product2, withCompany) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerPreloadAttributes, + conditions.SellerPreloadCompany, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale1, sale2}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + return err == nil && saleSeller.Name == "with" && sellerCompany != nil && sellerCompany.Equal(*company) + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + return err == nil && saleSeller.Name == "without" && sellerCompany == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadNullableAtSecondLevel() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + company := ts.createCompany("ditrit") + + withCompany := ts.createSeller("with", company) + withoutCompany := ts.createSeller("without", nil) + + sale1 := ts.createSale(0, product1, withoutCompany) + sale2 := ts.createSale(0, product2, withCompany) + + entities, err := ts.crudSaleService.Query( + conditions.SalePreloadSeller, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{sale1, sale2}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + // the not null one is not loaded + sellerCompany, err := saleSeller.GetCompany() + return errors.Is(err, orm.ErrRelationNotLoaded) && sellerCompany == nil + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if err != nil { + return false + } + + // we can be sure the null one is null + sellerCompany, err := saleSeller.GetCompany() + return err == nil && sellerCompany == nil + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadWithoutWhereConditionDoesNotFilterAtSecondLevel() { + product1 := ts.createProduct("a_string", 1, 0.0, false, nil) + product2 := ts.createProduct("", 2, 0.0, false, nil) + + seller1 := ts.createSeller("franco", nil) + + withSeller := ts.createSale(0, product1, seller1) + withoutSeller := ts.createSale(0, product2, nil) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerPreloadCompany, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{withSeller, withoutSeller}, entities) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + saleSeller, err := sale.GetSeller() + if saleSeller == nil || err != nil { + return false + } + + sellerCompany, err := saleSeller.GetCompany() + + return err == nil && saleSeller.Equal(*seller1) && sellerCompany == nil + })) + ts.True(pie.Any(entities, func(sale *models.Sale) bool { + // in this case sale.Seller will also be nil + // but we can now it's really null in the db because err is nil + saleSeller, err := sale.GetSeller() + return err == nil && saleSeller == nil + })) +} + func (ts *PreloadConditionsIntTestSuite) TestPreloadUIntModel() { brand1 := ts.createBrand("google") brand2 := ts.createBrand("apple") @@ -82,10 +283,12 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadUIntModel() { EqualList(&ts.Suite, []*models.Phone{phone1, phone2}, entities) ts.True(pie.Any(entities, func(phone *models.Phone) bool { - return phone.Brand.Equal(*brand1) + phoneBrand, err := phone.GetBrand() + return err == nil && phoneBrand.Equal(*brand1) })) ts.True(pie.Any(entities, func(phone *models.Phone) bool { - return phone.Brand.Equal(*brand2) + phoneBrand, err := phone.GetBrand() + return err == nil && phoneBrand.Equal(*brand2) })) } @@ -110,10 +313,12 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadWithWhereConditionFilters() ts.Nil(err) EqualList(&ts.Suite, []*models.Sale{match}, entities) - assert.DeepEqual(ts.T(), *product1, entities[0].Product) - ts.Equal("a_string", entities[0].Product.String) - ts.Equal(1, entities[0].Product.EmbeddedInt) - ts.Equal(2, entities[0].Product.GormEmbedded.Int) + saleProduct, err := entities[0].GetProduct() + ts.Nil(err) + assert.DeepEqual(ts.T(), product1, saleProduct) + ts.Equal("a_string", saleProduct.String) + ts.Equal(1, saleProduct.EmbeddedInt) + ts.Equal(2, saleProduct.GormEmbedded.Int) } func (ts *PreloadConditionsIntTestSuite) TestPreloadOneToOne() { @@ -134,32 +339,36 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadOneToOne() { EqualList(&ts.Suite, []*models.City{&capital1, &capital2}, entities) ts.True(pie.Any(entities, func(city *models.City) bool { - return city.Country.Equal(*country1) + cityCountry, err := city.GetCountry() + if err != nil { + return false + } + + return cityCountry.Equal(*country1) })) ts.True(pie.Any(entities, func(city *models.City) bool { - return city.Country.Equal(*country2) + cityCountry, err := city.GetCountry() + if err != nil { + return false + } + + return cityCountry.Equal(*country2) })) } -func (ts *PreloadConditionsIntTestSuite) TestPreloadHasMany() { - company1 := ts.createCompany("ditrit") - company2 := ts.createCompany("orness") +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadOneToOne() { + capital1 := models.City{ + Name: "Buenos Aires", + } - seller1 := ts.createSeller("franco", company1) - seller2 := ts.createSeller("agustin", company2) + ts.createCountry("Argentina", capital1) - entities, err := ts.crudSellerService.Query( - conditions.SellerPreloadCompany, - ) + entities, err := ts.crudCityService.Query() ts.Nil(err) - EqualList(&ts.Suite, []*models.Seller{seller1, seller2}, entities) - ts.True(pie.Any(entities, func(seller *models.Seller) bool { - return seller.Company.Equal(*company1) - })) - ts.True(pie.Any(entities, func(seller *models.Seller) bool { - return seller.Company.Equal(*company2) - })) + EqualList(&ts.Suite, []*models.City{&capital1}, entities) + _, err = entities[0].GetCountry() + ts.ErrorIs(err, orm.ErrRelationNotLoaded) } func (ts *PreloadConditionsIntTestSuite) TestPreloadOneToOneReversed() { @@ -180,10 +389,35 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadOneToOneReversed() { EqualList(&ts.Suite, []*models.Country{country1, country2}, entities) ts.True(pie.Any(entities, func(country *models.Country) bool { - return country.Capital.Equal(capital1) + countryCapital, err := country.GetCapital() + return err == nil && countryCapital.Equal(capital1) })) ts.True(pie.Any(entities, func(country *models.Country) bool { - return country.Capital.Equal(capital2) + countryCapital, err := country.GetCapital() + return err == nil && countryCapital.Equal(capital2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadHasManyReversed() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("franco", company1) + seller2 := ts.createSeller("agustin", company2) + + entities, err := ts.crudSellerService.Query( + conditions.SellerPreloadCompany, + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Seller{seller1, seller2}, entities) + ts.True(pie.Any(entities, func(seller *models.Seller) bool { + sellerCompany, err := seller.GetCompany() + return err == nil && sellerCompany.Equal(*company1) + })) + ts.True(pie.Any(entities, func(seller *models.Seller) bool { + sellerCompany, err := seller.GetCompany() + return err == nil && sellerCompany.Equal(*company2) })) } @@ -203,10 +437,12 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadSelfReferential() { EqualList(&ts.Suite, []*models.Employee{boss1, employee1, employee2}, entities) ts.True(pie.Any(entities, func(employee *models.Employee) bool { - return employee.Boss != nil && employee.Boss.Equal(*boss1) + employeeBoss, err := employee.GetBoss() + return err == nil && employeeBoss != nil && employeeBoss.Equal(*boss1) })) ts.True(pie.Any(entities, func(employee *models.Employee) bool { - return employee.Boss == nil + employeeBoss, err := employee.GetBoss() + return err == nil && employeeBoss == nil })) } @@ -232,10 +468,12 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadSelfReferentialAtSecondLevel EqualList(&ts.Suite, []*models.Employee{employee}, entities) - bossLoaded := entities[0].Boss + bossLoaded, err := entities[0].GetBoss() + ts.Nil(err) ts.True(bossLoaded.Equal(*boss)) - bossBossLoaded := bossLoaded.Boss + bossBossLoaded, err := bossLoaded.GetBoss() + ts.Nil(err) ts.True(bossBossLoaded.Equal(*bossBoss)) } @@ -264,8 +502,13 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadDifferentEntitiesWithConditi ts.Nil(err) EqualList(&ts.Suite, []*models.Sale{match}, entities) - assert.DeepEqual(ts.T(), *product1, entities[0].Product) - assert.DeepEqual(ts.T(), seller1, entities[0].Seller) + saleProduct, err := entities[0].GetProduct() + ts.Nil(err) + assert.DeepEqual(ts.T(), product1, saleProduct) + + saleSeller, err := entities[0].GetSeller() + ts.Nil(err) + assert.DeepEqual(ts.T(), seller1, saleSeller) } func (ts *PreloadConditionsIntTestSuite) TestPreloadDifferentEntitiesWithoutConditions() { @@ -292,8 +535,13 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadDifferentEntitiesWithoutCond ts.Nil(err) EqualList(&ts.Suite, []*models.Child{child}, entities) - assert.DeepEqual(ts.T(), *parent1, entities[0].Parent1) - assert.DeepEqual(ts.T(), *parent2, entities[0].Parent2) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent1, childParent1) + + childParent2, err := entities[0].GetParent2() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent2, childParent2) } func (ts *PreloadConditionsIntTestSuite) TestPreloadRelations() { @@ -319,8 +567,13 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadRelations() { ts.Nil(err) EqualList(&ts.Suite, []*models.Child{child}, entities) - assert.DeepEqual(ts.T(), *parent1, entities[0].Parent1) - assert.DeepEqual(ts.T(), *parent2, entities[0].Parent2) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent1, childParent1) + + childParent2, err := entities[0].GetParent2() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent2, childParent2) } func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadWithoutCondition() { @@ -349,8 +602,13 @@ func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadWithoutC ts.Nil(err) EqualList(&ts.Suite, []*models.Child{child}, entities) - assert.DeepEqual(ts.T(), *parent1, entities[0].Parent1) - assert.DeepEqual(ts.T(), *parentParent, entities[0].Parent1.ParentParent) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent1, childParent1) + + childParentParent, err := childParent1.GetParentParent() + ts.Nil(err) + assert.DeepEqual(ts.T(), parentParent, childParentParent) } func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadWithCondition() { @@ -400,8 +658,13 @@ func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadWithCond ts.Nil(err) EqualList(&ts.Suite, []*models.Child{child1}, entities) - assert.DeepEqual(ts.T(), *parent11, entities[0].Parent1) - assert.DeepEqual(ts.T(), *parentParent1, entities[0].Parent1.ParentParent) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent11, childParent1) + + childParentParent, err := childParent1.GetParentParent() + ts.Nil(err) + assert.DeepEqual(ts.T(), parentParent1, childParentParent) } func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadDiamond() { @@ -423,19 +686,28 @@ func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadDiamond( entities, err := ts.crudChildService.Query( conditions.ChildParent1( - conditions.Parent1PreloadAttributes, conditions.Parent1PreloadParentParent, ), conditions.ChildParent2( - conditions.Parent2PreloadAttributes, conditions.Parent2PreloadParentParent, ), ) ts.Nil(err) EqualList(&ts.Suite, []*models.Child{child}, entities) - assert.DeepEqual(ts.T(), *parent1, entities[0].Parent1) - assert.DeepEqual(ts.T(), *parent2, entities[0].Parent2) - assert.DeepEqual(ts.T(), *parentParent, entities[0].Parent1.ParentParent) - assert.DeepEqual(ts.T(), *parentParent, entities[0].Parent2.ParentParent) + childParent1, err := entities[0].GetParent1() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent1, childParent1) + + childParent2, err := entities[0].GetParent2() + ts.Nil(err) + assert.DeepEqual(ts.T(), parent2, childParent2) + + childParent1Parent, err := childParent1.GetParentParent() + ts.Nil(err) + assert.DeepEqual(ts.T(), parentParent, childParent1Parent) + + childParent2Parent, err := childParent2.GetParentParent() + ts.Nil(err) + assert.DeepEqual(ts.T(), parentParent, childParent2Parent) } diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index ac6ca873..0ec19abd 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -13,14 +13,14 @@ type WhereConditionsIntTestSuite struct { CRUDServiceCommonIntTestSuite crudProductService orm.CRUDService[models.Product, orm.UUID] crudSaleService orm.CRUDService[models.Sale, orm.UUID] - crudBrandService orm.CRUDService[models.Brand, uint] + crudBrandService orm.CRUDService[models.Brand, orm.UIntID] } func NewWhereConditionsIntTestSuite( db *gorm.DB, crudProductService orm.CRUDService[models.Product, orm.UUID], crudSaleService orm.CRUDService[models.Sale, orm.UUID], - crudBrandService orm.CRUDService[models.Brand, uint], + crudBrandService orm.CRUDService[models.Brand, orm.UIntID], ) *WhereConditionsIntTestSuite { return &WhereConditionsIntTestSuite{ CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ From f95f61fd968e666ee945eab263117d8e4ccc0951 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 28 Jun 2023 12:17:05 +0200 Subject: [PATCH 67/90] list and nested attributes preload --- orm/condition.go | 71 ++++++- orm/crudRepository.go | 35 ++-- orm/preload.go | 8 + .../conditions/company_conditions.go | 4 + .../conditions/seller_conditions.go | 23 ++- .../conditions/university_conditions.go | 45 +++++ testintegration/crudServiceCommon.go | 10 + testintegration/models/badaas-orm.go | 6 + testintegration/models/models.go | 15 +- testintegration/orm_test.go | 1 + testintegration/preload_conditions_test.go | 174 ++++++++++++++++++ utils/slice.go | 15 ++ utils/slice_test.go | 59 ++++++ 13 files changed, 450 insertions(+), 16 deletions(-) create mode 100644 testintegration/conditions/university_conditions.go create mode 100644 utils/slice.go create mode 100644 utils/slice_test.go diff --git a/orm/condition.go b/orm/condition.go index a763b926..e755fac8 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -18,7 +18,10 @@ var ( DeletedAtFieldID = FieldIdentifier{Field: deletedAtField} ) -var ErrEmptyConditions = errors.New("condition must have at least one inner condition") +var ( + ErrEmptyConditions = errors.New("condition must have at least one inner condition") + ErrOnlyPreloadsAllowed = errors.New("only conditions that do a preload are allowed") +) type Table struct { Name string @@ -234,6 +237,58 @@ func NewPreloadCondition[T Model](fields ...FieldIdentifier) PreloadCondition[T] } } +// Condition used to the preload a collection of models of a model +type CollectionPreloadCondition[T1 Model, T2 Model] struct { + CollectionField string + NestedPreloads []IJoinCondition[T2] +} + +//nolint:unused // see inside +func (condition CollectionPreloadCondition[T1, T2]) interfaceVerificationMethod(_ T1) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T1] +} + +func (condition CollectionPreloadCondition[T1, T2]) ApplyTo(query *gorm.DB, _ Table) (*gorm.DB, error) { + if len(condition.NestedPreloads) == 0 { + return query.Preload(condition.CollectionField), nil + } + + return query.Preload( + condition.CollectionField, + func(db *gorm.DB) *gorm.DB { + preloadsAsCondition := pie.Map( + condition.NestedPreloads, + func(joinCondition IJoinCondition[T2]) Condition[T2] { + return joinCondition + }, + ) + + query, err := applyConditionsToQuery[T2](db, preloadsAsCondition) + if err != nil { + _ = db.AddError(err) + return db + } + + return query + }, + ), nil +} + +// Condition used to the preload a collection of models of a model +func NewCollectionPreloadCondition[T1 Model, T2 Model](collectionField string, nestedPreloads []IJoinCondition[T2]) Condition[T1] { + if pie.Any(nestedPreloads, func(nestedPreload IJoinCondition[T2]) bool { + return !nestedPreload.makesPreload() || nestedPreload.makesFilter() + }) { + return NewInvalidCondition[T1](ErrOnlyPreloadsAllowed) + } + + return CollectionPreloadCondition[T1, T2]{ + CollectionField: collectionField, + NestedPreloads: nestedPreloads, + } +} + // Condition that verifies the value of a field, // using the Operator type FieldCondition[TObject Model, TAtribute any] struct { @@ -285,6 +340,9 @@ type IJoinCondition[T Model] interface { // Returns true if this condition or any nested condition makes a preload makesPreload() bool + + // Returns true if the condition of nay nested condition applies a filter (has where conditions) + makesFilter() bool } // Condition that joins with other table @@ -311,6 +369,17 @@ func (condition JoinCondition[T1, T2]) makesPreload() bool { }) } +// Returns true if the condition of nay nested condition applies a filter (has where conditions) +// +//nolint:unused // is used +func (condition JoinCondition[T1, T2]) makesFilter() bool { + whereConditions, joinConditions, _ := divideConditionsByType(condition.Conditions) + + return len(whereConditions) != 0 || pie.Any(joinConditions, func(cond IJoinCondition[T2]) bool { + return cond.makesFilter() + }) +} + // Applies a join between the tables of T1 and T2 // previousTableName is the name of the table of T1 // It also applies the nested conditions diff --git a/orm/crudRepository.go b/orm/crudRepository.go index b12013cb..308cba80 100644 --- a/orm/crudRepository.go +++ b/orm/crudRepository.go @@ -94,28 +94,39 @@ func (repository *CRUDRepositoryImpl[T, ID]) QueryOne(tx *gorm.DB, conditions .. // Get the list of models that match "conditions" inside transaction "tx" func (repository *CRUDRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) { - initialTableName, err := getTableName(tx, *new(T)) + query, err := applyConditionsToQuery(tx, conditions) if err != nil { return nil, err } - query := tx.Select(initialTableName + ".*") + // execute query + var entities []*T + err = query.Find(&entities).Error + + return entities, err +} + +func applyConditionsToQuery[T Model](query *gorm.DB, conditions []Condition[T]) (*gorm.DB, error) { + initialTableName, err := getTableName(query, *new(T)) + if err != nil { + return nil, err + } + + initialTable := Table{ + Name: initialTableName, + Alias: initialTableName, + Initial: true, + } + + query = query.Select(initialTableName + ".*") for _, condition := range conditions { - query, err = condition.ApplyTo(query, Table{ - Name: initialTableName, - Alias: initialTableName, - Initial: true, - }) + query, err = condition.ApplyTo(query, initialTable) if err != nil { return nil, err } } - // execute query - var entities []*T - err = query.Find(&entities).Error - - return entities, err + return query, nil } // Get the name of the table in "db" in which the data for "entity" is saved diff --git a/orm/preload.go b/orm/preload.go index 7600ae6d..7e0ad5aa 100644 --- a/orm/preload.go +++ b/orm/preload.go @@ -31,3 +31,11 @@ func VerifyPointerWithIDLoaded[TModel Model, TID ModelID](id TID, toVerify *TMod return toVerify, nil } + +func VerifyCollectionLoaded[T Model](collection *[]T) ([]T, error) { + if collection == nil { + return nil, ErrRelationNotLoaded + } + + return *collection, nil +} diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go index 408ec915..9a2a02d9 100644 --- a/testintegration/conditions/company_conditions.go +++ b/testintegration/conditions/company_conditions.go @@ -40,5 +40,9 @@ func CompanyName(operator orm.Operator[string]) orm.WhereCondition[models.Compan Operator: operator, } } +func CompanyPreloadSellers(nestedPreloads ...orm.IJoinCondition[models.Seller]) orm.Condition[models.Company] { + return orm.NewCollectionPreloadCondition[models.Company, models.Seller]("Sellers", nestedPreloads) +} var CompanyPreloadAttributes = orm.NewPreloadCondition[models.Company](companyNameFieldID) +var CompanyPreloadRelations = []orm.Condition[models.Company]{CompanyPreloadSellers()} diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go index f321579b..c206432c 100644 --- a/testintegration/conditions/seller_conditions.go +++ b/testintegration/conditions/seller_conditions.go @@ -59,6 +59,25 @@ func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models. Operator: operator, } } +func SellerUniversity(conditions ...orm.Condition[models.University]) orm.IJoinCondition[models.Seller] { + return orm.JoinCondition[models.Seller, models.University]{ + Conditions: conditions, + RelationField: "University", + T1Field: "UniversityID", + T1PreloadCondition: SellerPreloadAttributes, + T2Field: "ID", + } +} + +var SellerPreloadUniversity = SellerUniversity(UniversityPreloadAttributes) +var sellerUniversityIdFieldID = orm.FieldIdentifier{Field: "UniversityID"} + +func SellerUniversityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { + return orm.FieldCondition[models.Seller, orm.UUID]{ + FieldIdentifier: sellerUniversityIdFieldID, + Operator: operator, + } +} -var SellerPreloadAttributes = orm.NewPreloadCondition[models.Seller](sellerNameFieldID, sellerCompanyIdFieldID) -var SellerPreloadRelations = []orm.Condition[models.Seller]{SellerPreloadCompany} +var SellerPreloadAttributes = orm.NewPreloadCondition[models.Seller](sellerNameFieldID, sellerCompanyIdFieldID, sellerUniversityIdFieldID) +var SellerPreloadRelations = []orm.Condition[models.Seller]{SellerPreloadCompany, SellerPreloadUniversity} diff --git a/testintegration/conditions/university_conditions.go b/testintegration/conditions/university_conditions.go new file mode 100644 index 00000000..b670940c --- /dev/null +++ b/testintegration/conditions/university_conditions.go @@ -0,0 +1,45 @@ +// Code generated by badaas-cli v0.0.0, DO NOT EDIT. +package conditions + +import ( + orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/testintegration/models" + gorm "gorm.io/gorm" + "time" +) + +func UniversityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.University] { + return orm.FieldCondition[models.University, orm.UUID]{ + FieldIdentifier: orm.IDFieldID, + Operator: operator, + } +} +func UniversityCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.University] { + return orm.FieldCondition[models.University, time.Time]{ + FieldIdentifier: orm.CreatedAtFieldID, + Operator: operator, + } +} +func UniversityUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.University] { + return orm.FieldCondition[models.University, time.Time]{ + FieldIdentifier: orm.UpdatedAtFieldID, + Operator: operator, + } +} +func UniversityDeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.University] { + return orm.FieldCondition[models.University, gorm.DeletedAt]{ + FieldIdentifier: orm.DeletedAtFieldID, + Operator: operator, + } +} + +var universityNameFieldID = orm.FieldIdentifier{Field: "Name"} + +func UniversityName(operator orm.Operator[string]) orm.WhereCondition[models.University] { + return orm.FieldCondition[models.University, string]{ + FieldIdentifier: universityNameFieldID, + Operator: operator, + } +} + +var UniversityPreloadAttributes = orm.NewPreloadCondition[models.University](universityNameFieldID) diff --git a/testintegration/crudServiceCommon.go b/testintegration/crudServiceCommon.go index 4769ac3b..1fd63488 100644 --- a/testintegration/crudServiceCommon.go +++ b/testintegration/crudServiceCommon.go @@ -125,3 +125,13 @@ func (ts *CRUDServiceCommonIntTestSuite) createPhone(name string, brand models.B return entity } + +func (ts *CRUDServiceCommonIntTestSuite) createUniversity(name string) *models.University { + entity := &models.University{ + Name: name, + } + err := ts.db.Create(entity).Error + ts.Nil(err) + + return entity +} diff --git a/testintegration/models/badaas-orm.go b/testintegration/models/badaas-orm.go index 052ee6fc..d71a13c6 100644 --- a/testintegration/models/badaas-orm.go +++ b/testintegration/models/badaas-orm.go @@ -15,6 +15,9 @@ func (m Child) GetParent2() (*Parent2, error) { func (m City) GetCountry() (*Country, error) { return orm.VerifyPointerWithIDLoaded[Country](m.CountryID, m.Country) } +func (m Company) GetSellers() ([]Seller, error) { + return orm.VerifyCollectionLoaded[Seller](m.Sellers) +} func (m Country) GetCapital() (*City, error) { return orm.VerifyStructLoaded[City](&m.Capital) } @@ -39,3 +42,6 @@ func (m Sale) GetSeller() (*Seller, error) { func (m Seller) GetCompany() (*Company, error) { return orm.VerifyPointerLoaded[Company](m.CompanyID, m.Company) } +func (m Seller) GetUniversity() (*University, error) { + return orm.VerifyPointerLoaded[University](m.UniversityID, m.University) +} diff --git a/testintegration/models/models.go b/testintegration/models/models.go index 2a06c396..8b2f91fe 100644 --- a/testintegration/models/models.go +++ b/testintegration/models/models.go @@ -25,7 +25,7 @@ type Company struct { orm.UUIDModel Name string - Sellers []Seller // Company HasMany Sellers (Company 0..1 -> 0..* Seller) + Sellers *[]Seller // Company HasMany Sellers (Company 0..1 -> 0..* Seller) } func (m Company) Equal(other Company) bool { @@ -87,12 +87,25 @@ func (m Product) Equal(other Product) bool { return m.ID == other.ID } +type University struct { + orm.UUIDModel + + Name string +} + +func (m University) Equal(other University) bool { + return m.ID == other.ID +} + type Seller struct { orm.UUIDModel Name string Company *Company CompanyID *orm.UUID // Company HasMany Sellers (Company 0..1 -> 0..* Seller) + + University *University + UniversityID *orm.UUID } type Sale struct { diff --git a/testintegration/orm_test.go b/testintegration/orm_test.go index f8447cd5..1a11c6ec 100644 --- a/testintegration/orm_test.go +++ b/testintegration/orm_test.go @@ -50,6 +50,7 @@ func TestBaDaaSORM(t *testing.T) { // create crud services for models orm.GetCRUDServiceModule[models.Seller](), + orm.GetCRUDServiceModule[models.Company](), orm.GetCRUDServiceModule[models.Product](), orm.GetCRUDServiceModule[models.Sale](), orm.GetCRUDServiceModule[models.City](), diff --git a/testintegration/preload_conditions_test.go b/testintegration/preload_conditions_test.go index e56d4921..791d2172 100644 --- a/testintegration/preload_conditions_test.go +++ b/testintegration/preload_conditions_test.go @@ -10,11 +10,13 @@ import ( "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" + "github.com/ditrit/badaas/utils" ) type PreloadConditionsIntTestSuite struct { CRUDServiceCommonIntTestSuite crudSaleService orm.CRUDService[models.Sale, orm.UUID] + crudCompanyService orm.CRUDService[models.Company, orm.UUID] crudSellerService orm.CRUDService[models.Seller, orm.UUID] crudCountryService orm.CRUDService[models.Country, orm.UUID] crudCityService orm.CRUDService[models.City, orm.UUID] @@ -26,6 +28,7 @@ type PreloadConditionsIntTestSuite struct { func NewPreloadConditionsIntTestSuite( db *gorm.DB, crudSaleService orm.CRUDService[models.Sale, orm.UUID], + crudCompanyService orm.CRUDService[models.Company, orm.UUID], crudSellerService orm.CRUDService[models.Seller, orm.UUID], crudCountryService orm.CRUDService[models.Country, orm.UUID], crudCityService orm.CRUDService[models.City, orm.UUID], @@ -38,6 +41,7 @@ func NewPreloadConditionsIntTestSuite( db: db, }, crudSaleService: crudSaleService, + crudCompanyService: crudCompanyService, crudSellerService: crudSellerService, crudCountryService: crudCountryService, crudCityService: crudCityService, @@ -711,3 +715,173 @@ func (ts *PreloadConditionsIntTestSuite) TestJoinMultipleTimesAndPreloadDiamond( ts.Nil(err) assert.DeepEqual(ts.T(), parentParent, childParent2Parent) } + +func (ts *PreloadConditionsIntTestSuite) TestPreloadCollection() { + company := ts.createCompany("ditrit") + seller1 := ts.createSeller("1", company) + seller2 := ts.createSeller("2", company) + + entities, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers(), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company}, entities) + companySellers, err := entities[0].GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{*seller1, *seller2}, companySellers) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadEmptyCollection() { + company := ts.createCompany("ditrit") + + entities, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers(), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company}, entities) + companySellers, err := entities[0].GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{}, companySellers) +} + +func (ts *PreloadConditionsIntTestSuite) TestNoPreloadCollection() { + company := ts.createCompany("ditrit") + + entities, err := ts.crudCompanyService.Query() + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company}, entities) + _, err = entities[0].GetSellers() + ts.ErrorIs(err, orm.ErrRelationNotLoaded) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributes() { + company := ts.createCompany("ditrit") + + university1 := ts.createUniversity("uni1") + seller1 := ts.createSeller("1", company) + seller1.University = university1 + err := ts.db.Save(seller1).Error + ts.Nil(err) + + university2 := ts.createUniversity("uni1") + seller2 := ts.createSeller("2", company) + seller2.University = university2 + err = ts.db.Save(seller2).Error + ts.Nil(err) + + entities, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers( + conditions.SellerPreloadUniversity, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company}, entities) + companySellers, err := entities[0].GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{*seller1, *seller2}, companySellers) + + ts.True(pie.Any(*entities[0].Sellers, func(seller models.Seller) bool { + sellerUniversity, err := seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university1) + })) + ts.True(pie.Any(*entities[0].Sellers, func(seller models.Seller) bool { + sellerUniversity, err := seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadMultipleListsAndNestedAttributes() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + university1 := ts.createUniversity("uni1") + seller1 := ts.createSeller("1", company1) + seller1.University = university1 + err := ts.db.Save(seller1).Error + ts.Nil(err) + + university2 := ts.createUniversity("uni1") + seller2 := ts.createSeller("2", company1) + seller2.University = university2 + err = ts.db.Save(seller2).Error + ts.Nil(err) + + seller3 := ts.createSeller("3", company2) + seller3.University = university1 + err = ts.db.Save(seller3).Error + ts.Nil(err) + + seller4 := ts.createSeller("4", company2) + seller4.University = university2 + err = ts.db.Save(seller4).Error + ts.Nil(err) + + entities, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers( + conditions.SellerPreloadUniversity, + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Company{company1, company2}, entities) + + company1Loaded := *utils.FindFirst(entities, func(company *models.Company) bool { + return company.Equal(*company1) + }) + company2Loaded := *utils.FindFirst(entities, func(company *models.Company) bool { + return company.Equal(*company2) + }) + + company1Sellers, err := company1Loaded.GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{*seller1, *seller2}, company1Sellers) + + var sellerUniversity *models.University + + ts.True(pie.Any(*company1Loaded.Sellers, func(seller models.Seller) bool { + sellerUniversity, err = seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university1) + })) + ts.True(pie.Any(*company1Loaded.Sellers, func(seller models.Seller) bool { + sellerUniversity, err = seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university2) + })) + + company2Sellers, err := company2Loaded.GetSellers() + ts.Nil(err) + EqualList(&ts.Suite, []models.Seller{*seller3, *seller4}, company2Sellers) + + ts.True(pie.Any(*company2Loaded.Sellers, func(seller models.Seller) bool { + sellerUniversity, err := seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university1) + })) + ts.True(pie.Any(*company2Loaded.Sellers, func(seller models.Seller) bool { + sellerUniversity, err := seller.GetUniversity() + return err == nil && sellerUniversity.Equal(*university2) + })) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributesWithFiltersReturnsError() { + _, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers( + conditions.SellerUniversity( + conditions.UniversityPreloadAttributes, + conditions.UniversityId(orm.Eq(orm.NilUUID)), + ), + ), + ) + ts.ErrorIs(err, orm.ErrOnlyPreloadsAllowed) +} + +func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributesWithoutPreloadReturnsError() { + _, err := ts.crudCompanyService.Query( + conditions.CompanyPreloadSellers( + conditions.SellerUniversity(), + ), + ) + ts.ErrorIs(err, orm.ErrOnlyPreloadsAllowed) +} diff --git a/utils/slice.go b/utils/slice.go new file mode 100644 index 00000000..2b370951 --- /dev/null +++ b/utils/slice.go @@ -0,0 +1,15 @@ +package utils + +import ( + "github.com/elliotchance/pie/v2" +) + +func FindFirst[T any](ss []T, fn func(value T) bool) *T { + index := pie.FindFirstUsing(ss, fn) + + if index == -1 { + return nil + } + + return &ss[index] +} diff --git a/utils/slice_test.go b/utils/slice_test.go new file mode 100644 index 00000000..df05c090 --- /dev/null +++ b/utils/slice_test.go @@ -0,0 +1,59 @@ +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/utils" +) + +var ( + testResult3 = 33.04 + testResult4 = 0.11 +) + +var findFirstTests = []struct { + ss []float64 + expression func(value float64) bool + expected *float64 +}{ + { + nil, + func(value float64) bool { return value == 1.5 }, + nil, + }, + { + []float64{}, + func(value float64) bool { return value == 0.1 }, + nil, + }, + { + []float64{0.0, 1.5, 3.2}, + func(value float64) bool { return value == 9.99 }, + nil, + }, + { + []float64{5.4, 6.98, 4.987, 33.04}, + func(value float64) bool { return value == 33.04 }, + &testResult3, + }, + { + []float64{9.0, 0.11, 150.44, 33.04}, + func(value float64) bool { return value == 0.11 }, + &testResult4, + }, +} + +func TestFindFirst(t *testing.T) { + for _, test := range findFirstTests { + t.Run("", func(t *testing.T) { + result := utils.FindFirst(test.ss, test.expression) + if result == nil { + assert.Nil(t, test.expected) + } else { + assert.Equal(t, *test.expected, *result) + } + }) + } +} From 958f0bea7995264447674f948e4dff4836c14254 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 3 Aug 2023 17:19:02 +0200 Subject: [PATCH 68/90] update mocks --- mocks/configuration/Holder.go | 38 +++++++++++++++ mocks/orm/CRUDRepository.go | 4 +- mocks/orm/CRUDService.go | 4 +- mocks/orm/Condition.go | 23 ++++----- mocks/orm/IJoinCondition.go | 88 +++++++++++++++++++++++++++++++++++ mocks/orm/Model.go | 39 ++++++++++++++++ mocks/orm/ModelID.go | 39 ++++++++++++++++ mocks/orm/WhereCondition.go | 45 +++++++++--------- 8 files changed, 243 insertions(+), 37 deletions(-) create mode 100644 mocks/configuration/Holder.go create mode 100644 mocks/orm/IJoinCondition.go create mode 100644 mocks/orm/Model.go create mode 100644 mocks/orm/ModelID.go diff --git a/mocks/configuration/Holder.go b/mocks/configuration/Holder.go new file mode 100644 index 00000000..4dfc6188 --- /dev/null +++ b/mocks/configuration/Holder.go @@ -0,0 +1,38 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + zap "go.uber.org/zap" +) + +// Holder is an autogenerated mock type for the Holder type +type Holder struct { + mock.Mock +} + +// Log provides a mock function with given fields: logger +func (_m *Holder) Log(logger *zap.Logger) { + _m.Called(logger) +} + +// Reload provides a mock function with given fields: +func (_m *Holder) Reload() { + _m.Called() +} + +type mockConstructorTestingTNewHolder interface { + mock.TestingT + Cleanup(func()) +} + +// NewHolder creates a new instance of Holder. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewHolder(t mockConstructorTestingTNewHolder) *Holder { + mock := &Holder{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/CRUDRepository.go b/mocks/orm/CRUDRepository.go index db93a68f..d9894c12 100644 --- a/mocks/orm/CRUDRepository.go +++ b/mocks/orm/CRUDRepository.go @@ -9,7 +9,7 @@ import ( ) // CRUDRepository is an autogenerated mock type for the CRUDRepository type -type CRUDRepository[T interface{}, ID orm.BadaasID] struct { +type CRUDRepository[T orm.Model, ID orm.ModelID] struct { mock.Mock } @@ -153,7 +153,7 @@ type mockConstructorTestingTNewCRUDRepository interface { } // NewCRUDRepository creates a new instance of CRUDRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCRUDRepository[T interface{}, ID orm.BadaasID](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { +func NewCRUDRepository[T orm.Model, ID orm.ModelID](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { mock := &CRUDRepository[T, ID]{} mock.Mock.Test(t) diff --git a/mocks/orm/CRUDService.go b/mocks/orm/CRUDService.go index 1a047339..21c4d596 100644 --- a/mocks/orm/CRUDService.go +++ b/mocks/orm/CRUDService.go @@ -8,7 +8,7 @@ import ( ) // CRUDService is an autogenerated mock type for the CRUDService type -type CRUDService[T interface{}, ID orm.BadaasID] struct { +type CRUDService[T orm.Model, ID orm.ModelID] struct { mock.Mock } @@ -108,7 +108,7 @@ type mockConstructorTestingTNewCRUDService interface { } // NewCRUDService creates a new instance of CRUDService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCRUDService[T interface{}, ID orm.BadaasID](t mockConstructorTestingTNewCRUDService) *CRUDService[T, ID] { +func NewCRUDService[T orm.Model, ID orm.ModelID](t mockConstructorTestingTNewCRUDService) *CRUDService[T, ID] { mock := &CRUDService[T, ID]{} mock.Mock.Test(t) diff --git a/mocks/orm/Condition.go b/mocks/orm/Condition.go index 2d214a05..d9775761 100644 --- a/mocks/orm/Condition.go +++ b/mocks/orm/Condition.go @@ -3,34 +3,35 @@ package mocks import ( + orm "github.com/ditrit/badaas/orm" mock "github.com/stretchr/testify/mock" gorm "gorm.io/gorm" ) // Condition is an autogenerated mock type for the Condition type -type Condition[T interface{}] struct { +type Condition[T orm.Model] struct { mock.Mock } -// ApplyTo provides a mock function with given fields: query, tableName -func (_m *Condition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { - ret := _m.Called(query, tableName) +// ApplyTo provides a mock function with given fields: query, table +func (_m *Condition[T]) ApplyTo(query *gorm.DB, table orm.Table) (*gorm.DB, error) { + ret := _m.Called(query, table) var r0 *gorm.DB var r1 error - if rf, ok := ret.Get(0).(func(*gorm.DB, string) (*gorm.DB, error)); ok { - return rf(query, tableName) + if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) (*gorm.DB, error)); ok { + return rf(query, table) } - if rf, ok := ret.Get(0).(func(*gorm.DB, string) *gorm.DB); ok { - r0 = rf(query, tableName) + if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) *gorm.DB); ok { + r0 = rf(query, table) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*gorm.DB) } } - if rf, ok := ret.Get(1).(func(*gorm.DB, string) error); ok { - r1 = rf(query, tableName) + if rf, ok := ret.Get(1).(func(*gorm.DB, orm.Table) error); ok { + r1 = rf(query, table) } else { r1 = ret.Error(1) } @@ -49,7 +50,7 @@ type mockConstructorTestingTNewCondition interface { } // NewCondition creates a new instance of Condition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCondition[T interface{}](t mockConstructorTestingTNewCondition) *Condition[T] { +func NewCondition[T orm.Model](t mockConstructorTestingTNewCondition) *Condition[T] { mock := &Condition[T]{} mock.Mock.Test(t) diff --git a/mocks/orm/IJoinCondition.go b/mocks/orm/IJoinCondition.go new file mode 100644 index 00000000..c1c4411f --- /dev/null +++ b/mocks/orm/IJoinCondition.go @@ -0,0 +1,88 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + orm "github.com/ditrit/badaas/orm" + mock "github.com/stretchr/testify/mock" + gorm "gorm.io/gorm" +) + +// IJoinCondition is an autogenerated mock type for the IJoinCondition type +type IJoinCondition[T orm.Model] struct { + mock.Mock +} + +// ApplyTo provides a mock function with given fields: query, table +func (_m *IJoinCondition[T]) ApplyTo(query *gorm.DB, table orm.Table) (*gorm.DB, error) { + ret := _m.Called(query, table) + + var r0 *gorm.DB + var r1 error + if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) (*gorm.DB, error)); ok { + return rf(query, table) + } + if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) *gorm.DB); ok { + r0 = rf(query, table) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gorm.DB) + } + } + + if rf, ok := ret.Get(1).(func(*gorm.DB, orm.Table) error); ok { + r1 = rf(query, table) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// interfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *IJoinCondition[T]) interfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +// makesFilter provides a mock function with given fields: +func (_m *IJoinCondition[T]) makesFilter() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// makesPreload provides a mock function with given fields: +func (_m *IJoinCondition[T]) makesPreload() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +type mockConstructorTestingTNewIJoinCondition interface { + mock.TestingT + Cleanup(func()) +} + +// NewIJoinCondition creates a new instance of IJoinCondition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewIJoinCondition[T orm.Model](t mockConstructorTestingTNewIJoinCondition) *IJoinCondition[T] { + mock := &IJoinCondition[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/Model.go b/mocks/orm/Model.go new file mode 100644 index 00000000..ca967b67 --- /dev/null +++ b/mocks/orm/Model.go @@ -0,0 +1,39 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// Model is an autogenerated mock type for the Model type +type Model struct { + mock.Mock +} + +// IsLoaded provides a mock function with given fields: +func (_m *Model) IsLoaded() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +type mockConstructorTestingTNewModel interface { + mock.TestingT + Cleanup(func()) +} + +// NewModel creates a new instance of Model. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewModel(t mockConstructorTestingTNewModel) *Model { + mock := &Model{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/ModelID.go b/mocks/orm/ModelID.go new file mode 100644 index 00000000..dfaee03d --- /dev/null +++ b/mocks/orm/ModelID.go @@ -0,0 +1,39 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// ModelID is an autogenerated mock type for the ModelID type +type ModelID struct { + mock.Mock +} + +// IsNil provides a mock function with given fields: +func (_m *ModelID) IsNil() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +type mockConstructorTestingTNewModelID interface { + mock.TestingT + Cleanup(func()) +} + +// NewModelID creates a new instance of ModelID. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewModelID(t mockConstructorTestingTNewModelID) *ModelID { + mock := &ModelID{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/WhereCondition.go b/mocks/orm/WhereCondition.go index 725d3ef3..94eb0849 100644 --- a/mocks/orm/WhereCondition.go +++ b/mocks/orm/WhereCondition.go @@ -3,34 +3,35 @@ package mocks import ( + orm "github.com/ditrit/badaas/orm" mock "github.com/stretchr/testify/mock" gorm "gorm.io/gorm" ) // WhereCondition is an autogenerated mock type for the WhereCondition type -type WhereCondition[T interface{}] struct { +type WhereCondition[T orm.Model] struct { mock.Mock } -// ApplyTo provides a mock function with given fields: query, tableName -func (_m *WhereCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB, error) { - ret := _m.Called(query, tableName) +// ApplyTo provides a mock function with given fields: query, table +func (_m *WhereCondition[T]) ApplyTo(query *gorm.DB, table orm.Table) (*gorm.DB, error) { + ret := _m.Called(query, table) var r0 *gorm.DB var r1 error - if rf, ok := ret.Get(0).(func(*gorm.DB, string) (*gorm.DB, error)); ok { - return rf(query, tableName) + if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) (*gorm.DB, error)); ok { + return rf(query, table) } - if rf, ok := ret.Get(0).(func(*gorm.DB, string) *gorm.DB); ok { - r0 = rf(query, tableName) + if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) *gorm.DB); ok { + r0 = rf(query, table) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*gorm.DB) } } - if rf, ok := ret.Get(1).(func(*gorm.DB, string) error); ok { - r1 = rf(query, tableName) + if rf, ok := ret.Get(1).(func(*gorm.DB, orm.Table) error); ok { + r1 = rf(query, table) } else { r1 = ret.Error(1) } @@ -38,32 +39,32 @@ func (_m *WhereCondition[T]) ApplyTo(query *gorm.DB, tableName string) (*gorm.DB return r0, r1 } -// GetSQL provides a mock function with given fields: query, tableName -func (_m *WhereCondition[T]) GetSQL(query *gorm.DB, tableName string) (string, []interface{}, error) { - ret := _m.Called(query, tableName) +// GetSQL provides a mock function with given fields: query, table +func (_m *WhereCondition[T]) GetSQL(query *gorm.DB, table orm.Table) (string, []interface{}, error) { + ret := _m.Called(query, table) var r0 string var r1 []interface{} var r2 error - if rf, ok := ret.Get(0).(func(*gorm.DB, string) (string, []interface{}, error)); ok { - return rf(query, tableName) + if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) (string, []interface{}, error)); ok { + return rf(query, table) } - if rf, ok := ret.Get(0).(func(*gorm.DB, string) string); ok { - r0 = rf(query, tableName) + if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) string); ok { + r0 = rf(query, table) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(*gorm.DB, string) []interface{}); ok { - r1 = rf(query, tableName) + if rf, ok := ret.Get(1).(func(*gorm.DB, orm.Table) []interface{}); ok { + r1 = rf(query, table) } else { if ret.Get(1) != nil { r1 = ret.Get(1).([]interface{}) } } - if rf, ok := ret.Get(2).(func(*gorm.DB, string) error); ok { - r2 = rf(query, tableName) + if rf, ok := ret.Get(2).(func(*gorm.DB, orm.Table) error); ok { + r2 = rf(query, table) } else { r2 = ret.Error(2) } @@ -96,7 +97,7 @@ type mockConstructorTestingTNewWhereCondition interface { } // NewWhereCondition creates a new instance of WhereCondition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewWhereCondition[T interface{}](t mockConstructorTestingTNewWhereCondition) *WhereCondition[T] { +func NewWhereCondition[T orm.Model](t mockConstructorTestingTNewWhereCondition) *WhereCondition[T] { mock := &WhereCondition[T]{} mock.Mock.Test(t) From 4a7beb3009203dfde65473ba7bd31be818f57a57 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Thu, 3 Aug 2023 17:19:49 +0200 Subject: [PATCH 69/90] update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 24def010..6ad700ff 100644 --- a/changelog.md +++ b/changelog.md @@ -33,5 +33,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Transform BadAas into a library. - Add badaas-orm with the compilable query system. - Add operators support +- Add preloading [unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased From 0d72fd4b772bd273d92655640f211a4c34cf9c2b Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Fri, 4 Aug 2023 14:26:12 +0200 Subject: [PATCH 70/90] refactor: replace gorm.db by query object --- orm/condition.go | 106 +++++++++++++++++++++--------------------- orm/crudRepository.go | 27 +---------- orm/query.go | 78 +++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 77 deletions(-) create mode 100644 orm/query.go diff --git a/orm/condition.go b/orm/condition.go index e755fac8..bfed47d9 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -60,7 +60,7 @@ type Condition[T Model] interface { // Applies the condition to the "query" // using the "tableName" as name for the table holding // the data for object of type T - ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) + ApplyTo(query *Query, table Table) error // This method is necessary to get the compiler to verify // that an object is of type Condition[T], @@ -75,7 +75,7 @@ type WhereCondition[T Model] interface { Condition[T] // Get the sql string and values to use in the query - GetSQL(query *gorm.DB, table Table) (string, []any, error) + GetSQL(query *Query, table Table) (string, []any, error) // Returns true if the DeletedAt column if affected by the condition // If no condition affects the DeletedAt, the verification that it's null will be added automatically @@ -95,11 +95,11 @@ func (condition ContainerCondition[T]) interfaceVerificationMethod(_ T) { // that an object is of type Condition[T] } -func (condition ContainerCondition[T]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { +func (condition ContainerCondition[T]) ApplyTo(query *Query, table Table) error { return applyWhereCondition[T](condition, query, table) } -func (condition ContainerCondition[T]) GetSQL(query *gorm.DB, table Table) (string, []any, error) { +func (condition ContainerCondition[T]) GetSQL(query *Query, table Table) (string, []any, error) { sqlString, values, err := condition.ConnectionCondition.GetSQL(query, table) if err != nil { return "", nil, err @@ -141,11 +141,11 @@ func (condition ConnectionCondition[T]) interfaceVerificationMethod(_ T) { // that an object is of type Condition[T] } -func (condition ConnectionCondition[T]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { +func (condition ConnectionCondition[T]) ApplyTo(query *Query, table Table) error { return applyWhereCondition[T](condition, query, table) } -func (condition ConnectionCondition[T]) GetSQL(query *gorm.DB, table Table) (string, []any, error) { +func (condition ConnectionCondition[T]) GetSQL(query *Query, table Table) (string, []any, error) { sqlStrings := []string{} values := []any{} @@ -185,14 +185,20 @@ type FieldIdentifier struct { ColumnPrefix string } -func (columnID FieldIdentifier) ColumnName(db *gorm.DB, table Table) string { - columnName := columnID.Column +// Returns the name of the column in which the field is saved in the table +func (fieldID FieldIdentifier[T]) ColumnName(query *Query, table Table) string { + columnName := fieldID.Column if columnName == "" { - columnName = db.NamingStrategy.ColumnName(table.Name, columnID.Field) + columnName = query.ColumnName(table, fieldID.Field) } // add column prefix and table name once we know the column name - return columnID.ColumnPrefix + columnName + return fieldID.ColumnPrefix + columnName +} + +// Returns the SQL to get the value of the field in the table +func (fieldID FieldIdentifier[T]) ColumnSQL(query *Query, table Table) string { + return table.Alias + "." + fieldID.ColumnName(query, table) } // Condition used to the preload the attributes of a model @@ -206,21 +212,12 @@ func (condition PreloadCondition[T]) interfaceVerificationMethod(_ T) { // that an object is of type Condition[T] } -func (condition PreloadCondition[T]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { +func (condition PreloadCondition[T]) ApplyTo(query *Query, table Table) error { for _, fieldID := range condition.Fields { - columnName := fieldID.ColumnName(query, table) - - query.Statement.Selects = append( - query.Statement.Selects, - fmt.Sprintf( - "%[1]s.%[2]s AS \"%[1]s__%[2]s\"", // name used by gorm to load the fields inside the models - table.Alias, - columnName, - ), - ) + query.AddSelect(table, fieldID) } - return query, nil + return nil } // Condition used to the preload the attributes of a model @@ -249,12 +246,13 @@ func (condition CollectionPreloadCondition[T1, T2]) interfaceVerificationMethod( // that an object is of type Condition[T1] } -func (condition CollectionPreloadCondition[T1, T2]) ApplyTo(query *gorm.DB, _ Table) (*gorm.DB, error) { +func (condition CollectionPreloadCondition[T1, T2]) ApplyTo(query *Query, _ Table) error { if len(condition.NestedPreloads) == 0 { - return query.Preload(condition.CollectionField), nil + query.Preload(condition.CollectionField) + return nil } - return query.Preload( + query.Preload( condition.CollectionField, func(db *gorm.DB) *gorm.DB { preloadsAsCondition := pie.Map( @@ -264,15 +262,17 @@ func (condition CollectionPreloadCondition[T1, T2]) ApplyTo(query *gorm.DB, _ Ta }, ) - query, err := applyConditionsToQuery[T2](db, preloadsAsCondition) + preloadInternalQuery, err := NewQuery(db, preloadsAsCondition) if err != nil { _ = db.AddError(err) return db } - return query + return preloadInternalQuery.gormDB }, - ), nil + ) + + return nil } // Condition used to the preload a collection of models of a model @@ -304,24 +304,26 @@ func (condition FieldCondition[TObject, TAtribute]) interfaceVerificationMethod( // Returns a gorm Where condition that can be used // to filter that the Field as a value of Value -func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { +func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *Query, table Table) error { return applyWhereCondition[TObject](condition, query, table) } -func applyWhereCondition[T Model](condition WhereCondition[T], query *gorm.DB, table Table) (*gorm.DB, error) { +func applyWhereCondition[T Model](condition WhereCondition[T], query *Query, table Table) error { sql, values, err := condition.GetSQL(query, table) if err != nil { - return nil, err + return err } if condition.affectsDeletedAt() { - query = query.Unscoped() + query.Unscoped() } - return query.Where( + query.Where( sql, values..., - ), nil + ) + + return nil } //nolint:unused // is used @@ -329,7 +331,7 @@ func (condition FieldCondition[TObject, TAtribute]) affectsDeletedAt() bool { return condition.FieldIdentifier.Field == deletedAtField } -func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *gorm.DB, table Table) (string, []any, error) { +func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *Query, table Table) (string, []any, error) { columnName := table.Alias + "." + condition.FieldIdentifier.ColumnName(query, table) return condition.Operator.ToSQL(columnName) } @@ -383,13 +385,13 @@ func (condition JoinCondition[T1, T2]) makesFilter() bool { // Applies a join between the tables of T1 and T2 // previousTableName is the name of the table of T1 // It also applies the nested conditions -func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (*gorm.DB, error) { +func (condition JoinCondition[T1, T2]) ApplyTo(query *Query, t1Table Table) error { whereConditions, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) // get the sql to do the join with T2 t2Table, err := t1Table.DeliverTable(query, *new(T2), condition.RelationField) if err != nil { - return nil, err + return err } makesPreload := condition.makesPreload() @@ -405,7 +407,7 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (* onQuery, onValues, err := connectionCondition.GetSQL(query, t2Table) if err != nil { - return nil, err + return err } if onQuery != "" { @@ -420,7 +422,7 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (* } // add the join to the query - query = query.Joins(joinQuery, onValues...) + query.Joins(joinQuery, onValues...) // apply T1 preload condition // if this condition has a T2 preload condition @@ -428,36 +430,36 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *gorm.DB, t1Table Table) (* // and this is not first level (T1 is the type of the repository) // because T1 is always loaded in that case if makesPreload && !t1Table.IsInitial() { - query, err = condition.T1PreloadCondition.ApplyTo(query, t1Table) + err = condition.T1PreloadCondition.ApplyTo(query, t1Table) if err != nil { - return nil, err + return err } } // apply T2 preload condition if t2PreloadCondition != nil { - query, err = t2PreloadCondition.ApplyTo(query, t2Table) + err = t2PreloadCondition.ApplyTo(query, t2Table) if err != nil { - return nil, err + return err } } // apply nested joins for _, joinCondition := range joinConditions { - query, err = joinCondition.ApplyTo(query, t2Table) + err = joinCondition.ApplyTo(query, t2Table) if err != nil { - return nil, err + return err } } - return query, nil + return nil } // Returns the SQL string to do a join between T1 and T2 // taking into account that the ID attribute necessary to do it // can be either in T1's or T2's table. func (condition JoinCondition[T1, T2]) getSQLJoin( - query *gorm.DB, + query *Query, t1Table Table, t2Table Table, isLeftJoin bool, @@ -472,9 +474,9 @@ func (condition JoinCondition[T1, T2]) getSQLJoin( `, t2Table.Name, t2Table.Alias, - query.NamingStrategy.ColumnName(t2Table.Name, condition.T2Field), + query.ColumnName(t2Table, condition.T2Field), t1Table.Alias, - query.NamingStrategy.ColumnName(t1Table.Name, condition.T1Field), + query.ColumnName(t1Table, condition.T1Field), joinString, ) } @@ -555,11 +557,11 @@ func (condition InvalidCondition[T]) interfaceVerificationMethod(_ T) { // that an object is of type Condition[T] } -func (condition InvalidCondition[T]) ApplyTo(_ *gorm.DB, _ Table) (*gorm.DB, error) { - return nil, condition.Err +func (condition InvalidCondition[T]) ApplyTo(_ *Query, _ Table) error { + return condition.Err } -func (condition InvalidCondition[T]) GetSQL(_ *gorm.DB, _ Table) (string, []any, error) { +func (condition InvalidCondition[T]) GetSQL(_ *Query, _ Table) (string, []any, error) { return "", nil, condition.Err } diff --git a/orm/crudRepository.go b/orm/crudRepository.go index 308cba80..a0e1eb51 100644 --- a/orm/crudRepository.go +++ b/orm/crudRepository.go @@ -94,41 +94,18 @@ func (repository *CRUDRepositoryImpl[T, ID]) QueryOne(tx *gorm.DB, conditions .. // Get the list of models that match "conditions" inside transaction "tx" func (repository *CRUDRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) { - query, err := applyConditionsToQuery(tx, conditions) + query, err := NewQuery(tx, conditions) if err != nil { return nil, err } // execute query var entities []*T - err = query.Find(&entities).Error + err = query.Find(&entities) return entities, err } -func applyConditionsToQuery[T Model](query *gorm.DB, conditions []Condition[T]) (*gorm.DB, error) { - initialTableName, err := getTableName(query, *new(T)) - if err != nil { - return nil, err - } - - initialTable := Table{ - Name: initialTableName, - Alias: initialTableName, - Initial: true, - } - - query = query.Select(initialTableName + ".*") - for _, condition := range conditions { - query, err = condition.ApplyTo(query, initialTable) - if err != nil { - return nil, err - } - } - - return query, nil -} - // Get the name of the table in "db" in which the data for "entity" is saved // returns error is table name can not be found by gorm, // probably because the type of "entity" is not registered using AddModel diff --git a/orm/query.go b/orm/query.go new file mode 100644 index 00000000..5e119a65 --- /dev/null +++ b/orm/query.go @@ -0,0 +1,78 @@ +package orm + +import ( + "fmt" + + "gorm.io/gorm" +) + +type Query struct { + gormDB *gorm.DB +} + +func (query *Query) AddSelect(table Table, fieldID iFieldIdentifier) { + columnName := fieldID.ColumnName(query, table) + + query.gormDB.Statement.Selects = append( + query.gormDB.Statement.Selects, + fmt.Sprintf( + "%[1]s.%[2]s AS \"%[1]s__%[2]s\"", // name used by gorm to load the fields inside the models + table.Alias, + columnName, + ), + ) +} + +func (query *Query) Preload(preloadQuery string, args ...interface{}) { + query.gormDB = query.gormDB.Preload(preloadQuery, args...) +} + +func (query *Query) Unscoped() { + query.gormDB = query.gormDB.Unscoped() +} + +func (query *Query) Where(whereQuery interface{}, args ...interface{}) { + query.gormDB = query.gormDB.Where(whereQuery, args...) +} + +func (query *Query) Joins(joinQuery string, args ...interface{}) { + query.gormDB = query.gormDB.Joins(joinQuery, args...) +} + +func (query *Query) Find(dest interface{}, conds ...interface{}) error { + query.gormDB = query.gormDB.Find(dest, conds...) + + return query.gormDB.Error +} + +func (query Query) ColumnName(table Table, fieldName string) string { + return query.gormDB.NamingStrategy.ColumnName(table.Name, fieldName) +} + +func NewQuery[T Model](db *gorm.DB, conditions []Condition[T]) (*Query, error) { + model := *new(T) + + initialTableName, err := getTableName(db, model) + if err != nil { + return nil, err + } + + initialTable := Table{ + Name: initialTableName, + Alias: initialTableName, + Initial: true, + } + + query := &Query{ + gormDB: db.Select(initialTableName + ".*"), + } + + for _, condition := range conditions { + err = condition.ApplyTo(query, initialTable) + if err != nil { + return nil, err + } + } + + return query, nil +} From 0236a7c068b632899fdf09f275c098f9000a1ebd Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Fri, 4 Aug 2023 14:29:16 +0200 Subject: [PATCH 71/90] move table to the same file of query --- orm/condition.go | 33 --------------------------------- orm/query.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/orm/condition.go b/orm/condition.go index bfed47d9..ff63e8ab 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -23,39 +23,6 @@ var ( ErrOnlyPreloadsAllowed = errors.New("only conditions that do a preload are allowed") ) -type Table struct { - Name string - Alias string - Initial bool -} - -// Returns true if the Table is the initial table in a query -func (table Table) IsInitial() bool { - return table.Initial -} - -// Returns the related Table corresponding to the model -func (table Table) DeliverTable(query *gorm.DB, model Model, relationName string) (Table, error) { - // get the name of the table for the model - tableName, err := getTableName(query, model) - if err != nil { - return Table{}, err - } - - // add a suffix to avoid tables with the same name when joining - // the same table more than once - tableAlias := relationName - if !table.IsInitial() { - tableAlias = table.Alias + "__" + relationName - } - - return Table{ - Name: tableName, - Alias: tableAlias, - Initial: false, - }, nil -} - type Condition[T Model] interface { // Applies the condition to the "query" // using the "tableName" as name for the table holding diff --git a/orm/query.go b/orm/query.go index 5e119a65..db10b2e7 100644 --- a/orm/query.go +++ b/orm/query.go @@ -6,6 +6,39 @@ import ( "gorm.io/gorm" ) +type Table struct { + Name string + Alias string + Initial bool +} + +// Returns true if the Table is the initial table in a query +func (t Table) IsInitial() bool { + return t.Initial +} + +// Returns the related Table corresponding to the model +func (t Table) DeliverTable(query *Query, model Model, relationName string) (Table, error) { + // get the name of the table for the model + tableName, err := getTableName(query.gormDB, model) + if err != nil { + return Table{}, err + } + + // add a suffix to avoid tables with the same name when joining + // the same table more than once + tableAlias := relationName + if !t.IsInitial() { + tableAlias = t.Alias + "__" + relationName + } + + return Table{ + Name: tableName, + Alias: tableAlias, + Initial: false, + }, nil +} + type Query struct { gormDB *gorm.DB } From 2fc02191dbfe6619e6b2de78a0707b33b5c66d29 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Fri, 4 Aug 2023 14:35:05 +0200 Subject: [PATCH 72/90] move unsafe condition to unsafe package --- orm/condition.go | 117 +++++++---------------- orm/operator.go | 20 ++-- orm/operators.go | 38 ++++---- orm/query.go | 2 +- orm/sql/operators.go | 47 +++++++++ orm/unsafe/condition.go | 43 +++++++++ testintegration/join_conditions_test.go | 3 +- testintegration/where_conditions_test.go | 3 +- 8 files changed, 164 insertions(+), 109 deletions(-) create mode 100644 orm/sql/operators.go create mode 100644 orm/unsafe/condition.go diff --git a/orm/condition.go b/orm/condition.go index ff63e8ab..e15e3b48 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -7,6 +7,8 @@ import ( "github.com/elliotchance/pie/v2" "gorm.io/gorm" + + "github.com/ditrit/badaas/orm/sql" ) const deletedAtField = "DeletedAt" @@ -33,7 +35,7 @@ type Condition[T Model] interface { // that an object is of type Condition[T], // since if no method receives by parameter a type T, // any other Condition[T2] would also be considered a Condition[T]. - interfaceVerificationMethod(T) + InterfaceVerificationMethod(T) } // Conditions that can be used in a where clause @@ -46,24 +48,23 @@ type WhereCondition[T Model] interface { // Returns true if the DeletedAt column if affected by the condition // If no condition affects the DeletedAt, the verification that it's null will be added automatically - affectsDeletedAt() bool + AffectsDeletedAt() bool } // Condition that contains a internal condition. // Example: NOT (internal condition) type ContainerCondition[T Model] struct { ConnectionCondition WhereCondition[T] - Prefix string + Prefix sql.Operator } -//nolint:unused // see inside -func (condition ContainerCondition[T]) interfaceVerificationMethod(_ T) { +func (condition ContainerCondition[T]) InterfaceVerificationMethod(_ T) { // This method is necessary to get the compiler to verify // that an object is of type Condition[T] } func (condition ContainerCondition[T]) ApplyTo(query *Query, table Table) error { - return applyWhereCondition[T](condition, query, table) + return ApplyWhereCondition[T](condition, query, table) } func (condition ContainerCondition[T]) GetSQL(query *Query, table Table) (string, []any, error) { @@ -72,19 +73,18 @@ func (condition ContainerCondition[T]) GetSQL(query *Query, table Table) (string return "", nil, err } - sqlString = condition.Prefix + " (" + sqlString + ")" + sqlString = condition.Prefix.String() + " (" + sqlString + ")" return sqlString, values, nil } -//nolint:unused // is used -func (condition ContainerCondition[T]) affectsDeletedAt() bool { - return condition.ConnectionCondition.affectsDeletedAt() +func (condition ContainerCondition[T]) AffectsDeletedAt() bool { + return condition.ConnectionCondition.AffectsDeletedAt() } // Condition that contains a internal condition. // Example: NOT (internal condition) -func NewContainerCondition[T Model](prefix string, conditions ...WhereCondition[T]) WhereCondition[T] { +func NewContainerCondition[T Model](prefix sql.Operator, conditions ...WhereCondition[T]) WhereCondition[T] { if len(conditions) == 0 { return NewInvalidCondition[T](ErrEmptyConditions) } @@ -98,18 +98,17 @@ func NewContainerCondition[T Model](prefix string, conditions ...WhereCondition[ // Condition that connects multiple conditions. // Example: condition1 AND condition2 type ConnectionCondition[T Model] struct { - Connector string + Connector sql.Operator Conditions []WhereCondition[T] } -//nolint:unused // see inside -func (condition ConnectionCondition[T]) interfaceVerificationMethod(_ T) { +func (condition ConnectionCondition[T]) InterfaceVerificationMethod(_ T) { // This method is necessary to get the compiler to verify // that an object is of type Condition[T] } func (condition ConnectionCondition[T]) ApplyTo(query *Query, table Table) error { - return applyWhereCondition[T](condition, query, table) + return ApplyWhereCondition[T](condition, query, table) } func (condition ConnectionCondition[T]) GetSQL(query *Query, table Table) (string, []any, error) { @@ -127,19 +126,21 @@ func (condition ConnectionCondition[T]) GetSQL(query *Query, table Table) (strin values = append(values, internalValues...) } - return strings.Join(sqlStrings, " "+condition.Connector+" "), values, nil + return strings.Join( + sqlStrings, + " "+condition.Connector.String()+" ", + ), values, nil } -//nolint:unused // is used -func (condition ConnectionCondition[T]) affectsDeletedAt() bool { +func (condition ConnectionCondition[T]) AffectsDeletedAt() bool { return pie.Any(condition.Conditions, func(internalCondition WhereCondition[T]) bool { - return internalCondition.affectsDeletedAt() + return internalCondition.AffectsDeletedAt() }) } // Condition that connects multiple conditions. // Example: condition1 AND condition2 -func NewConnectionCondition[T Model](connector string, conditions ...WhereCondition[T]) WhereCondition[T] { +func NewConnectionCondition[T Model](connector sql.Operator, conditions ...WhereCondition[T]) WhereCondition[T] { return ConnectionCondition[T]{ Connector: connector, Conditions: conditions, @@ -153,7 +154,7 @@ type FieldIdentifier struct { } // Returns the name of the column in which the field is saved in the table -func (fieldID FieldIdentifier[T]) ColumnName(query *Query, table Table) string { +func (fieldID FieldIdentifier) ColumnName(query *Query, table Table) string { columnName := fieldID.Column if columnName == "" { columnName = query.ColumnName(table, fieldID.Field) @@ -164,7 +165,7 @@ func (fieldID FieldIdentifier[T]) ColumnName(query *Query, table Table) string { } // Returns the SQL to get the value of the field in the table -func (fieldID FieldIdentifier[T]) ColumnSQL(query *Query, table Table) string { +func (fieldID FieldIdentifier) ColumnSQL(query *Query, table Table) string { return table.Alias + "." + fieldID.ColumnName(query, table) } @@ -173,8 +174,7 @@ type PreloadCondition[T Model] struct { Fields []FieldIdentifier } -//nolint:unused // see inside -func (condition PreloadCondition[T]) interfaceVerificationMethod(_ T) { +func (condition PreloadCondition[T]) InterfaceVerificationMethod(_ T) { // This method is necessary to get the compiler to verify // that an object is of type Condition[T] } @@ -207,8 +207,7 @@ type CollectionPreloadCondition[T1 Model, T2 Model] struct { NestedPreloads []IJoinCondition[T2] } -//nolint:unused // see inside -func (condition CollectionPreloadCondition[T1, T2]) interfaceVerificationMethod(_ T1) { +func (condition CollectionPreloadCondition[T1, T2]) InterfaceVerificationMethod(_ T1) { // This method is necessary to get the compiler to verify // that an object is of type Condition[T1] } @@ -263,8 +262,7 @@ type FieldCondition[TObject Model, TAtribute any] struct { Operator Operator[TAtribute] } -//nolint:unused // see inside -func (condition FieldCondition[TObject, TAtribute]) interfaceVerificationMethod(_ TObject) { +func (condition FieldCondition[TObject, TAtribute]) InterfaceVerificationMethod(_ TObject) { // This method is necessary to get the compiler to verify // that an object is of type Condition[T] } @@ -272,16 +270,16 @@ func (condition FieldCondition[TObject, TAtribute]) interfaceVerificationMethod( // Returns a gorm Where condition that can be used // to filter that the Field as a value of Value func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *Query, table Table) error { - return applyWhereCondition[TObject](condition, query, table) + return ApplyWhereCondition[TObject](condition, query, table) } -func applyWhereCondition[T Model](condition WhereCondition[T], query *Query, table Table) error { +func ApplyWhereCondition[T Model](condition WhereCondition[T], query *Query, table Table) error { sql, values, err := condition.GetSQL(query, table) if err != nil { return err } - if condition.affectsDeletedAt() { + if condition.AffectsDeletedAt() { query.Unscoped() } @@ -293,8 +291,7 @@ func applyWhereCondition[T Model](condition WhereCondition[T], query *Query, tab return nil } -//nolint:unused // is used -func (condition FieldCondition[TObject, TAtribute]) affectsDeletedAt() bool { +func (condition FieldCondition[TObject, TAtribute]) AffectsDeletedAt() bool { return condition.FieldIdentifier.Field == deletedAtField } @@ -324,7 +321,7 @@ type JoinCondition[T1 Model, T2 Model] struct { T1PreloadCondition PreloadCondition[T1] } -func (condition JoinCondition[T1, T2]) interfaceVerificationMethod(t T1) { +func (condition JoinCondition[T1, T2]) InterfaceVerificationMethod(_ T1) { // This method is necessary to get the compiler to verify // that an object is of type Condition[T] } @@ -381,7 +378,7 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *Query, t1Table Table) erro joinQuery += " AND " + onQuery } - if !connectionCondition.affectsDeletedAt() { + if !connectionCondition.AffectsDeletedAt() { joinQuery += fmt.Sprintf( " AND %s.deleted_at IS NULL", t2Table.Alias, @@ -475,51 +472,12 @@ func divideConditionsByType[T Model]( return } -// Condition that can be used to express conditions that are not supported (yet?) by BaDORM -// Example: table1.columnX = table2.columnY -type UnsafeCondition[T Model] struct { - SQLCondition string - Values []any -} - -//nolint:unused // see inside -func (condition UnsafeCondition[T]) interfaceVerificationMethod(_ T) { - // This method is necessary to get the compiler to verify - // that an object is of type Condition[T] -} - -func (condition UnsafeCondition[T]) ApplyTo(query *gorm.DB, table Table) (*gorm.DB, error) { - return applyWhereCondition[T](condition, query, table) -} - -func (condition UnsafeCondition[T]) GetSQL(_ *gorm.DB, table Table) (string, []any, error) { - return fmt.Sprintf( - condition.SQLCondition, - table.Alias, - ), condition.Values, nil -} - -//nolint:unused // is used -func (condition UnsafeCondition[T]) affectsDeletedAt() bool { - return false -} - -// Condition that can be used to express conditions that are not supported (yet?) by BaDORM -// Example: table1.columnX = table2.columnY -func NewUnsafeCondition[T Model](condition string, values []any) UnsafeCondition[T] { - return UnsafeCondition[T]{ - SQLCondition: condition, - Values: values, - } -} - // Condition used to returns an error when the query is executed type InvalidCondition[T any] struct { Err error } -//nolint:unused // see inside -func (condition InvalidCondition[T]) interfaceVerificationMethod(_ T) { +func (condition InvalidCondition[T]) InterfaceVerificationMethod(_ T) { // This method is necessary to get the compiler to verify // that an object is of type Condition[T] } @@ -532,8 +490,7 @@ func (condition InvalidCondition[T]) GetSQL(_ *Query, _ Table) (string, []any, e return "", nil, condition.Err } -//nolint:unused // is used -func (condition InvalidCondition[T]) affectsDeletedAt() bool { +func (condition InvalidCondition[T]) AffectsDeletedAt() bool { return false } @@ -548,13 +505,13 @@ func NewInvalidCondition[T any](err error) InvalidCondition[T] { // ref: https://www.postgresql.org/docs/current/functions-logical.html func And[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { - return NewConnectionCondition("AND", conditions...) + return NewConnectionCondition(sql.And, conditions...) } func Or[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { - return NewConnectionCondition("OR", conditions...) + return NewConnectionCondition(sql.Or, conditions...) } func Not[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { - return NewContainerCondition("NOT", conditions...) + return NewContainerCondition(sql.Not, conditions...) } diff --git a/orm/operator.go b/orm/operator.go index b2b83789..1f37d7cf 100644 --- a/orm/operator.go +++ b/orm/operator.go @@ -1,6 +1,10 @@ package orm -import "fmt" +import ( + "fmt" + + "github.com/ditrit/badaas/orm/sql" +) type Operator[T any] interface { // Transform the Operator to a SQL string and a list of values to use in the query @@ -23,7 +27,7 @@ type ValueOperator[T any] struct { } type operation struct { - SQLOperator string + SQLOperator sql.Operator Value any } @@ -37,20 +41,18 @@ func (operator ValueOperator[T]) ToSQL(columnName string) (string, []any, error) values := []any{} for _, operation := range operator.Operations { - operatorString += " " + operation.SQLOperator + " ?" + operatorString += " " + operation.SQLOperator.String() + " ?" values = append(values, operation.Value) } return operatorString, values, nil } -func NewValueOperator[T any](sqlOperator string, value any) ValueOperator[T] { - operator := ValueOperator[T]{} - - return operator.AddOperation(sqlOperator, value) +func NewValueOperator[T any](sqlOperator sql.Operator, value any) ValueOperator[T] { + return *new(ValueOperator[T]).AddOperation(sqlOperator, value) } -func (operator *ValueOperator[T]) AddOperation(sqlOperator string, value any) ValueOperator[T] { +func (operator *ValueOperator[T]) AddOperation(sqlOperator sql.Operator, value any) *ValueOperator[T] { operator.Operations = append( operator.Operations, operation{ @@ -59,7 +61,7 @@ func (operator *ValueOperator[T]) AddOperation(sqlOperator string, value any) Va }, ) - return *operator + return operator } // Operator that verifies a predicate diff --git a/orm/operators.go b/orm/operators.go index a8093515..11bbd871 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -1,38 +1,42 @@ package orm +import ( + "github.com/ditrit/badaas/orm/sql" +) + // Comparison Operators // ref: https://www.postgresql.org/docs/current/functions-comparison.html // EqualTo // IsNotDistinct must be used in cases where value can be NULL func Eq[T any](value T) Operator[T] { - return NewValueOperator[T]("=", value) + return NewValueOperator[T](sql.Eq, value) } // NotEqualTo // IsDistinct must be used in cases where value can be NULL func NotEq[T any](value T) Operator[T] { - return NewValueOperator[T]("<>", value) + return NewValueOperator[T](sql.NotEq, value) } // LessThan func Lt[T any](value T) Operator[T] { - return NewValueOperator[T]("<", value) + return NewValueOperator[T](sql.Lt, value) } // LessThanOrEqualTo func LtOrEq[T any](value T) Operator[T] { - return NewValueOperator[T]("<=", value) + return NewValueOperator[T](sql.LtOrEq, value) } // GreaterThan func Gt[T any](value T) Operator[T] { - return NewValueOperator[T](">", value) + return NewValueOperator[T](sql.Gt, value) } // GreaterThanOrEqualTo func GtOrEq[T any](value T) Operator[T] { - return NewValueOperator[T](">=", value) + return NewValueOperator[T](sql.GtOrEq, value) } // Comparison Predicates @@ -40,17 +44,17 @@ func GtOrEq[T any](value T) Operator[T] { // Equivalent to v1 < value < v2 func Between[T any](v1 T, v2 T) Operator[T] { - return newBetweenOperator("BETWEEN", v1, v2) + return newBetweenOperator(sql.Between, v1, v2) } // Equivalent to NOT (v1 < value < v2) func NotBetween[T any](v1 T, v2 T) Operator[T] { - return newBetweenOperator("NOT BETWEEN", v1, v2) + return newBetweenOperator(sql.NotBetween, v1, v2) } -func newBetweenOperator[T any](sqlOperator string, v1 T, v2 T) Operator[T] { +func newBetweenOperator[T any](sqlOperator sql.Operator, v1 T, v2 T) Operator[T] { operator := NewValueOperator[T](sqlOperator, v1) - return operator.AddOperation("AND", v2) + return operator.AddOperation(sql.And, v2) } func IsNull[T any]() PredicateOperator[T] { @@ -88,21 +92,21 @@ func IsNotUnknown() PredicateOperator[bool] { } func IsDistinct[T any](value T) ValueOperator[T] { - return NewValueOperator[T]("IS DISTINCT FROM", value) + return NewValueOperator[T](sql.IsDistinct, value) } func IsNotDistinct[T any](value T) ValueOperator[T] { - return NewValueOperator[T]("IS NOT DISTINCT FROM", value) + return NewValueOperator[T](sql.IsNotDistinct, value) } // Row and Array Comparisons func ArrayIn[T any](values ...T) ValueOperator[T] { - return NewValueOperator[T]("IN", values) + return NewValueOperator[T](sql.ArrayIn, values) } func ArrayNotIn[T any](values ...T) ValueOperator[T] { - return NewValueOperator[T]("NOT IN", values) + return NewValueOperator[T](sql.ArrayNotIn, values) } // Pattern Matching @@ -111,14 +115,14 @@ type LikeOperator struct { ValueOperator[string] } -func NewLikeOperator(sqlOperator string, pattern string) LikeOperator { +func NewLikeOperator(sqlOperator sql.Operator, pattern string) LikeOperator { return LikeOperator{ ValueOperator: NewValueOperator[string](sqlOperator, pattern), } } func (operator LikeOperator) Escape(escape rune) ValueOperator[string] { - return operator.AddOperation("ESCAPE", string(escape)) + return *operator.AddOperation(sql.Escape, string(escape)) } // Patterns: @@ -127,5 +131,5 @@ func (operator LikeOperator) Escape(escape rune) ValueOperator[string] { // // ref: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE func Like(pattern string) LikeOperator { - return NewLikeOperator("LIKE", pattern) + return NewLikeOperator(sql.Like, pattern) } diff --git a/orm/query.go b/orm/query.go index db10b2e7..10f3ed07 100644 --- a/orm/query.go +++ b/orm/query.go @@ -43,7 +43,7 @@ type Query struct { gormDB *gorm.DB } -func (query *Query) AddSelect(table Table, fieldID iFieldIdentifier) { +func (query *Query) AddSelect(table Table, fieldID FieldIdentifier) { columnName := fieldID.ColumnName(query, table) query.gormDB.Statement.Selects = append( diff --git a/orm/sql/operators.go b/orm/sql/operators.go new file mode 100644 index 00000000..7150e044 --- /dev/null +++ b/orm/sql/operators.go @@ -0,0 +1,47 @@ +package sql + +type Operator uint + +const ( + Eq Operator = iota + NotEq + Lt + LtOrEq + Gt + GtOrEq + Between + NotBetween + IsDistinct + IsNotDistinct + Like + Escape + ArrayIn + ArrayNotIn + And + Or + Not +) + +func (op Operator) String() string { + return operatorToSQL[op] +} + +var operatorToSQL = map[Operator]string{ + Eq: "=", + NotEq: "<>", + Lt: "<", + LtOrEq: "<=", + Gt: ">", + GtOrEq: ">=", + Between: "BETWEEN", + NotBetween: "NOT BETWEEN", + IsDistinct: "IS DISTINCT FROM", + IsNotDistinct: "IS NOT DISTINCT FROM", + Like: "LIKE", + Escape: "ESCAPE", + ArrayIn: "IN", + ArrayNotIn: "NOT IN", + And: "AND", + Or: "OR", + Not: "NOT", +} diff --git a/orm/unsafe/condition.go b/orm/unsafe/condition.go new file mode 100644 index 00000000..5ba19e58 --- /dev/null +++ b/orm/unsafe/condition.go @@ -0,0 +1,43 @@ +package unsafe + +import ( + "fmt" + + "github.com/ditrit/badaas/orm" +) + +// Condition that can be used to express conditions that are not supported (yet?) by badaas-orm +// Example: table1.columnX = table2.columnY +type Condition[T orm.Model] struct { + SQLCondition string + Values []any +} + +func (condition Condition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition Condition[T]) ApplyTo(query *orm.Query, table orm.Table) error { + return orm.ApplyWhereCondition[T](condition, query, table) +} + +func (condition Condition[T]) GetSQL(_ *orm.Query, table orm.Table) (string, []any, error) { + return fmt.Sprintf( + condition.SQLCondition, + table.Alias, + ), condition.Values, nil +} + +func (condition Condition[T]) AffectsDeletedAt() bool { + return false +} + +// Condition that can be used to express conditions that are not supported (yet?) by badaas-orm +// Example: table1.columnX = table2.columnY +func NewCondition[T orm.Model](condition string, values ...any) Condition[T] { + return Condition[T]{ + SQLCondition: condition, + Values: values, + } +} diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index b7833399..208654c8 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -4,6 +4,7 @@ import ( "gorm.io/gorm" "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/unsafe" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" ) @@ -370,7 +371,7 @@ func (ts *JoinConditionsIntTestSuite) TestJoinWithUnsafeCondition() { entities, err := ts.crudSaleService.Query( conditions.SaleSeller( conditions.SellerCompany( - orm.NewUnsafeCondition[models.Company]("%s.name = Seller.name", []any{}), + unsafe.NewCondition[models.Company]("%s.name = Seller.name"), ), ), ) diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index 0ec19abd..e3cacfe9 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -5,6 +5,7 @@ import ( "gotest.tools/assert" "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/unsafe" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" ) @@ -542,7 +543,7 @@ func (ts *WhereConditionsIntTestSuite) TestUnsafeCondition() { ts.createProduct("not_match", 2, 0.0, true, nil) entities, err := ts.crudProductService.Query( - orm.NewUnsafeCondition[models.Product]("%s.int = ?", []any{1}), + unsafe.NewCondition[models.Product]("%s.int = ?", 1), ) ts.Nil(err) From 95b0dbd0e04cf2de95463d35d38fb6755126d63b Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Fri, 4 Aug 2023 15:01:02 +0200 Subject: [PATCH 73/90] add details to errors --- orm/condition.go | 20 +++++------ orm/errors.go | 40 ++++++++++++++++++++++ orm/sql/operators.go | 24 +++++++++++++ testintegration/join_conditions_test.go | 1 + testintegration/preload_conditions_test.go | 2 ++ 5 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 orm/errors.go diff --git a/orm/condition.go b/orm/condition.go index e15e3b48..28217a93 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -1,7 +1,6 @@ package orm import ( - "errors" "fmt" "strings" @@ -20,11 +19,6 @@ var ( DeletedAtFieldID = FieldIdentifier{Field: deletedAtField} ) -var ( - ErrEmptyConditions = errors.New("condition must have at least one inner condition") - ErrOnlyPreloadsAllowed = errors.New("only conditions that do a preload are allowed") -) - type Condition[T Model] interface { // Applies the condition to the "query" // using the "tableName" as name for the table holding @@ -86,7 +80,7 @@ func (condition ContainerCondition[T]) AffectsDeletedAt() bool { // Example: NOT (internal condition) func NewContainerCondition[T Model](prefix sql.Operator, conditions ...WhereCondition[T]) WhereCondition[T] { if len(conditions) == 0 { - return NewInvalidCondition[T](ErrEmptyConditions) + return NewInvalidCondition[T](emptyConditionsError[T](prefix)) } return ContainerCondition[T]{ @@ -246,7 +240,7 @@ func NewCollectionPreloadCondition[T1 Model, T2 Model](collectionField string, n if pie.Any(nestedPreloads, func(nestedPreload IJoinCondition[T2]) bool { return !nestedPreload.makesPreload() || nestedPreload.makesFilter() }) { - return NewInvalidCondition[T1](ErrOnlyPreloadsAllowed) + return NewInvalidCondition[T1](onlyPreloadsAllowedError[T1](collectionField)) } return CollectionPreloadCondition[T1, T2]{ @@ -296,8 +290,14 @@ func (condition FieldCondition[TObject, TAtribute]) AffectsDeletedAt() bool { } func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *Query, table Table) (string, []any, error) { - columnName := table.Alias + "." + condition.FieldIdentifier.ColumnName(query, table) - return condition.Operator.ToSQL(columnName) + sqlString, values, err := condition.Operator.ToSQL( + condition.FieldIdentifier.ColumnSQL(query, table), + ) + if err != nil { + return "", nil, conditionOperatorError[TObject](err, condition) + } + + return sqlString, values, nil } // Interface of a join condition that joins T with any other model diff --git a/orm/errors.go b/orm/errors.go new file mode 100644 index 00000000..512ef69d --- /dev/null +++ b/orm/errors.go @@ -0,0 +1,40 @@ +package orm + +import ( + "errors" + "fmt" + + "github.com/ditrit/badaas/orm/sql" +) + +var ( + ErrEmptyConditions = errors.New("condition must have at least one inner condition") + ErrOnlyPreloadsAllowed = errors.New("only conditions that do a preload are allowed") +) + +func conditionOperatorError[TObject Model, TAtribute any](operatorErr error, condition FieldCondition[TObject, TAtribute]) error { + return fmt.Errorf( + "%w; model: %T, field: %s", + operatorErr, + *new(TObject), + condition.FieldIdentifier.Field, + ) +} + +func emptyConditionsError[T Model](connector sql.Operator) error { + return fmt.Errorf( + "%w; connector: %s; model: %T", + ErrEmptyConditions, + connector.Name(), + *new(T), + ) +} + +func onlyPreloadsAllowedError[T Model](fieldName string) error { + return fmt.Errorf( + "%w; model: %T, field: %s", + ErrOnlyPreloadsAllowed, + *new(T), + fieldName, + ) +} diff --git a/orm/sql/operators.go b/orm/sql/operators.go index 7150e044..fcf0c536 100644 --- a/orm/sql/operators.go +++ b/orm/sql/operators.go @@ -45,3 +45,27 @@ var operatorToSQL = map[Operator]string{ Or: "OR", Not: "NOT", } + +func (op Operator) Name() string { + return operatorToName[op] +} + +var operatorToName = map[Operator]string{ + Eq: "Eq", + NotEq: "NotEq", + Lt: "Lt", + LtOrEq: "LtOrEq", + Gt: "Gt", + GtOrEq: "GtOrEq", + Between: "Between", + NotBetween: "NotBetween", + IsDistinct: "IsDistinct", + IsNotDistinct: "IsNotDistinct", + Like: "Like", + Escape: "Escape", + ArrayIn: "ArrayIn", + ArrayNotIn: "ArrayNotIn", + And: "And", + Or: "Or", + Not: "Not", +} diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index 208654c8..b93e4d69 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -404,4 +404,5 @@ func (ts *JoinConditionsIntTestSuite) TestJoinWithEmptyContainerConditionReturns ), ) ts.ErrorIs(err, orm.ErrEmptyConditions) + ts.ErrorContains(err, "connector: Not; model: models.Product") } diff --git a/testintegration/preload_conditions_test.go b/testintegration/preload_conditions_test.go index 791d2172..931bae4b 100644 --- a/testintegration/preload_conditions_test.go +++ b/testintegration/preload_conditions_test.go @@ -875,6 +875,7 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributesWithF ), ) ts.ErrorIs(err, orm.ErrOnlyPreloadsAllowed) + ts.ErrorContains(err, "model: models.Company, field: Sellers") } func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributesWithoutPreloadReturnsError() { @@ -884,4 +885,5 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributesWitho ), ) ts.ErrorIs(err, orm.ErrOnlyPreloadsAllowed) + ts.ErrorContains(err, "model: models.Company, field: Sellers") } From 12b8255ce99359cbc034caba5f787700406b974d Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Fri, 4 Aug 2023 16:37:07 +0200 Subject: [PATCH 74/90] add dynamic operators --- orm/condition.go | 52 ++++---- orm/dynamic/operator.go | 13 ++ orm/dynamic/operators.go | 67 +++++++++++ orm/errors.go | 27 +++++ orm/operator.go | 84 +++++++++++-- orm/operators.go | 10 +- orm/query.go | 29 ++++- persistence/models/User.go | 2 +- .../conditions/bicycle_conditions.go | 49 ++++++-- .../conditions/brand_conditions.go | 42 +++++-- .../conditions/child_conditions.go | 78 ++++++++++-- testintegration/conditions/city_conditions.go | 49 ++++++-- .../conditions/company_conditions.go | 42 +++++-- .../conditions/country_conditions.go | 42 +++++-- .../conditions/employee_conditions.go | 49 ++++++-- .../conditions/parent1_conditions.go | 47 ++++++-- .../conditions/parent2_conditions.go | 47 ++++++-- .../conditions/parent_parent_conditions.go | 59 +++++++-- .../conditions/person_conditions.go | 42 +++++-- .../conditions/phone_conditions.go | 49 ++++++-- .../conditions/product_conditions.go | 111 ++++++++++++----- testintegration/conditions/sale_conditions.go | 63 ++++++++-- .../conditions/seller_conditions.go | 56 +++++++-- .../conditions/university_conditions.go | 47 ++++++-- testintegration/join_conditions_test.go | 112 ++++++++++++++++++ testintegration/models/models.go | 6 +- testintegration/operators_test.go | 65 ++++++++++ 27 files changed, 1130 insertions(+), 209 deletions(-) create mode 100644 orm/dynamic/operator.go create mode 100644 orm/dynamic/operators.go diff --git a/orm/condition.go b/orm/condition.go index 28217a93..f14c197f 100644 --- a/orm/condition.go +++ b/orm/condition.go @@ -2,6 +2,7 @@ package orm import ( "fmt" + "reflect" "strings" "github.com/elliotchance/pie/v2" @@ -12,13 +13,6 @@ import ( const deletedAtField = "DeletedAt" -var ( - IDFieldID = FieldIdentifier{Field: "ID"} - CreatedAtFieldID = FieldIdentifier{Field: "CreatedAt"} - UpdatedAtFieldID = FieldIdentifier{Field: "UpdatedAt"} - DeletedAtFieldID = FieldIdentifier{Field: deletedAtField} -) - type Condition[T Model] interface { // Applies the condition to the "query" // using the "tableName" as name for the table holding @@ -141,14 +135,25 @@ func NewConnectionCondition[T Model](connector sql.Operator, conditions ...Where } } -type FieldIdentifier struct { +type iFieldIdentifier interface { + ColumnName(query *Query, table Table) string + ColumnSQL(query *Query, table Table) string + GetModelType() reflect.Type +} + +type FieldIdentifier[T any] struct { Column string Field string ColumnPrefix string + ModelType reflect.Type +} + +func (fieldID FieldIdentifier[T]) GetModelType() reflect.Type { + return fieldID.ModelType } // Returns the name of the column in which the field is saved in the table -func (fieldID FieldIdentifier) ColumnName(query *Query, table Table) string { +func (fieldID FieldIdentifier[T]) ColumnName(query *Query, table Table) string { columnName := fieldID.Column if columnName == "" { columnName = query.ColumnName(table, fieldID.Field) @@ -159,13 +164,13 @@ func (fieldID FieldIdentifier) ColumnName(query *Query, table Table) string { } // Returns the SQL to get the value of the field in the table -func (fieldID FieldIdentifier) ColumnSQL(query *Query, table Table) string { +func (fieldID FieldIdentifier[T]) ColumnSQL(query *Query, table Table) string { return table.Alias + "." + fieldID.ColumnName(query, table) } // Condition used to the preload the attributes of a model type PreloadCondition[T Model] struct { - Fields []FieldIdentifier + Fields []iFieldIdentifier } func (condition PreloadCondition[T]) InterfaceVerificationMethod(_ T) { @@ -182,21 +187,14 @@ func (condition PreloadCondition[T]) ApplyTo(query *Query, table Table) error { } // Condition used to the preload the attributes of a model -func NewPreloadCondition[T Model](fields ...FieldIdentifier) PreloadCondition[T] { +func NewPreloadCondition[T Model](fields ...iFieldIdentifier) PreloadCondition[T] { return PreloadCondition[T]{ - Fields: append( - fields, - // base model fields - IDFieldID, - CreatedAtFieldID, - UpdatedAtFieldID, - DeletedAtFieldID, - ), + Fields: fields, } } // Condition used to the preload a collection of models of a model -type CollectionPreloadCondition[T1 Model, T2 Model] struct { +type CollectionPreloadCondition[T1, T2 Model] struct { CollectionField string NestedPreloads []IJoinCondition[T2] } @@ -252,7 +250,7 @@ func NewCollectionPreloadCondition[T1 Model, T2 Model](collectionField string, n // Condition that verifies the value of a field, // using the Operator type FieldCondition[TObject Model, TAtribute any] struct { - FieldIdentifier FieldIdentifier + FieldIdentifier FieldIdentifier[TAtribute] Operator Operator[TAtribute] } @@ -291,6 +289,7 @@ func (condition FieldCondition[TObject, TAtribute]) AffectsDeletedAt() bool { func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *Query, table Table) (string, []any, error) { sqlString, values, err := condition.Operator.ToSQL( + query, condition.FieldIdentifier.ColumnSQL(query, table), ) if err != nil { @@ -352,8 +351,10 @@ func (condition JoinCondition[T1, T2]) makesFilter() bool { func (condition JoinCondition[T1, T2]) ApplyTo(query *Query, t1Table Table) error { whereConditions, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) + t2Model := *new(T2) + // get the sql to do the join with T2 - t2Table, err := t1Table.DeliverTable(query, *new(T2), condition.RelationField) + t2Table, err := t1Table.DeliverTable(query, t2Model, condition.RelationField) if err != nil { return err } @@ -366,6 +367,11 @@ func (condition JoinCondition[T1, T2]) ApplyTo(query *Query, t1Table Table) erro len(whereConditions) == 0 && makesPreload, ) + query.AddConcernedModel( + t2Model, + t2Table, + ) + // apply WhereConditions to the join in the "on" clause connectionCondition := And(whereConditions...) diff --git a/orm/dynamic/operator.go b/orm/dynamic/operator.go new file mode 100644 index 00000000..1e0cb81e --- /dev/null +++ b/orm/dynamic/operator.go @@ -0,0 +1,13 @@ +package dynamic + +import ( + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/sql" +) + +func NewValueOperator[T any]( + sqlOperator sql.Operator, + field orm.FieldIdentifier[T], +) *orm.ValueOperator[T] { + return orm.NewValueOperator[T](sqlOperator, field) +} diff --git a/orm/dynamic/operators.go b/orm/dynamic/operators.go new file mode 100644 index 00000000..2fb68eba --- /dev/null +++ b/orm/dynamic/operators.go @@ -0,0 +1,67 @@ +package dynamic + +import ( + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/sql" +) + +// Comparison Operators +// ref: https://www.postgresql.org/docs/current/functions-comparison.html + +// EqualTo +func Eq[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return NewValueOperator(sql.Eq, field) +} + +// NotEqualTo +func NotEq[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return NewValueOperator(sql.NotEq, field) +} + +// LessThan +func Lt[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return NewValueOperator(sql.Lt, field) +} + +// LessThanOrEqualTo +func LtOrEq[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return NewValueOperator(sql.LtOrEq, field) +} + +// GreaterThan +func Gt[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return NewValueOperator(sql.Gt, field) +} + +// GreaterThanOrEqualTo +func GtOrEq[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return NewValueOperator(sql.GtOrEq, field) +} + +// Comparison Predicates +// ref: https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE + +// Equivalent to field1 < value < field2 +func Between[T any](field1, field2 orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return newBetweenOperator(sql.Between, field1, field2) +} + +// Equivalent to NOT (field1 < value < field2) +func NotBetween[T any](field1, field2 orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return newBetweenOperator(sql.NotBetween, field1, field2) +} + +func newBetweenOperator[T any](sqlOperator sql.Operator, field1, field2 orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + operator := NewValueOperator(sqlOperator, field1) + return operator.AddOperation(sql.And, field2) +} + +// Boolean Comparison Predicates + +func IsDistinct[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return NewValueOperator(sql.IsDistinct, field) +} + +func IsNotDistinct[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { + return NewValueOperator(sql.IsNotDistinct, field) +} diff --git a/orm/errors.go b/orm/errors.go index 512ef69d..e6cda9d9 100644 --- a/orm/errors.go +++ b/orm/errors.go @@ -7,6 +7,33 @@ import ( "github.com/ditrit/badaas/orm/sql" ) +// operators + +var ( + ErrFieldModelNotConcerned = errors.New("field's model is not concerned by the query (not joined)") + ErrJoinMustBeSelected = errors.New("field's model is joined more than once, select which one you want to use with SelectJoin") +) + +func OperatorError(err error, sqlOperator sql.Operator) error { + return fmt.Errorf("%w; operator: %s", err, sqlOperator.Name()) +} + +func fieldModelNotConcernedError(field iFieldIdentifier, sqlOperator sql.Operator) error { + return OperatorError(fmt.Errorf("%w; not concerned model: %s", + ErrFieldModelNotConcerned, + field.GetModelType(), + ), sqlOperator) +} + +func joinMustBeSelectedError(field iFieldIdentifier, sqlOperator sql.Operator) error { + return OperatorError(fmt.Errorf("%w; joined multiple times model: %s", + ErrJoinMustBeSelected, + field.GetModelType(), + ), sqlOperator) +} + +// conditions + var ( ErrEmptyConditions = errors.New("condition must have at least one inner condition") ErrOnlyPreloadsAllowed = errors.New("only conditions that do a preload are allowed") diff --git a/orm/operator.go b/orm/operator.go index 1f37d7cf..7acd984c 100644 --- a/orm/operator.go +++ b/orm/operator.go @@ -9,7 +9,7 @@ import ( type Operator[T any] interface { // Transform the Operator to a SQL string and a list of values to use in the query // columnName is used by the operator to determine which is the objective column. - ToSQL(columnName string) (string, []any, error) + ToSQL(query *Query, columnName string) (string, []any, error) // This method is necessary to get the compiler to verify // that an object is of type Operator[T], @@ -18,6 +18,18 @@ type Operator[T any] interface { InterfaceVerificationMethod(T) } +type DynamicOperator[T any] interface { + Operator[T] + + // Allows to choose which number of join use + // for the value in position "valueNumber" + // when the value is a field and its model is joined more than once. + // Does nothing if the valueNumber is bigger than the amount of values. + SelectJoin(valueNumber, joinNumber uint) DynamicOperator[T] +} + +const undefinedJoinNumber = -1 + // Operator that compares the value of the column against a fixed value // If Operations has multiple entries, operations will be nested // Example (single): value = v1 @@ -29,6 +41,7 @@ type ValueOperator[T any] struct { type operation struct { SQLOperator sql.Operator Value any + JoinNumber int } func (operator ValueOperator[T]) InterfaceVerificationMethod(_ T) { @@ -36,20 +49,72 @@ func (operator ValueOperator[T]) InterfaceVerificationMethod(_ T) { // that an object is of type Operator[T] } -func (operator ValueOperator[T]) ToSQL(columnName string) (string, []any, error) { - operatorString := columnName +// Allows to choose which number of join use +// for the operation in position "operationNumber" +// when the value is a field and its model is joined more than once. +// Does nothing if the operationNumber is bigger than the amount of operations. +func (operator *ValueOperator[T]) SelectJoin(operationNumber, joinNumber uint) DynamicOperator[T] { + if operationNumber >= uint(len(operator.Operations)) { + return operator + } + + operationSaved := operator.Operations[operationNumber] + operationSaved.JoinNumber = int(joinNumber) + operator.Operations[operationNumber] = operationSaved + + return operator +} + +func (operator ValueOperator[T]) ToSQL(query *Query, columnName string) (string, []any, error) { + operationString := columnName values := []any{} + // add each operation to the sql for _, operation := range operator.Operations { - operatorString += " " + operation.SQLOperator.String() + " ?" - values = append(values, operation.Value) + field, isField := operation.Value.(iFieldIdentifier) + if isField { + // if the value of the operation is a field, + // verify that this field is concerned by the query + // (a join was performed with the model to which this field belongs) + // and get the alias of the table of this model. + modelTable, err := getModelTable(query, field, operation.JoinNumber, operation.SQLOperator) + if err != nil { + return "", nil, err + } + + operationString += fmt.Sprintf( + " %s %s", + operation.SQLOperator, + field.ColumnSQL(query, modelTable), + ) + } else { + operationString += " " + operation.SQLOperator.String() + " ?" + values = append(values, operation.Value) + } + } + + return operationString, values, nil +} + +func getModelTable(query *Query, field iFieldIdentifier, joinNumber int, sqlOperator sql.Operator) (Table, error) { + modelTables := query.GetTables(field.GetModelType()) + if modelTables == nil { + return Table{}, fieldModelNotConcernedError(field, sqlOperator) + } + + if len(modelTables) == 1 { + return modelTables[0], nil + } + + if joinNumber == undefinedJoinNumber { + return Table{}, joinMustBeSelectedError(field, sqlOperator) } - return operatorString, values, nil + return modelTables[joinNumber], nil } -func NewValueOperator[T any](sqlOperator sql.Operator, value any) ValueOperator[T] { - return *new(ValueOperator[T]).AddOperation(sqlOperator, value) +func NewValueOperator[T any](sqlOperator sql.Operator, value any) *ValueOperator[T] { + return new(ValueOperator[T]).AddOperation(sqlOperator, value) } func (operator *ValueOperator[T]) AddOperation(sqlOperator sql.Operator, value any) *ValueOperator[T] { @@ -58,6 +123,7 @@ func (operator *ValueOperator[T]) AddOperation(sqlOperator sql.Operator, value a operation{ Value: value, SQLOperator: sqlOperator, + JoinNumber: undefinedJoinNumber, }, ) @@ -75,7 +141,7 @@ func (operator PredicateOperator[T]) InterfaceVerificationMethod(_ T) { // that an object is of type Operator[T] } -func (operator PredicateOperator[T]) ToSQL(columnName string) (string, []any, error) { +func (operator PredicateOperator[T]) ToSQL(_ *Query, columnName string) (string, []any, error) { return fmt.Sprintf("%s %s", columnName, operator.SQLOperator), []any{}, nil } diff --git a/orm/operators.go b/orm/operators.go index 11bbd871..24333420 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -91,21 +91,21 @@ func IsNotUnknown() PredicateOperator[bool] { return NewPredicateOperator[bool]("IS NOT UNKNOWN") } -func IsDistinct[T any](value T) ValueOperator[T] { +func IsDistinct[T any](value T) Operator[T] { return NewValueOperator[T](sql.IsDistinct, value) } -func IsNotDistinct[T any](value T) ValueOperator[T] { +func IsNotDistinct[T any](value T) Operator[T] { return NewValueOperator[T](sql.IsNotDistinct, value) } // Row and Array Comparisons -func ArrayIn[T any](values ...T) ValueOperator[T] { +func ArrayIn[T any](values ...T) Operator[T] { return NewValueOperator[T](sql.ArrayIn, values) } -func ArrayNotIn[T any](values ...T) ValueOperator[T] { +func ArrayNotIn[T any](values ...T) Operator[T] { return NewValueOperator[T](sql.ArrayNotIn, values) } @@ -117,7 +117,7 @@ type LikeOperator struct { func NewLikeOperator(sqlOperator sql.Operator, pattern string) LikeOperator { return LikeOperator{ - ValueOperator: NewValueOperator[string](sqlOperator, pattern), + ValueOperator: *NewValueOperator[string](sqlOperator, pattern), } } diff --git a/orm/query.go b/orm/query.go index 10f3ed07..3ed913ec 100644 --- a/orm/query.go +++ b/orm/query.go @@ -2,6 +2,7 @@ package orm import ( "fmt" + "reflect" "gorm.io/gorm" ) @@ -40,10 +41,11 @@ func (t Table) DeliverTable(query *Query, model Model, relationName string) (Tab } type Query struct { - gormDB *gorm.DB + gormDB *gorm.DB + concernedModels map[reflect.Type][]Table } -func (query *Query) AddSelect(table Table, fieldID FieldIdentifier) { +func (query *Query) AddSelect(table Table, fieldID iFieldIdentifier) { columnName := fieldID.ColumnName(query, table) query.gormDB.Statement.Selects = append( @@ -78,6 +80,25 @@ func (query *Query) Find(dest interface{}, conds ...interface{}) error { return query.gormDB.Error } +func (query *Query) AddConcernedModel(model Model, table Table) { + tableList, isPresent := query.concernedModels[reflect.TypeOf(model)] + if !isPresent { + query.concernedModels[reflect.TypeOf(model)] = []Table{table} + } else { + tableList = append(tableList, table) + query.concernedModels[reflect.TypeOf(model)] = tableList + } +} + +func (query *Query) GetTables(modelType reflect.Type) []Table { + tableList, isPresent := query.concernedModels[modelType] + if !isPresent { + return nil + } + + return tableList +} + func (query Query) ColumnName(table Table, fieldName string) string { return query.gormDB.NamingStrategy.ColumnName(table.Name, fieldName) } @@ -97,8 +118,10 @@ func NewQuery[T Model](db *gorm.DB, conditions []Condition[T]) (*Query, error) { } query := &Query{ - gormDB: db.Select(initialTableName + ".*"), + gormDB: db.Select(initialTableName + ".*"), + concernedModels: map[reflect.Type][]Table{}, } + query.AddConcernedModel(model, initialTable) for _, condition := range conditions { err = condition.ApplyTo(query, initialTable) diff --git a/persistence/models/User.go b/persistence/models/User.go index 3a588a42..4eb76d51 100644 --- a/persistence/models/User.go +++ b/persistence/models/User.go @@ -14,7 +14,7 @@ type User struct { func UserEmailCondition(operator orm.Operator[string]) orm.WhereCondition[User] { return orm.FieldCondition[User, string]{ - FieldIdentifier: orm.FieldIdentifier{ + FieldIdentifier: orm.FieldIdentifier[string]{ Field: "Email", }, Operator: operator, diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go index d8e7fa9a..876f9250 100644 --- a/testintegration/conditions/bicycle_conditions.go +++ b/testintegration/conditions/bicycle_conditions.go @@ -4,39 +4,67 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var bicycleType = reflect.TypeOf(*new(models.Bicycle)) +var BicycleIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: bicycleType, +} + func BicycleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: BicycleIdField, Operator: operator, } } + +var BicycleCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: bicycleType, +} + func BicycleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: BicycleCreatedAtField, Operator: operator, } } + +var BicycleUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: bicycleType, +} + func BicycleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: BicycleUpdatedAtField, Operator: operator, } } + +var BicycleDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: bicycleType, +} + func BicycleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: BicycleDeletedAtField, Operator: operator, } } -var bicycleNameFieldID = orm.FieldIdentifier{Field: "Name"} +var BicycleNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: bicycleType, +} func BicycleName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, string]{ - FieldIdentifier: bicycleNameFieldID, + FieldIdentifier: BicycleNameField, Operator: operator, } } @@ -51,14 +79,17 @@ func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.IJoinCondition } var BicyclePreloadOwner = BicycleOwner(PersonPreloadAttributes) -var bicycleOwnerNameFieldID = orm.FieldIdentifier{Field: "OwnerName"} +var BicycleOwnerNameField = orm.FieldIdentifier[string]{ + Field: "OwnerName", + ModelType: bicycleType, +} func BicycleOwnerName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { return orm.FieldCondition[models.Bicycle, string]{ - FieldIdentifier: bicycleOwnerNameFieldID, + FieldIdentifier: BicycleOwnerNameField, Operator: operator, } } -var BicyclePreloadAttributes = orm.NewPreloadCondition[models.Bicycle](bicycleNameFieldID, bicycleOwnerNameFieldID) +var BicyclePreloadAttributes = orm.NewPreloadCondition[models.Bicycle](BicycleIdField, BicycleCreatedAtField, BicycleUpdatedAtField, BicycleDeletedAtField, BicycleNameField, BicycleOwnerNameField) var BicyclePreloadRelations = []orm.Condition[models.Bicycle]{BicyclePreloadOwner} diff --git a/testintegration/conditions/brand_conditions.go b/testintegration/conditions/brand_conditions.go index 38238144..3b9518b2 100644 --- a/testintegration/conditions/brand_conditions.go +++ b/testintegration/conditions/brand_conditions.go @@ -4,41 +4,69 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var brandType = reflect.TypeOf(*new(models.Brand)) +var BrandIdField = orm.FieldIdentifier[orm.UIntID]{ + Field: "ID", + ModelType: brandType, +} + func BrandId(operator orm.Operator[orm.UIntID]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, orm.UIntID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: BrandIdField, Operator: operator, } } + +var BrandCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: brandType, +} + func BrandCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: BrandCreatedAtField, Operator: operator, } } + +var BrandUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: brandType, +} + func BrandUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: BrandUpdatedAtField, Operator: operator, } } + +var BrandDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: brandType, +} + func BrandDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: BrandDeletedAtField, Operator: operator, } } -var brandNameFieldID = orm.FieldIdentifier{Field: "Name"} +var BrandNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: brandType, +} func BrandName(operator orm.Operator[string]) orm.WhereCondition[models.Brand] { return orm.FieldCondition[models.Brand, string]{ - FieldIdentifier: brandNameFieldID, + FieldIdentifier: BrandNameField, Operator: operator, } } -var BrandPreloadAttributes = orm.NewPreloadCondition[models.Brand](brandNameFieldID) +var BrandPreloadAttributes = orm.NewPreloadCondition[models.Brand](BrandIdField, BrandCreatedAtField, BrandUpdatedAtField, BrandDeletedAtField, BrandNameField) diff --git a/testintegration/conditions/child_conditions.go b/testintegration/conditions/child_conditions.go index 8b856349..2dc0a23e 100644 --- a/testintegration/conditions/child_conditions.go +++ b/testintegration/conditions/child_conditions.go @@ -4,31 +4,79 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" + "reflect" "time" ) +var childType = reflect.TypeOf(*new(models.Child)) +var ChildIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: childType, +} + func ChildId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Child] { return orm.FieldCondition[models.Child, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: ChildIdField, Operator: operator, } } + +var ChildCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: childType, +} + func ChildCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Child] { return orm.FieldCondition[models.Child, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: ChildCreatedAtField, Operator: operator, } } + +var ChildUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: childType, +} + func ChildUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Child] { return orm.FieldCondition[models.Child, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: ChildUpdatedAtField, + Operator: operator, + } +} + +var ChildDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: childType, +} + +func ChildDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Child] { + return orm.FieldCondition[models.Child, time.Time]{ + FieldIdentifier: ChildDeletedAtField, Operator: operator, } } -func ChildDeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.Child] { - return orm.FieldCondition[models.Child, gorm.DeletedAt]{ - FieldIdentifier: orm.DeletedAtFieldID, + +var ChildNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: childType, +} + +func ChildName(operator orm.Operator[string]) orm.WhereCondition[models.Child] { + return orm.FieldCondition[models.Child, string]{ + FieldIdentifier: ChildNameField, + Operator: operator, + } +} + +var ChildNumberField = orm.FieldIdentifier[int]{ + Field: "Number", + ModelType: childType, +} + +func ChildNumber(operator orm.Operator[int]) orm.WhereCondition[models.Child] { + return orm.FieldCondition[models.Child, int]{ + FieldIdentifier: ChildNumberField, Operator: operator, } } @@ -43,11 +91,14 @@ func ChildParent1(conditions ...orm.Condition[models.Parent1]) orm.IJoinConditio } var ChildPreloadParent1 = ChildParent1(Parent1PreloadAttributes) -var childParent1IdFieldID = orm.FieldIdentifier{Field: "Parent1ID"} +var ChildParent1IdField = orm.FieldIdentifier[orm.UUID]{ + Field: "Parent1ID", + ModelType: childType, +} func ChildParent1Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Child] { return orm.FieldCondition[models.Child, orm.UUID]{ - FieldIdentifier: childParent1IdFieldID, + FieldIdentifier: ChildParent1IdField, Operator: operator, } } @@ -62,14 +113,17 @@ func ChildParent2(conditions ...orm.Condition[models.Parent2]) orm.IJoinConditio } var ChildPreloadParent2 = ChildParent2(Parent2PreloadAttributes) -var childParent2IdFieldID = orm.FieldIdentifier{Field: "Parent2ID"} +var ChildParent2IdField = orm.FieldIdentifier[orm.UUID]{ + Field: "Parent2ID", + ModelType: childType, +} func ChildParent2Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Child] { return orm.FieldCondition[models.Child, orm.UUID]{ - FieldIdentifier: childParent2IdFieldID, + FieldIdentifier: ChildParent2IdField, Operator: operator, } } -var ChildPreloadAttributes = orm.NewPreloadCondition[models.Child](childParent1IdFieldID, childParent2IdFieldID) +var ChildPreloadAttributes = orm.NewPreloadCondition[models.Child](ChildIdField, ChildCreatedAtField, ChildUpdatedAtField, ChildDeletedAtField, ChildNameField, ChildNumberField, ChildParent1IdField, ChildParent2IdField) var ChildPreloadRelations = []orm.Condition[models.Child]{ChildPreloadParent1, ChildPreloadParent2} diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go index 458b8784..caa99bd0 100644 --- a/testintegration/conditions/city_conditions.go +++ b/testintegration/conditions/city_conditions.go @@ -4,39 +4,67 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var cityType = reflect.TypeOf(*new(models.City)) +var CityIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: cityType, +} + func CityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: CityIdField, Operator: operator, } } + +var CityCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: cityType, +} + func CityCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: CityCreatedAtField, Operator: operator, } } + +var CityUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: cityType, +} + func CityUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: CityUpdatedAtField, Operator: operator, } } + +var CityDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: cityType, +} + func CityDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: CityDeletedAtField, Operator: operator, } } -var cityNameFieldID = orm.FieldIdentifier{Field: "Name"} +var CityNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: cityType, +} func CityName(operator orm.Operator[string]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, string]{ - FieldIdentifier: cityNameFieldID, + FieldIdentifier: CityNameField, Operator: operator, } } @@ -51,14 +79,17 @@ func CityCountry(conditions ...orm.Condition[models.Country]) orm.IJoinCondition } var CityPreloadCountry = CityCountry(CountryPreloadAttributes) -var cityCountryIdFieldID = orm.FieldIdentifier{Field: "CountryID"} +var CityCountryIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "CountryID", + ModelType: cityType, +} func CityCountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { return orm.FieldCondition[models.City, orm.UUID]{ - FieldIdentifier: cityCountryIdFieldID, + FieldIdentifier: CityCountryIdField, Operator: operator, } } -var CityPreloadAttributes = orm.NewPreloadCondition[models.City](cityNameFieldID, cityCountryIdFieldID) +var CityPreloadAttributes = orm.NewPreloadCondition[models.City](CityIdField, CityCreatedAtField, CityUpdatedAtField, CityDeletedAtField, CityNameField, CityCountryIdField) var CityPreloadRelations = []orm.Condition[models.City]{CityPreloadCountry} diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go index 9a2a02d9..b7a09705 100644 --- a/testintegration/conditions/company_conditions.go +++ b/testintegration/conditions/company_conditions.go @@ -4,39 +4,67 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var companyType = reflect.TypeOf(*new(models.Company)) +var CompanyIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: companyType, +} + func CompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: CompanyIdField, Operator: operator, } } + +var CompanyCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: companyType, +} + func CompanyCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: CompanyCreatedAtField, Operator: operator, } } + +var CompanyUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: companyType, +} + func CompanyUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: CompanyUpdatedAtField, Operator: operator, } } + +var CompanyDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: companyType, +} + func CompanyDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: CompanyDeletedAtField, Operator: operator, } } -var companyNameFieldID = orm.FieldIdentifier{Field: "Name"} +var CompanyNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: companyType, +} func CompanyName(operator orm.Operator[string]) orm.WhereCondition[models.Company] { return orm.FieldCondition[models.Company, string]{ - FieldIdentifier: companyNameFieldID, + FieldIdentifier: CompanyNameField, Operator: operator, } } @@ -44,5 +72,5 @@ func CompanyPreloadSellers(nestedPreloads ...orm.IJoinCondition[models.Seller]) return orm.NewCollectionPreloadCondition[models.Company, models.Seller]("Sellers", nestedPreloads) } -var CompanyPreloadAttributes = orm.NewPreloadCondition[models.Company](companyNameFieldID) +var CompanyPreloadAttributes = orm.NewPreloadCondition[models.Company](CompanyIdField, CompanyCreatedAtField, CompanyUpdatedAtField, CompanyDeletedAtField, CompanyNameField) var CompanyPreloadRelations = []orm.Condition[models.Company]{CompanyPreloadSellers()} diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go index 14dcaeeb..49c46df5 100644 --- a/testintegration/conditions/country_conditions.go +++ b/testintegration/conditions/country_conditions.go @@ -4,39 +4,67 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var countryType = reflect.TypeOf(*new(models.Country)) +var CountryIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: countryType, +} + func CountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: CountryIdField, Operator: operator, } } + +var CountryCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: countryType, +} + func CountryCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: CountryCreatedAtField, Operator: operator, } } + +var CountryUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: countryType, +} + func CountryUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: CountryUpdatedAtField, Operator: operator, } } + +var CountryDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: countryType, +} + func CountryDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: CountryDeletedAtField, Operator: operator, } } -var countryNameFieldID = orm.FieldIdentifier{Field: "Name"} +var CountryNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: countryType, +} func CountryName(operator orm.Operator[string]) orm.WhereCondition[models.Country] { return orm.FieldCondition[models.Country, string]{ - FieldIdentifier: countryNameFieldID, + FieldIdentifier: CountryNameField, Operator: operator, } } @@ -51,5 +79,5 @@ func CountryCapital(conditions ...orm.Condition[models.City]) orm.IJoinCondition } var CountryPreloadCapital = CountryCapital(CityPreloadAttributes) -var CountryPreloadAttributes = orm.NewPreloadCondition[models.Country](countryNameFieldID) +var CountryPreloadAttributes = orm.NewPreloadCondition[models.Country](CountryIdField, CountryCreatedAtField, CountryUpdatedAtField, CountryDeletedAtField, CountryNameField) var CountryPreloadRelations = []orm.Condition[models.Country]{CountryPreloadCapital} diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go index b2f9f0d4..5e116eeb 100644 --- a/testintegration/conditions/employee_conditions.go +++ b/testintegration/conditions/employee_conditions.go @@ -4,39 +4,67 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var employeeType = reflect.TypeOf(*new(models.Employee)) +var EmployeeIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: employeeType, +} + func EmployeeId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: EmployeeIdField, Operator: operator, } } + +var EmployeeCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: employeeType, +} + func EmployeeCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: EmployeeCreatedAtField, Operator: operator, } } + +var EmployeeUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: employeeType, +} + func EmployeeUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: EmployeeUpdatedAtField, Operator: operator, } } + +var EmployeeDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: employeeType, +} + func EmployeeDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: EmployeeDeletedAtField, Operator: operator, } } -var employeeNameFieldID = orm.FieldIdentifier{Field: "Name"} +var EmployeeNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: employeeType, +} func EmployeeName(operator orm.Operator[string]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, string]{ - FieldIdentifier: employeeNameFieldID, + FieldIdentifier: EmployeeNameField, Operator: operator, } } @@ -51,14 +79,17 @@ func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.IJoinConditi } var EmployeePreloadBoss = EmployeeBoss(EmployeePreloadAttributes) -var employeeBossIdFieldID = orm.FieldIdentifier{Field: "BossID"} +var EmployeeBossIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "BossID", + ModelType: employeeType, +} func EmployeeBossId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { return orm.FieldCondition[models.Employee, orm.UUID]{ - FieldIdentifier: employeeBossIdFieldID, + FieldIdentifier: EmployeeBossIdField, Operator: operator, } } -var EmployeePreloadAttributes = orm.NewPreloadCondition[models.Employee](employeeNameFieldID, employeeBossIdFieldID) +var EmployeePreloadAttributes = orm.NewPreloadCondition[models.Employee](EmployeeIdField, EmployeeCreatedAtField, EmployeeUpdatedAtField, EmployeeDeletedAtField, EmployeeNameField, EmployeeBossIdField) var EmployeePreloadRelations = []orm.Condition[models.Employee]{EmployeePreloadBoss} diff --git a/testintegration/conditions/parent1_conditions.go b/testintegration/conditions/parent1_conditions.go index 6907024e..51343e1c 100644 --- a/testintegration/conditions/parent1_conditions.go +++ b/testintegration/conditions/parent1_conditions.go @@ -4,31 +4,55 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" + "reflect" "time" ) +var parent1Type = reflect.TypeOf(*new(models.Parent1)) +var Parent1IdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: parent1Type, +} + func Parent1Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent1] { return orm.FieldCondition[models.Parent1, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: Parent1IdField, Operator: operator, } } + +var Parent1CreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: parent1Type, +} + func Parent1CreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent1] { return orm.FieldCondition[models.Parent1, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: Parent1CreatedAtField, Operator: operator, } } + +var Parent1UpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: parent1Type, +} + func Parent1UpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent1] { return orm.FieldCondition[models.Parent1, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: Parent1UpdatedAtField, Operator: operator, } } -func Parent1DeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.Parent1] { - return orm.FieldCondition[models.Parent1, gorm.DeletedAt]{ - FieldIdentifier: orm.DeletedAtFieldID, + +var Parent1DeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: parent1Type, +} + +func Parent1DeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent1] { + return orm.FieldCondition[models.Parent1, time.Time]{ + FieldIdentifier: Parent1DeletedAtField, Operator: operator, } } @@ -43,14 +67,17 @@ func Parent1ParentParent(conditions ...orm.Condition[models.ParentParent]) orm.I } var Parent1PreloadParentParent = Parent1ParentParent(ParentParentPreloadAttributes) -var parent1ParentParentIdFieldID = orm.FieldIdentifier{Field: "ParentParentID"} +var Parent1ParentParentIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ParentParentID", + ModelType: parent1Type, +} func Parent1ParentParentId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent1] { return orm.FieldCondition[models.Parent1, orm.UUID]{ - FieldIdentifier: parent1ParentParentIdFieldID, + FieldIdentifier: Parent1ParentParentIdField, Operator: operator, } } -var Parent1PreloadAttributes = orm.NewPreloadCondition[models.Parent1](parent1ParentParentIdFieldID) +var Parent1PreloadAttributes = orm.NewPreloadCondition[models.Parent1](Parent1IdField, Parent1CreatedAtField, Parent1UpdatedAtField, Parent1DeletedAtField, Parent1ParentParentIdField) var Parent1PreloadRelations = []orm.Condition[models.Parent1]{Parent1PreloadParentParent} diff --git a/testintegration/conditions/parent2_conditions.go b/testintegration/conditions/parent2_conditions.go index 96ed18dd..df4df26a 100644 --- a/testintegration/conditions/parent2_conditions.go +++ b/testintegration/conditions/parent2_conditions.go @@ -4,31 +4,55 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" + "reflect" "time" ) +var parent2Type = reflect.TypeOf(*new(models.Parent2)) +var Parent2IdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: parent2Type, +} + func Parent2Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent2] { return orm.FieldCondition[models.Parent2, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: Parent2IdField, Operator: operator, } } + +var Parent2CreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: parent2Type, +} + func Parent2CreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent2] { return orm.FieldCondition[models.Parent2, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: Parent2CreatedAtField, Operator: operator, } } + +var Parent2UpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: parent2Type, +} + func Parent2UpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent2] { return orm.FieldCondition[models.Parent2, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: Parent2UpdatedAtField, Operator: operator, } } -func Parent2DeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.Parent2] { - return orm.FieldCondition[models.Parent2, gorm.DeletedAt]{ - FieldIdentifier: orm.DeletedAtFieldID, + +var Parent2DeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: parent2Type, +} + +func Parent2DeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent2] { + return orm.FieldCondition[models.Parent2, time.Time]{ + FieldIdentifier: Parent2DeletedAtField, Operator: operator, } } @@ -43,14 +67,17 @@ func Parent2ParentParent(conditions ...orm.Condition[models.ParentParent]) orm.I } var Parent2PreloadParentParent = Parent2ParentParent(ParentParentPreloadAttributes) -var parent2ParentParentIdFieldID = orm.FieldIdentifier{Field: "ParentParentID"} +var Parent2ParentParentIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ParentParentID", + ModelType: parent2Type, +} func Parent2ParentParentId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent2] { return orm.FieldCondition[models.Parent2, orm.UUID]{ - FieldIdentifier: parent2ParentParentIdFieldID, + FieldIdentifier: Parent2ParentParentIdField, Operator: operator, } } -var Parent2PreloadAttributes = orm.NewPreloadCondition[models.Parent2](parent2ParentParentIdFieldID) +var Parent2PreloadAttributes = orm.NewPreloadCondition[models.Parent2](Parent2IdField, Parent2CreatedAtField, Parent2UpdatedAtField, Parent2DeletedAtField, Parent2ParentParentIdField) var Parent2PreloadRelations = []orm.Condition[models.Parent2]{Parent2PreloadParentParent} diff --git a/testintegration/conditions/parent_parent_conditions.go b/testintegration/conditions/parent_parent_conditions.go index 613881cc..00d48641 100644 --- a/testintegration/conditions/parent_parent_conditions.go +++ b/testintegration/conditions/parent_parent_conditions.go @@ -4,42 +4,81 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" + "reflect" "time" ) +var parentParentType = reflect.TypeOf(*new(models.ParentParent)) +var ParentParentIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: parentParentType, +} + func ParentParentId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.ParentParent] { return orm.FieldCondition[models.ParentParent, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: ParentParentIdField, Operator: operator, } } + +var ParentParentCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: parentParentType, +} + func ParentParentCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.ParentParent] { return orm.FieldCondition[models.ParentParent, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: ParentParentCreatedAtField, Operator: operator, } } + +var ParentParentUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: parentParentType, +} + func ParentParentUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.ParentParent] { return orm.FieldCondition[models.ParentParent, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: ParentParentUpdatedAtField, Operator: operator, } } -func ParentParentDeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.ParentParent] { - return orm.FieldCondition[models.ParentParent, gorm.DeletedAt]{ - FieldIdentifier: orm.DeletedAtFieldID, + +var ParentParentDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: parentParentType, +} + +func ParentParentDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.ParentParent] { + return orm.FieldCondition[models.ParentParent, time.Time]{ + FieldIdentifier: ParentParentDeletedAtField, Operator: operator, } } -var parentParentNameFieldID = orm.FieldIdentifier{Field: "Name"} +var ParentParentNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: parentParentType, +} func ParentParentName(operator orm.Operator[string]) orm.WhereCondition[models.ParentParent] { return orm.FieldCondition[models.ParentParent, string]{ - FieldIdentifier: parentParentNameFieldID, + FieldIdentifier: ParentParentNameField, + Operator: operator, + } +} + +var ParentParentNumberField = orm.FieldIdentifier[int]{ + Field: "Number", + ModelType: parentParentType, +} + +func ParentParentNumber(operator orm.Operator[int]) orm.WhereCondition[models.ParentParent] { + return orm.FieldCondition[models.ParentParent, int]{ + FieldIdentifier: ParentParentNumberField, Operator: operator, } } -var ParentParentPreloadAttributes = orm.NewPreloadCondition[models.ParentParent](parentParentNameFieldID) +var ParentParentPreloadAttributes = orm.NewPreloadCondition[models.ParentParent](ParentParentIdField, ParentParentCreatedAtField, ParentParentUpdatedAtField, ParentParentDeletedAtField, ParentParentNameField, ParentParentNumberField) diff --git a/testintegration/conditions/person_conditions.go b/testintegration/conditions/person_conditions.go index 6a674425..d43a16a7 100644 --- a/testintegration/conditions/person_conditions.go +++ b/testintegration/conditions/person_conditions.go @@ -4,41 +4,69 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var personType = reflect.TypeOf(*new(models.Person)) +var PersonIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: personType, +} + func PersonId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: PersonIdField, Operator: operator, } } + +var PersonCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: personType, +} + func PersonCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: PersonCreatedAtField, Operator: operator, } } + +var PersonUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: personType, +} + func PersonUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: PersonUpdatedAtField, Operator: operator, } } + +var PersonDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: personType, +} + func PersonDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: PersonDeletedAtField, Operator: operator, } } -var personNameFieldID = orm.FieldIdentifier{Field: "Name"} +var PersonNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: personType, +} func PersonName(operator orm.Operator[string]) orm.WhereCondition[models.Person] { return orm.FieldCondition[models.Person, string]{ - FieldIdentifier: personNameFieldID, + FieldIdentifier: PersonNameField, Operator: operator, } } -var PersonPreloadAttributes = orm.NewPreloadCondition[models.Person](personNameFieldID) +var PersonPreloadAttributes = orm.NewPreloadCondition[models.Person](PersonIdField, PersonCreatedAtField, PersonUpdatedAtField, PersonDeletedAtField, PersonNameField) diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go index 93dac6ab..976a3c3a 100644 --- a/testintegration/conditions/phone_conditions.go +++ b/testintegration/conditions/phone_conditions.go @@ -4,39 +4,67 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var phoneType = reflect.TypeOf(*new(models.Phone)) +var PhoneIdField = orm.FieldIdentifier[orm.UIntID]{ + Field: "ID", + ModelType: phoneType, +} + func PhoneId(operator orm.Operator[orm.UIntID]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, orm.UIntID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: PhoneIdField, Operator: operator, } } + +var PhoneCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: phoneType, +} + func PhoneCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: PhoneCreatedAtField, Operator: operator, } } + +var PhoneUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: phoneType, +} + func PhoneUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: PhoneUpdatedAtField, Operator: operator, } } + +var PhoneDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: phoneType, +} + func PhoneDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: PhoneDeletedAtField, Operator: operator, } } -var phoneNameFieldID = orm.FieldIdentifier{Field: "Name"} +var PhoneNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: phoneType, +} func PhoneName(operator orm.Operator[string]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, string]{ - FieldIdentifier: phoneNameFieldID, + FieldIdentifier: PhoneNameField, Operator: operator, } } @@ -51,14 +79,17 @@ func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.IJoinCondition[mo } var PhonePreloadBrand = PhoneBrand(BrandPreloadAttributes) -var phoneBrandIdFieldID = orm.FieldIdentifier{Field: "BrandID"} +var PhoneBrandIdField = orm.FieldIdentifier[uint]{ + Field: "BrandID", + ModelType: phoneType, +} func PhoneBrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { return orm.FieldCondition[models.Phone, uint]{ - FieldIdentifier: phoneBrandIdFieldID, + FieldIdentifier: PhoneBrandIdField, Operator: operator, } } -var PhonePreloadAttributes = orm.NewPreloadCondition[models.Phone](phoneNameFieldID, phoneBrandIdFieldID) +var PhonePreloadAttributes = orm.NewPreloadCondition[models.Phone](PhoneIdField, PhoneCreatedAtField, PhoneUpdatedAtField, PhoneDeletedAtField, PhoneNameField, PhoneBrandIdField) var PhonePreloadRelations = []orm.Condition[models.Phone]{PhonePreloadBrand} diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go index cb794a50..6ec05899 100644 --- a/testintegration/conditions/product_conditions.go +++ b/testintegration/conditions/product_conditions.go @@ -4,134 +4,191 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var productType = reflect.TypeOf(*new(models.Product)) +var ProductIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: productType, +} + func ProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: ProductIdField, Operator: operator, } } + +var ProductCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: productType, +} + func ProductCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: ProductCreatedAtField, Operator: operator, } } + +var ProductUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: productType, +} + func ProductUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: ProductUpdatedAtField, Operator: operator, } } + +var ProductDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: productType, +} + func ProductDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: ProductDeletedAtField, Operator: operator, } } -var productStringFieldID = orm.FieldIdentifier{Column: "string_something_else"} +var ProductStringField = orm.FieldIdentifier[string]{ + Column: "string_something_else", + Field: "String", + ModelType: productType, +} func ProductString(operator orm.Operator[string]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, string]{ - FieldIdentifier: productStringFieldID, + FieldIdentifier: ProductStringField, Operator: operator, } } -var productIntFieldID = orm.FieldIdentifier{Field: "Int"} +var ProductIntField = orm.FieldIdentifier[int]{ + Field: "Int", + ModelType: productType, +} func ProductInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, int]{ - FieldIdentifier: productIntFieldID, + FieldIdentifier: ProductIntField, Operator: operator, } } -var productIntPointerFieldID = orm.FieldIdentifier{Field: "IntPointer"} +var ProductIntPointerField = orm.FieldIdentifier[int]{ + Field: "IntPointer", + ModelType: productType, +} func ProductIntPointer(operator orm.Operator[int]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, int]{ - FieldIdentifier: productIntPointerFieldID, + FieldIdentifier: ProductIntPointerField, Operator: operator, } } -var productFloatFieldID = orm.FieldIdentifier{Field: "Float"} +var ProductFloatField = orm.FieldIdentifier[float64]{ + Field: "Float", + ModelType: productType, +} func ProductFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, float64]{ - FieldIdentifier: productFloatFieldID, + FieldIdentifier: ProductFloatField, Operator: operator, } } -var productNullFloatFieldID = orm.FieldIdentifier{Field: "NullFloat"} +var ProductNullFloatField = orm.FieldIdentifier[float64]{ + Field: "NullFloat", + ModelType: productType, +} func ProductNullFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, float64]{ - FieldIdentifier: productNullFloatFieldID, + FieldIdentifier: ProductNullFloatField, Operator: operator, } } -var productBoolFieldID = orm.FieldIdentifier{Field: "Bool"} +var ProductBoolField = orm.FieldIdentifier[bool]{ + Field: "Bool", + ModelType: productType, +} func ProductBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, bool]{ - FieldIdentifier: productBoolFieldID, + FieldIdentifier: ProductBoolField, Operator: operator, } } -var productNullBoolFieldID = orm.FieldIdentifier{Field: "NullBool"} +var ProductNullBoolField = orm.FieldIdentifier[bool]{ + Field: "NullBool", + ModelType: productType, +} func ProductNullBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, bool]{ - FieldIdentifier: productNullBoolFieldID, + FieldIdentifier: ProductNullBoolField, Operator: operator, } } -var productByteArrayFieldID = orm.FieldIdentifier{Field: "ByteArray"} +var ProductByteArrayField = orm.FieldIdentifier[[]uint8]{ + Field: "ByteArray", + ModelType: productType, +} func ProductByteArray(operator orm.Operator[[]uint8]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, []uint8]{ - FieldIdentifier: productByteArrayFieldID, + FieldIdentifier: ProductByteArrayField, Operator: operator, } } -var productMultiStringFieldID = orm.FieldIdentifier{Field: "MultiString"} +var ProductMultiStringField = orm.FieldIdentifier[models.MultiString]{ + Field: "MultiString", + ModelType: productType, +} func ProductMultiString(operator orm.Operator[models.MultiString]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, models.MultiString]{ - FieldIdentifier: productMultiStringFieldID, + FieldIdentifier: ProductMultiStringField, Operator: operator, } } -var productToBeEmbeddedEmbeddedIntFieldID = orm.FieldIdentifier{Field: "EmbeddedInt"} +var ProductToBeEmbeddedEmbeddedIntField = orm.FieldIdentifier[int]{ + Field: "EmbeddedInt", + ModelType: productType, +} func ProductToBeEmbeddedEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, int]{ - FieldIdentifier: productToBeEmbeddedEmbeddedIntFieldID, + FieldIdentifier: ProductToBeEmbeddedEmbeddedIntField, Operator: operator, } } -var productGormEmbeddedIntFieldID = orm.FieldIdentifier{ +var ProductGormEmbeddedIntField = orm.FieldIdentifier[int]{ ColumnPrefix: "gorm_embedded_", Field: "Int", + ModelType: productType, } func ProductGormEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { return orm.FieldCondition[models.Product, int]{ - FieldIdentifier: productGormEmbeddedIntFieldID, + FieldIdentifier: ProductGormEmbeddedIntField, Operator: operator, } } -var ProductPreloadAttributes = orm.NewPreloadCondition[models.Product](productStringFieldID, productIntFieldID, productIntPointerFieldID, productFloatFieldID, productNullFloatFieldID, productBoolFieldID, productNullBoolFieldID, productByteArrayFieldID, productMultiStringFieldID, productToBeEmbeddedEmbeddedIntFieldID, productGormEmbeddedIntFieldID) +var ProductPreloadAttributes = orm.NewPreloadCondition[models.Product](ProductIdField, ProductCreatedAtField, ProductUpdatedAtField, ProductDeletedAtField, ProductStringField, ProductIntField, ProductIntPointerField, ProductFloatField, ProductNullFloatField, ProductBoolField, ProductNullBoolField, ProductByteArrayField, ProductMultiStringField, ProductToBeEmbeddedEmbeddedIntField, ProductGormEmbeddedIntField) diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go index 5fb11c46..6ab2984c 100644 --- a/testintegration/conditions/sale_conditions.go +++ b/testintegration/conditions/sale_conditions.go @@ -4,48 +4,79 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var saleType = reflect.TypeOf(*new(models.Sale)) +var SaleIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: saleType, +} + func SaleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: SaleIdField, Operator: operator, } } + +var SaleCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: saleType, +} + func SaleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: SaleCreatedAtField, Operator: operator, } } + +var SaleUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: saleType, +} + func SaleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: SaleUpdatedAtField, Operator: operator, } } + +var SaleDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: saleType, +} + func SaleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: SaleDeletedAtField, Operator: operator, } } -var saleCodeFieldID = orm.FieldIdentifier{Field: "Code"} +var SaleCodeField = orm.FieldIdentifier[int]{ + Field: "Code", + ModelType: saleType, +} func SaleCode(operator orm.Operator[int]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, int]{ - FieldIdentifier: saleCodeFieldID, + FieldIdentifier: SaleCodeField, Operator: operator, } } -var saleDescriptionFieldID = orm.FieldIdentifier{Field: "Description"} +var SaleDescriptionField = orm.FieldIdentifier[string]{ + Field: "Description", + ModelType: saleType, +} func SaleDescription(operator orm.Operator[string]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, string]{ - FieldIdentifier: saleDescriptionFieldID, + FieldIdentifier: SaleDescriptionField, Operator: operator, } } @@ -60,11 +91,14 @@ func SaleProduct(conditions ...orm.Condition[models.Product]) orm.IJoinCondition } var SalePreloadProduct = SaleProduct(ProductPreloadAttributes) -var saleProductIdFieldID = orm.FieldIdentifier{Field: "ProductID"} +var SaleProductIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ProductID", + ModelType: saleType, +} func SaleProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, orm.UUID]{ - FieldIdentifier: saleProductIdFieldID, + FieldIdentifier: SaleProductIdField, Operator: operator, } } @@ -79,14 +113,17 @@ func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.IJoinCondition[m } var SalePreloadSeller = SaleSeller(SellerPreloadAttributes) -var saleSellerIdFieldID = orm.FieldIdentifier{Field: "SellerID"} +var SaleSellerIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "SellerID", + ModelType: saleType, +} func SaleSellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { return orm.FieldCondition[models.Sale, orm.UUID]{ - FieldIdentifier: saleSellerIdFieldID, + FieldIdentifier: SaleSellerIdField, Operator: operator, } } -var SalePreloadAttributes = orm.NewPreloadCondition[models.Sale](saleCodeFieldID, saleDescriptionFieldID, saleProductIdFieldID, saleSellerIdFieldID) +var SalePreloadAttributes = orm.NewPreloadCondition[models.Sale](SaleIdField, SaleCreatedAtField, SaleUpdatedAtField, SaleDeletedAtField, SaleCodeField, SaleDescriptionField, SaleProductIdField, SaleSellerIdField) var SalePreloadRelations = []orm.Condition[models.Sale]{SalePreloadProduct, SalePreloadSeller} diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go index c206432c..611ca23a 100644 --- a/testintegration/conditions/seller_conditions.go +++ b/testintegration/conditions/seller_conditions.go @@ -4,39 +4,67 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" + "reflect" "time" ) +var sellerType = reflect.TypeOf(*new(models.Seller)) +var SellerIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: sellerType, +} + func SellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: SellerIdField, Operator: operator, } } + +var SellerCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: sellerType, +} + func SellerCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: SellerCreatedAtField, Operator: operator, } } + +var SellerUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: sellerType, +} + func SellerUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: SellerUpdatedAtField, Operator: operator, } } + +var SellerDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: sellerType, +} + func SellerDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, time.Time]{ - FieldIdentifier: orm.DeletedAtFieldID, + FieldIdentifier: SellerDeletedAtField, Operator: operator, } } -var sellerNameFieldID = orm.FieldIdentifier{Field: "Name"} +var SellerNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: sellerType, +} func SellerName(operator orm.Operator[string]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, string]{ - FieldIdentifier: sellerNameFieldID, + FieldIdentifier: SellerNameField, Operator: operator, } } @@ -51,11 +79,14 @@ func SellerCompany(conditions ...orm.Condition[models.Company]) orm.IJoinConditi } var SellerPreloadCompany = SellerCompany(CompanyPreloadAttributes) -var sellerCompanyIdFieldID = orm.FieldIdentifier{Field: "CompanyID"} +var SellerCompanyIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "CompanyID", + ModelType: sellerType, +} func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, orm.UUID]{ - FieldIdentifier: sellerCompanyIdFieldID, + FieldIdentifier: SellerCompanyIdField, Operator: operator, } } @@ -70,14 +101,17 @@ func SellerUniversity(conditions ...orm.Condition[models.University]) orm.IJoinC } var SellerPreloadUniversity = SellerUniversity(UniversityPreloadAttributes) -var sellerUniversityIdFieldID = orm.FieldIdentifier{Field: "UniversityID"} +var SellerUniversityIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "UniversityID", + ModelType: sellerType, +} func SellerUniversityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { return orm.FieldCondition[models.Seller, orm.UUID]{ - FieldIdentifier: sellerUniversityIdFieldID, + FieldIdentifier: SellerUniversityIdField, Operator: operator, } } -var SellerPreloadAttributes = orm.NewPreloadCondition[models.Seller](sellerNameFieldID, sellerCompanyIdFieldID, sellerUniversityIdFieldID) +var SellerPreloadAttributes = orm.NewPreloadCondition[models.Seller](SellerIdField, SellerCreatedAtField, SellerUpdatedAtField, SellerDeletedAtField, SellerNameField, SellerCompanyIdField, SellerUniversityIdField) var SellerPreloadRelations = []orm.Condition[models.Seller]{SellerPreloadCompany, SellerPreloadUniversity} diff --git a/testintegration/conditions/university_conditions.go b/testintegration/conditions/university_conditions.go index b670940c..fd5255bd 100644 --- a/testintegration/conditions/university_conditions.go +++ b/testintegration/conditions/university_conditions.go @@ -4,42 +4,69 @@ package conditions import ( orm "github.com/ditrit/badaas/orm" models "github.com/ditrit/badaas/testintegration/models" - gorm "gorm.io/gorm" + "reflect" "time" ) +var universityType = reflect.TypeOf(*new(models.University)) +var UniversityIdField = orm.FieldIdentifier[orm.UUID]{ + Field: "ID", + ModelType: universityType, +} + func UniversityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.University] { return orm.FieldCondition[models.University, orm.UUID]{ - FieldIdentifier: orm.IDFieldID, + FieldIdentifier: UniversityIdField, Operator: operator, } } + +var UniversityCreatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "CreatedAt", + ModelType: universityType, +} + func UniversityCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.University] { return orm.FieldCondition[models.University, time.Time]{ - FieldIdentifier: orm.CreatedAtFieldID, + FieldIdentifier: UniversityCreatedAtField, Operator: operator, } } + +var UniversityUpdatedAtField = orm.FieldIdentifier[time.Time]{ + Field: "UpdatedAt", + ModelType: universityType, +} + func UniversityUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.University] { return orm.FieldCondition[models.University, time.Time]{ - FieldIdentifier: orm.UpdatedAtFieldID, + FieldIdentifier: UniversityUpdatedAtField, Operator: operator, } } -func UniversityDeletedAt(operator orm.Operator[gorm.DeletedAt]) orm.WhereCondition[models.University] { - return orm.FieldCondition[models.University, gorm.DeletedAt]{ - FieldIdentifier: orm.DeletedAtFieldID, + +var UniversityDeletedAtField = orm.FieldIdentifier[time.Time]{ + Field: "DeletedAt", + ModelType: universityType, +} + +func UniversityDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.University] { + return orm.FieldCondition[models.University, time.Time]{ + FieldIdentifier: UniversityDeletedAtField, Operator: operator, } } -var universityNameFieldID = orm.FieldIdentifier{Field: "Name"} +var UniversityNameField = orm.FieldIdentifier[string]{ + Field: "Name", + ModelType: universityType, +} func UniversityName(operator orm.Operator[string]) orm.WhereCondition[models.University] { return orm.FieldCondition[models.University, string]{ - FieldIdentifier: universityNameFieldID, + FieldIdentifier: UniversityNameField, Operator: operator, } } -var UniversityPreloadAttributes = orm.NewPreloadCondition[models.University](universityNameFieldID) +var UniversityPreloadAttributes = orm.NewPreloadCondition[models.University](UniversityIdField, UniversityCreatedAtField, UniversityUpdatedAtField, UniversityDeletedAtField, UniversityNameField) diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index b93e4d69..a2143b3e 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -4,6 +4,7 @@ import ( "gorm.io/gorm" "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/dynamic" "github.com/ditrit/badaas/orm/unsafe" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" @@ -18,6 +19,7 @@ type JoinConditionsIntTestSuite struct { crudEmployeeService orm.CRUDService[models.Employee, orm.UUID] crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID] crudPhoneService orm.CRUDService[models.Phone, orm.UIntID] + crudChildService orm.CRUDService[models.Child, orm.UUID] } func NewJoinConditionsIntTestSuite( @@ -29,6 +31,7 @@ func NewJoinConditionsIntTestSuite( crudEmployeeService orm.CRUDService[models.Employee, orm.UUID], crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID], crudPhoneService orm.CRUDService[models.Phone, orm.UIntID], + crudChildService orm.CRUDService[models.Child, orm.UUID], ) *JoinConditionsIntTestSuite { return &JoinConditionsIntTestSuite{ CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ @@ -41,6 +44,7 @@ func NewJoinConditionsIntTestSuite( crudEmployeeService: crudEmployeeService, crudBicycleService: crudBicycleService, crudPhoneService: crudPhoneService, + crudChildService: crudChildService, } } @@ -406,3 +410,111 @@ func (ts *JoinConditionsIntTestSuite) TestJoinWithEmptyContainerConditionReturns ts.ErrorIs(err, orm.ErrEmptyConditions) ts.ErrorContains(err, "connector: Not; model: models.Product") } + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorOver2Tables() { + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("ditrit", company1) + ts.createSeller("agustin", company2) + + entities, err := ts.crudSellerService.Query( + conditions.SellerCompany( + conditions.CompanyName( + dynamic.Eq(conditions.SellerNameField), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Seller{seller1}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorOver2TablesAtMoreLevel() { + product1 := ts.createProduct("", 0, 0.0, false, nil) + product2 := ts.createProduct("", 0, 0.0, false, nil) + + company1 := ts.createCompany("ditrit") + company2 := ts.createCompany("orness") + + seller1 := ts.createSeller("ditrit", company1) + seller2 := ts.createSeller("agustin", company2) + + match := ts.createSale(0, product1, seller1) + ts.createSale(0, product2, seller2) + + entities, err := ts.crudSaleService.Query( + conditions.SaleSeller( + conditions.SellerCompany( + conditions.CompanyName( + dynamic.Eq(conditions.SellerNameField), + ), + ), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Sale{match}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorWithNotJoinedModelReturnsError() { + _, err := ts.crudChildService.Query( + conditions.ChildId(dynamic.Eq(conditions.ParentParentIdField)), + ) + ts.ErrorIs(err, orm.ErrFieldModelNotConcerned) + ts.ErrorContains(err, "not concerned model: models.ParentParent; operator: Eq; model: models.Child, field: ID") +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorJoinMoreThanOnceWithoutSelectJoinReturnsError() { + _, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1ParentParent(), + ), + conditions.ChildParent2( + conditions.Parent2ParentParent(), + ), + conditions.ChildId(dynamic.Eq(conditions.ParentParentIdField)), + ) + ts.ErrorIs(err, orm.ErrJoinMustBeSelected) + ts.ErrorContains(err, "joined multiple times model: models.ParentParent; operator: Eq; model: models.Child, field: ID") +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorJoinMoreThanOnceWithSelectJoin() { + parentParent := &models.ParentParent{Name: "franco"} + parent1 := &models.Parent1{ParentParent: *parentParent} + parent2 := &models.Parent2{ParentParent: *parentParent} + child := &models.Child{Parent1: *parent1, Parent2: *parent2, Name: "franco"} + err := ts.db.Create(child).Error + ts.Nil(err) + + entities, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1ParentParent(), + ), + conditions.ChildParent2( + conditions.Parent2ParentParent(), + ), + conditions.ChildName( + dynamic.Eq(conditions.ParentParentNameField).SelectJoin(0, 0), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Child{child}, entities) +} + +func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorJoinMoreThanOnceWithoutSelectJoinOnMultivalueOperatorReturnsError() { + _, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1ParentParent(), + ), + conditions.ChildParent2( + conditions.Parent2ParentParent(), + ), + conditions.ChildId( + dynamic.Between(conditions.ParentParentIdField, conditions.ParentParentIdField), + ), + ) + ts.ErrorIs(err, orm.ErrJoinMustBeSelected) + ts.ErrorContains(err, "joined multiple times model: models.ParentParent; operator: Between; model: models.Child, field: ID") +} diff --git a/testintegration/models/models.go b/testintegration/models/models.go index 8b2f91fe..0253a68c 100644 --- a/testintegration/models/models.go +++ b/testintegration/models/models.go @@ -203,7 +203,8 @@ func (m Phone) Equal(other Phone) bool { type ParentParent struct { orm.UUIDModel - Name string + Name string + Number int } func (m ParentParent) Equal(other ParentParent) bool { @@ -235,6 +236,9 @@ func (m Parent2) Equal(other Parent2) bool { type Child struct { orm.UUIDModel + Name string + Number int + Parent1 Parent1 Parent1ID orm.UUID diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index b7d733eb..02cf8c8c 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -6,6 +6,7 @@ import ( "gorm.io/gorm" "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/dynamic" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" ) @@ -467,3 +468,67 @@ func (ts *OperatorsIntTestSuite) TestLikeEscape() { EqualList(&ts.Suite, []*models.Product{match1, match2}, entities) } + +func (ts *OperatorsIntTestSuite) TestDynamicOperatorForBasicType() { + int1 := 1 + product1 := ts.createProduct("", 1, 0.0, false, &int1) + ts.createProduct("", 2, 0.0, false, &int1) + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductInt( + dynamic.Eq(conditions.ProductIntPointerField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{product1}, entities) +} + +func (ts *OperatorsIntTestSuite) TestDynamicOperatorForCustomType() { + match := ts.createProduct("salut,hola", 1, 0.0, false, nil) + match.MultiString = models.MultiString{"salut", "hola"} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("salut,hola", 1, 0.0, false, nil) + ts.createProduct("hola", 1, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductMultiString( + dynamic.Eq(conditions.ProductMultiStringField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestDynamicOperatorForBaseModelAttribute() { + match := ts.createProduct("", 1, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductDeletedAt(dynamic.IsNotDistinct(conditions.ProductDeletedAtField)), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestDynamicOperatorForNotNullTypeCanBeComparedWithNullableType() { + match := ts.createProduct("", 1, 1.0, false, nil) + match.NullFloat = sql.NullFloat64{Valid: true, Float64: 1.0} + err := ts.db.Save(match).Error + ts.Nil(err) + + ts.createProduct("", 1, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + dynamic.Eq[float64](conditions.ProductNullFloatField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} From e5e925ef897d745e7c47d4553e84704aea179558 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Fri, 4 Aug 2023 16:38:14 +0200 Subject: [PATCH 75/90] add unsafe operators --- orm/unsafe/operators.go | 77 +++++++++++++++++++++++++++++++ testintegration/operators_test.go | 69 +++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 orm/unsafe/operators.go diff --git a/orm/unsafe/operators.go b/orm/unsafe/operators.go new file mode 100644 index 00000000..cda4fa2b --- /dev/null +++ b/orm/unsafe/operators.go @@ -0,0 +1,77 @@ +package unsafe + +import ( + "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/sql" +) + +// Comparison Operators +// ref: https://www.postgresql.org/docs/current/functions-comparison.html + +// EqualTo +func Eq[T any](value any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.Eq, value) +} + +// NotEqualTo +func NotEq[T any](value any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.NotEq, value) +} + +// LessThan +func Lt[T any](value any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.Lt, value) +} + +// LessThanOrEqualTo +func LtOrEq[T any](value any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.LtOrEq, value) +} + +// GreaterThan +func Gt[T any](value any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.Gt, value) +} + +// GreaterThanOrEqualTo +func GtOrEq[T any](value any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.GtOrEq, value) +} + +// Comparison Predicates +// ref: https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE + +// Equivalent to v1 < value < v2 +func Between[T any](v1, v2 any) orm.DynamicOperator[T] { + return newBetweenOperator[T](sql.Between, v1, v2) +} + +// Equivalent to NOT (v1 < value < v2) +func NotBetween[T any](v1, v2 any) orm.DynamicOperator[T] { + return newBetweenOperator[T](sql.NotBetween, v1, v2) +} + +func newBetweenOperator[T any](sqlOperator sql.Operator, v1, v2 any) orm.DynamicOperator[T] { + operator := orm.NewValueOperator[T](sqlOperator, v1) + return operator.AddOperation(sql.And, v2) +} + +// Boolean Comparison Predicates + +func IsDistinct[T any](value any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.IsDistinct, value) +} + +func IsNotDistinct[T any](value any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.IsNotDistinct, value) +} + +// Row and Array Comparisons + +func ArrayIn[T any](values ...any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.ArrayIn, values) +} + +func ArrayNotIn[T any](values ...any) orm.DynamicOperator[T] { + return orm.NewValueOperator[T](sql.ArrayNotIn, values) +} diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index 02cf8c8c..d8aaff56 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -2,11 +2,13 @@ package testintegration import ( "database/sql" + "strings" "gorm.io/gorm" "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/orm/dynamic" + "github.com/ditrit/badaas/orm/unsafe" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" ) @@ -532,3 +534,70 @@ func (ts *OperatorsIntTestSuite) TestDynamicOperatorForNotNullTypeCanBeComparedW EqualList(&ts.Suite, []*models.Product{match}, entities) } + +func (ts *OperatorsIntTestSuite) TestUnsafeOperatorInCaseTypesNotMatchConvertible() { + // comparisons between types are allowed when they are convertible + match := ts.createProduct("", 0, 2.1, false, nil) + ts.createProduct("", 0, 0, false, nil) + ts.createProduct("", 0, 2, false, nil) + ts.createProduct("", 0, 2.3, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64]("2.1"), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} + +func (ts *OperatorsIntTestSuite) TestUnsafeOperatorInCaseTypesNotMatchNotConvertible() { + _, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64]("not_convertible_to_float"), + ), + ) + ts.ErrorContains(err, "not_convertible_to_float") +} + +func (ts *OperatorsIntTestSuite) TestUnsafeOperatorInCaseFieldWithTypesNotMatch() { + _, err := ts.crudProductService.Query( + conditions.ProductFloat( + unsafe.Eq[float64](conditions.ProductStringField), + ), + ) + + ts.True( + strings.Contains( + err.Error(), + "ERROR: operator does not exist: numeric = text (SQLSTATE 42883)", // postgresql + ) || strings.Contains( + err.Error(), + "ERROR: unsupported comparison operator: = (SQLSTATE 22023)", // cockroachdb + ), + ) +} + +func (ts *OperatorsIntTestSuite) TestUnsafeOperatorCanCompareFieldsThatMapToTheSameType() { + match := ts.createProduct("hola,chau", 1, 1.0, false, nil) + match.MultiString = models.MultiString{"hola", "chau"} + err := ts.db.Save(match).Error + ts.Nil(err) + + notMatch := ts.createProduct("chau", 0, 0.0, false, nil) + notMatch.MultiString = models.MultiString{"hola", "chau"} + err = ts.db.Save(notMatch).Error + ts.Nil(err) + + ts.createProduct("", 0, 0.0, false, nil) + + entities, err := ts.crudProductService.Query( + conditions.ProductString( + unsafe.Eq[string](conditions.ProductMultiStringField), + ), + ) + ts.Nil(err) + + EqualList(&ts.Suite, []*models.Product{match}, entities) +} From 745f13e84b4bfbe28aa84081c12187fe973681d0 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 13:25:01 +0200 Subject: [PATCH 76/90] update chagelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 6ad700ff..a34454b2 100644 --- a/changelog.md +++ b/changelog.md @@ -34,5 +34,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add badaas-orm with the compilable query system. - Add operators support - Add preloading +- Add dynamic and unsafe operators [unreleased]: https://github.com/ditrit/badaas/blob/main/changelog.md#unreleased From 948a829db59d7750d93579b86c22693e40dfe8b2 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 16:18:06 +0200 Subject: [PATCH 77/90] update mocks --- mocks/orm/Condition.go | 27 +++--------- mocks/orm/DynamicOperator.go | 82 +++++++++++++++++++++++++++++++++++ mocks/orm/IJoinCondition.go | 27 +++--------- mocks/orm/Operator.go | 27 +++++++----- mocks/orm/WhereCondition.go | 65 +++++++++++---------------- mocks/orm/iFieldIdentifier.go | 74 +++++++++++++++++++++++++++++++ 6 files changed, 211 insertions(+), 91 deletions(-) create mode 100644 mocks/orm/DynamicOperator.go create mode 100644 mocks/orm/iFieldIdentifier.go diff --git a/mocks/orm/Condition.go b/mocks/orm/Condition.go index d9775761..9edfd710 100644 --- a/mocks/orm/Condition.go +++ b/mocks/orm/Condition.go @@ -5,7 +5,6 @@ package mocks import ( orm "github.com/ditrit/badaas/orm" mock "github.com/stretchr/testify/mock" - gorm "gorm.io/gorm" ) // Condition is an autogenerated mock type for the Condition type @@ -14,33 +13,21 @@ type Condition[T orm.Model] struct { } // ApplyTo provides a mock function with given fields: query, table -func (_m *Condition[T]) ApplyTo(query *gorm.DB, table orm.Table) (*gorm.DB, error) { +func (_m *Condition[T]) ApplyTo(query *orm.Query, table orm.Table) error { ret := _m.Called(query, table) - var r0 *gorm.DB - var r1 error - if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) (*gorm.DB, error)); ok { - return rf(query, table) - } - if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) *gorm.DB); ok { + var r0 error + if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) error); ok { r0 = rf(query, table) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*gorm.DB) - } - } - - if rf, ok := ret.Get(1).(func(*gorm.DB, orm.Table) error); ok { - r1 = rf(query, table) - } else { - r1 = ret.Error(1) + r0 = ret.Error(0) } - return r0, r1 + return r0 } -// interfaceVerificationMethod provides a mock function with given fields: _a0 -func (_m *Condition[T]) interfaceVerificationMethod(_a0 T) { +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *Condition[T]) InterfaceVerificationMethod(_a0 T) { _m.Called(_a0) } diff --git a/mocks/orm/DynamicOperator.go b/mocks/orm/DynamicOperator.go new file mode 100644 index 00000000..3615106a --- /dev/null +++ b/mocks/orm/DynamicOperator.go @@ -0,0 +1,82 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + orm "github.com/ditrit/badaas/orm" + mock "github.com/stretchr/testify/mock" +) + +// DynamicOperator is an autogenerated mock type for the DynamicOperator type +type DynamicOperator[T interface{}] struct { + mock.Mock +} + +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *DynamicOperator[T]) InterfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +// SelectJoin provides a mock function with given fields: valueNumber, joinNumber +func (_m *DynamicOperator[T]) SelectJoin(valueNumber uint, joinNumber uint) orm.DynamicOperator[T] { + ret := _m.Called(valueNumber, joinNumber) + + var r0 orm.DynamicOperator[T] + if rf, ok := ret.Get(0).(func(uint, uint) orm.DynamicOperator[T]); ok { + r0 = rf(valueNumber, joinNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(orm.DynamicOperator[T]) + } + } + + return r0 +} + +// ToSQL provides a mock function with given fields: query, columnName +func (_m *DynamicOperator[T]) ToSQL(query *orm.Query, columnName string) (string, []interface{}, error) { + ret := _m.Called(query, columnName) + + var r0 string + var r1 []interface{} + var r2 error + if rf, ok := ret.Get(0).(func(*orm.Query, string) (string, []interface{}, error)); ok { + return rf(query, columnName) + } + if rf, ok := ret.Get(0).(func(*orm.Query, string) string); ok { + r0 = rf(query, columnName) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(*orm.Query, string) []interface{}); ok { + r1 = rf(query, columnName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + if rf, ok := ret.Get(2).(func(*orm.Query, string) error); ok { + r2 = rf(query, columnName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type mockConstructorTestingTNewDynamicOperator interface { + mock.TestingT + Cleanup(func()) +} + +// NewDynamicOperator creates a new instance of DynamicOperator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewDynamicOperator[T interface{}](t mockConstructorTestingTNewDynamicOperator) *DynamicOperator[T] { + mock := &DynamicOperator[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/IJoinCondition.go b/mocks/orm/IJoinCondition.go index c1c4411f..e93d30c4 100644 --- a/mocks/orm/IJoinCondition.go +++ b/mocks/orm/IJoinCondition.go @@ -5,7 +5,6 @@ package mocks import ( orm "github.com/ditrit/badaas/orm" mock "github.com/stretchr/testify/mock" - gorm "gorm.io/gorm" ) // IJoinCondition is an autogenerated mock type for the IJoinCondition type @@ -14,33 +13,21 @@ type IJoinCondition[T orm.Model] struct { } // ApplyTo provides a mock function with given fields: query, table -func (_m *IJoinCondition[T]) ApplyTo(query *gorm.DB, table orm.Table) (*gorm.DB, error) { +func (_m *IJoinCondition[T]) ApplyTo(query *orm.Query, table orm.Table) error { ret := _m.Called(query, table) - var r0 *gorm.DB - var r1 error - if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) (*gorm.DB, error)); ok { - return rf(query, table) - } - if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) *gorm.DB); ok { + var r0 error + if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) error); ok { r0 = rf(query, table) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*gorm.DB) - } + r0 = ret.Error(0) } - if rf, ok := ret.Get(1).(func(*gorm.DB, orm.Table) error); ok { - r1 = rf(query, table) - } else { - r1 = ret.Error(1) - } - - return r0, r1 + return r0 } -// interfaceVerificationMethod provides a mock function with given fields: _a0 -func (_m *IJoinCondition[T]) interfaceVerificationMethod(_a0 T) { +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *IJoinCondition[T]) InterfaceVerificationMethod(_a0 T) { _m.Called(_a0) } diff --git a/mocks/orm/Operator.go b/mocks/orm/Operator.go index e2ae8183..f950024a 100644 --- a/mocks/orm/Operator.go +++ b/mocks/orm/Operator.go @@ -2,7 +2,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" +import ( + orm "github.com/ditrit/badaas/orm" + mock "github.com/stretchr/testify/mock" +) // Operator is an autogenerated mock type for the Operator type type Operator[T interface{}] struct { @@ -14,32 +17,32 @@ func (_m *Operator[T]) InterfaceVerificationMethod(_a0 T) { _m.Called(_a0) } -// ToSQL provides a mock function with given fields: columnName -func (_m *Operator[T]) ToSQL(columnName string) (string, []interface{}, error) { - ret := _m.Called(columnName) +// ToSQL provides a mock function with given fields: query, columnName +func (_m *Operator[T]) ToSQL(query *orm.Query, columnName string) (string, []interface{}, error) { + ret := _m.Called(query, columnName) var r0 string var r1 []interface{} var r2 error - if rf, ok := ret.Get(0).(func(string) (string, []interface{}, error)); ok { - return rf(columnName) + if rf, ok := ret.Get(0).(func(*orm.Query, string) (string, []interface{}, error)); ok { + return rf(query, columnName) } - if rf, ok := ret.Get(0).(func(string) string); ok { - r0 = rf(columnName) + if rf, ok := ret.Get(0).(func(*orm.Query, string) string); ok { + r0 = rf(query, columnName) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(string) []interface{}); ok { - r1 = rf(columnName) + if rf, ok := ret.Get(1).(func(*orm.Query, string) []interface{}); ok { + r1 = rf(query, columnName) } else { if ret.Get(1) != nil { r1 = ret.Get(1).([]interface{}) } } - if rf, ok := ret.Get(2).(func(string) error); ok { - r2 = rf(columnName) + if rf, ok := ret.Get(2).(func(*orm.Query, string) error); ok { + r2 = rf(query, columnName) } else { r2 = ret.Error(2) } diff --git a/mocks/orm/WhereCondition.go b/mocks/orm/WhereCondition.go index 94eb0849..cc0ae5c4 100644 --- a/mocks/orm/WhereCondition.go +++ b/mocks/orm/WhereCondition.go @@ -5,7 +5,6 @@ package mocks import ( orm "github.com/ditrit/badaas/orm" mock "github.com/stretchr/testify/mock" - gorm "gorm.io/gorm" ) // WhereCondition is an autogenerated mock type for the WhereCondition type @@ -13,49 +12,51 @@ type WhereCondition[T orm.Model] struct { mock.Mock } -// ApplyTo provides a mock function with given fields: query, table -func (_m *WhereCondition[T]) ApplyTo(query *gorm.DB, table orm.Table) (*gorm.DB, error) { - ret := _m.Called(query, table) +// AffectsDeletedAt provides a mock function with given fields: +func (_m *WhereCondition[T]) AffectsDeletedAt() bool { + ret := _m.Called() - var r0 *gorm.DB - var r1 error - if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) (*gorm.DB, error)); ok { - return rf(query, table) - } - if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) *gorm.DB); ok { - r0 = rf(query, table) + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*gorm.DB) - } + r0 = ret.Get(0).(bool) } - if rf, ok := ret.Get(1).(func(*gorm.DB, orm.Table) error); ok { - r1 = rf(query, table) + return r0 +} + +// ApplyTo provides a mock function with given fields: query, table +func (_m *WhereCondition[T]) ApplyTo(query *orm.Query, table orm.Table) error { + ret := _m.Called(query, table) + + var r0 error + if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) error); ok { + r0 = rf(query, table) } else { - r1 = ret.Error(1) + r0 = ret.Error(0) } - return r0, r1 + return r0 } // GetSQL provides a mock function with given fields: query, table -func (_m *WhereCondition[T]) GetSQL(query *gorm.DB, table orm.Table) (string, []interface{}, error) { +func (_m *WhereCondition[T]) GetSQL(query *orm.Query, table orm.Table) (string, []interface{}, error) { ret := _m.Called(query, table) var r0 string var r1 []interface{} var r2 error - if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) (string, []interface{}, error)); ok { + if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) (string, []interface{}, error)); ok { return rf(query, table) } - if rf, ok := ret.Get(0).(func(*gorm.DB, orm.Table) string); ok { + if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) string); ok { r0 = rf(query, table) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(*gorm.DB, orm.Table) []interface{}); ok { + if rf, ok := ret.Get(1).(func(*orm.Query, orm.Table) []interface{}); ok { r1 = rf(query, table) } else { if ret.Get(1) != nil { @@ -63,7 +64,7 @@ func (_m *WhereCondition[T]) GetSQL(query *gorm.DB, table orm.Table) (string, [] } } - if rf, ok := ret.Get(2).(func(*gorm.DB, orm.Table) error); ok { + if rf, ok := ret.Get(2).(func(*orm.Query, orm.Table) error); ok { r2 = rf(query, table) } else { r2 = ret.Error(2) @@ -72,22 +73,8 @@ func (_m *WhereCondition[T]) GetSQL(query *gorm.DB, table orm.Table) (string, [] return r0, r1, r2 } -// affectsDeletedAt provides a mock function with given fields: -func (_m *WhereCondition[T]) affectsDeletedAt() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// interfaceVerificationMethod provides a mock function with given fields: _a0 -func (_m *WhereCondition[T]) interfaceVerificationMethod(_a0 T) { +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *WhereCondition[T]) InterfaceVerificationMethod(_a0 T) { _m.Called(_a0) } diff --git a/mocks/orm/iFieldIdentifier.go b/mocks/orm/iFieldIdentifier.go new file mode 100644 index 00000000..bf5ded50 --- /dev/null +++ b/mocks/orm/iFieldIdentifier.go @@ -0,0 +1,74 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + orm "github.com/ditrit/badaas/orm" + mock "github.com/stretchr/testify/mock" + + reflect "reflect" +) + +// iFieldIdentifier is an autogenerated mock type for the iFieldIdentifier type +type iFieldIdentifier struct { + mock.Mock +} + +// ColumnName provides a mock function with given fields: query, table +func (_m *iFieldIdentifier) ColumnName(query *orm.Query, table orm.Table) string { + ret := _m.Called(query, table) + + var r0 string + if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) string); ok { + r0 = rf(query, table) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// ColumnSQL provides a mock function with given fields: query, table +func (_m *iFieldIdentifier) ColumnSQL(query *orm.Query, table orm.Table) string { + ret := _m.Called(query, table) + + var r0 string + if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) string); ok { + r0 = rf(query, table) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetModelType provides a mock function with given fields: +func (_m *iFieldIdentifier) GetModelType() reflect.Type { + ret := _m.Called() + + var r0 reflect.Type + if rf, ok := ret.Get(0).(func() reflect.Type); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(reflect.Type) + } + } + + return r0 +} + +type mockConstructorTestingTnewIFieldIdentifier interface { + mock.TestingT + Cleanup(func()) +} + +// newIFieldIdentifier creates a new instance of iFieldIdentifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func newIFieldIdentifier(t mockConstructorTestingTnewIFieldIdentifier) *iFieldIdentifier { + mock := &iFieldIdentifier{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From 6fc1481512d122dd7c1364a60681729bf2f45079 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 15:14:49 +0200 Subject: [PATCH 78/90] add new linting configuration --- .github/workflows/CI.yml | 2 +- .golangci.yml | 401 ++++++++++++++++++--------------------- 2 files changed, 185 insertions(+), 218 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5d8a6213..818afcd6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -34,7 +34,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.52.2 + version: v1.53.3 skip-cache: true skip-pkg-cache: true skip-build-cache: true diff --git a/.golangci.yml b/.golangci.yml index d4705090..9bf38e5e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,242 +1,207 @@ -# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY -# -# The original version of this file is located in the https://github.com/istio/common-files repo. -# If you're looking at this file in a different repo and want to make a change, please go to the -# common-files repo, make the change there and check it in. Then come back to this repo and run -# "make update-common". - -output: - # Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions - # - # Multiple can be specified by separating them by comma, output can be provided - # for each of them by separating format name and path by colon symbol. - # Output path can be either `stdout`, `stderr` or path to the file to write to. - # Example: "checkstyle:report.json,colored-line-number" - # - # Default: colored-line-number - format: github-actions - # Print lines of code with issue. - # Default: true - print-issued-lines: false - # Print linter name in the end of issue text. - # Default: true - print-linter-name: false - # Make issues output unique by line. - # Default: true - uniq-by-line: false - # Add a prefix to the output file references. - # Default is no prefix. - path-prefix: "" - # Sort results by: filepath, line and column. - sort-results: false +# based on +# - https://github.com/istio/common-files/blob/master/files/common/config/.golangci.yml +# - https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322 run: - tests: false # timeout for analysis, e.g. 30s, 5m, default is 1m - deadline: 20m - build-tags: - - integ - - integfuzz + deadline: 3m # which dirs to skip: they won't be analyzed; # can use regexp here: generated.*, regexp is applied on full path; # default value is empty list, but next dirs are always skipped independently # from this option's value: - #vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ skip-dirs: - - genfiles$ - - vendor$ - + - mocks$ # which files to skip: they will be analyzed, but issues from them # won't be reported. Default value is empty list, but there is # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - skip-files: - - ".*\\.pb\\.go" - - ".*\\.gen\\.go" + # autogenerated files. + # skip-files: + # - ".*\\.pb\\.go" + # - ".*\\.gen\\.go" linters: - disable-all: true - enable: - - deadcode - - exportloopref - - gocritic - - revive - - gosimple - - govet - - ineffassign - - lll - - misspell - - staticcheck - - structcheck - - stylecheck - - typecheck - - unconvert - - unparam - - varcheck + enable-all: true + disable: + - dogsled # [sometimes necessary] checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - exhaustruct # [make it easier to construct structs] checks if all structure fields are initialized + - gochecknoglobals # [we use them] checks that no global variables exist + - gochecknoinits # [we use them] checks that no init functions are present in Go code + - godot # [not necessary] checks if comments end in a period + - godox # [we use them] detects FIXME, TODO and other comment keywords + - ireturn # [useful for mocks generation] accept interfaces, return concrete types + - nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + - nonamedreturns # [are util sometimes] reports all named returns + - paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + - testpackage # [doesn't allow white box tests] makes you use a separate _test package + - thelper # [not the expected result by us] detects golang test helpers without t.Helper() call and checks the consistency of test helpers + - varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + # deprecated + - deadcode # [deprecated, replaced by unused] finds unused code + - exhaustivestruct # [deprecated, replaced by exhaustruct] checks if all struct's fields are initialized + - golint # [deprecated, replaced by revive] golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - ifshort # [deprecated] checks that your code uses short syntax for if-statements whenever possible + - interfacer # [deprecated] suggests narrower interface types + - maligned # [deprecated, replaced by govet fieldalignment] detects Go structs that would take less memory if their fields were sorted + - nosnakecase # [deprecated, replaced by revive var-naming] detects snake case of variable naming and function name + - scopelint # [deprecated, replaced by exportloopref] checks for unpinned variables in go programs + - structcheck # [deprecated, replaced by unused] finds unused struct fields + - varcheck # [deprecated, replaced by unused] finds unused global variables and constants + # can be util in the future for better errors + - goerr113 # [too strict] checks the errors handling expressions + - wrapcheck # [too strict] checks that errors returned from external packages are wrapped fast: false +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 20 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 0.0 + depguard: + rules: + main: + deny: + - pkg: "github.com/gogo/protobuf" + desc: gogo/protobuf is deprecated, use golang/protobuf + errcheck: + # report about not checking of errors in type assetions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: true + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: false + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 80 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 40 + gci: + sections: + - standard # Captures all standard packages if they do not match another section. + - default # Contains all imports that could not be matched to another section type. + - prefix(github.com/ditrit/) # Groups all imports with the specified Prefix. + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + goimports: + # put imports beginning with prefix after 3rd-party packages; + # it's a comma-separated list of prefixes + local-prefixes: github.com/ditrit + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + recommendations: + - github.com/google/uuid + reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/google/uuid + reason: "gofrs' package is not go module" govet: - # report about shadowed variables - check-shadowing: false - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true - misspell: - # Correct spellings using locale preferences for US or UK. - # Default is to use a neutral variety of English. - # Setting locale to US will correct the British spelling of 'colour' to 'color'. - locale: US - ignore-words: - - cancelled + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: false lll: # max line length, lines longer will be reported. Default is 120. # '\t' is counted as 1 character by default, and can be changed with the tab-width option line-length: 160 # tab width in spaces. Default to 1. tab-width: 1 - revive: - ignore-generated-header: false - severity: "warning" - confidence: 0.0 - error-code: 2 - warning-code: 1 - rules: - - name: blank-imports - - name: context-keys-type - - name: time-naming - - name: var-declaration - - name: unexported-return - - name: errorf - - name: context-as-argument - - name: dot-imports - - name: error-return - - name: error-strings - - name: error-naming - - name: increment-decrement - - name: var-naming - - name: package-comments - - name: range - - name: receiver-naming - - name: indent-error-flow - - name: superfluous-else - - name: modifies-parameter - - name: unreachable-code - - name: struct-tag - - name: constant-logical-expr - - name: bool-literal-in-expr - - name: redefines-builtin-id - - name: imports-blacklist - - name: range-val-in-closure - - name: range-val-address - - name: waitgroup-by-value - - name: atomic - - name: call-to-gc - - name: duplicated-imports - - name: string-of-int - - name: defer - arguments: [["call-chain"]] - - name: unconditional-recursion - - name: identical-branches - # the following rules can be enabled in the future - # - name: empty-lines - # - name: confusing-results - # - name: empty-block - # - name: get-return - # - name: confusing-naming - # - name: unexported-naming - - name: early-return - # - name: unused-parameter - # - name: unnecessary-stmt - # - name: deep-exit - # - name: import-shadowing - # - name: modifies-value-receiver - # - name: unused-receiver - # - name: bare-return - # - name: flag-parameter - # - name: unhandled-error - # - name: if-return - unused: - # treat code as a program (not a library) and report unused exported identifiers; default is false. - # XXX: if you enable this setting, unused will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find funcs usages. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false + misspell: + # Correct spellings using locale preferences for US or UK. + # Default is to use a neutral variety of English. + # Setting locale to US will correct the British spelling of 'colour' to 'color'. + locale: US + ignore-words: + - cancelled + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + rowserrcheck: + # database/sql is always checked + # Default: [] + packages: + - github.com/jmoiron/sqlx unparam: # call graph construction algorithm (cha, rta). In general, use cha for libraries, # and rta for programs with main packages. Default is cha. - algo: rta - + algo: cha # Inspect exported functions, default is false. Set to true if no external program/library imports your code. # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: # if it's called for subdir of a project it can't find external interfaces. All text editor integrations # with golangci-lint call it on a directory with the changed file. check-exported: false - gocritic: - enabled-checks: - - appendCombine - - argOrder - - assignOp - - badCond - - boolExprSimplify - - builtinShadow - - captLocal - - caseOrder - - codegenComment - - commentedOutCode - - commentedOutImport - - defaultCaseOrder - - deprecatedComment - - docStub - - dupArg - - dupBranchBody - - dupCase - - dupSubExpr - - elseif - - emptyFallthrough - - equalFold - - flagDeref - - flagName - - hexLiteral - - indexAlloc - - initClause - - methodExprCall - - nilValReturn - - octalLiteral - - offBy1 - - rangeExprCopy - - regexpMust - - sloppyLen - - stringXbytes - - switchTrue - - typeAssertChain - - typeSwitchVar - - typeUnparen - - underef - - unlambda - - unnecessaryBlock - - unslice - - valSwap - - weakCond - - # Unused - # - yodaStyleExpr - # - appendAssign - # - commentFormatting - # - emptyStringTest - # - exitAfterDefer - # - ifElseChain - # - hugeParam - # - importShadow - # - nestingReduce - # - paramTypeCombine - # - ptrToRefParam - # - rangeValCopy - # - singleCaseSwitch - # - sloppyReassign - # - unlabelStmt - # - unnamedResult - # - wrapperFunc + unused: + # treat code as a program (not a library) and report unused exported identifiers; default is false. + # XXX: if you enable this setting, unused will report a lot of false-positives in text editors: + # if it's called for subdir of a project it can't find funcs usages. All text editor integrations + # with golangci-lint call it on a directory with the changed file. + check-exported: false + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + gosec: + config: + # Globals are applicable to all rules. + global: + # If true, ignore #nosec in comments (and an alternative as well). + # Default: false + nosec: true + # Audit mode enables addition checks that for normal code analysis might be too nosy. + # Default: false + audit: true issues: # List of regexps of issue texts to exclude, empty list by default. @@ -245,27 +210,29 @@ issues: # excluded by default patterns execute `golangci-lint run --help` exclude: - composite literal uses unkeyed fields - exclude-rules: # Exclude some linters from running on test files. - - path: _test\.go$|^tests/|^samples/ + - path: _test\.go$|^testintegration/|^test_e2e/ linters: - errcheck - - maligned - - # TODO(https://github.com/dominikh/go-tools/issues/732) remove this once we update + - forcetypeassert + - funlen + - goconst + - noctx + - gomnd + # We need to use the deprecated module since the jsonpb replacement is not backwards compatible. - linters: - staticcheck - text: "SA1019: package github.com/golang/protobuf" - + text: "SA1019: package github.com/golang/protobuf/jsonpb" + - linters: + - staticcheck + text: 'SA1019: "github.com/golang/protobuf/jsonpb"' # Independently from option `exclude` we use default exclude patterns, # it can be disabled by this option. To list all # excluded by default patterns execute `golangci-lint run --help`. # Default value for this option is true. exclude-use-default: true - # Maximum issues count per one linter. Set to 0 to disable. Default is 50. max-per-linter: 0 - # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. max-same-issues: 0 \ No newline at end of file From 58744db766a3c70372636254ffd245c9a48390e5 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 16:00:14 +0200 Subject: [PATCH 79/90] fix new lint errors --- badaas.go | 14 +-- badaas_test.go | 14 +-- configuration/DatabaseConfiguration.go | 6 +- configuration/DatabaseConfigurationKeys.go | 4 +- configuration/DatabaseConfiguration_test.go | 18 +++- .../{ConfigurationHolder.go => Holder.go} | 2 +- configuration/HttpServerConfiguration.go | 3 +- configuration/HttpServerConfiguration_test.go | 28 +++--- configuration/InitializationConfiguration.go | 3 +- .../InitializationConfiguration_test.go | 6 +- configuration/LoggerConfiguration.go | 3 +- configuration/LoggerConfigurationKeys.go | 1 + configuration/LoggerConfiguration_test.go | 19 ++-- configuration/PaginationConfiguration.go | 3 +- configuration/PaginationConfiguration_test.go | 14 +-- configuration/ServerConfigurationKeys.go | 8 +- configuration/SessionConfiguration.go | 6 +- configuration/SessionConfigurationKeys.go | 6 +- configuration/SessionConfiguration_test.go | 34 ++++--- configuration/defaults.go | 13 +++ controllers/basicAuth.go | 9 +- controllers/basicAuth_test.go | 18 ++-- controllers/info.go | 3 +- go.mod | 4 - go.sum | 93 ------------------- httperrors/httperrors.go | 12 ++- httperrors/httperrors_test.go | 74 ++++++++------- logger/log.go | 11 ++- logger/log_test.go | 3 +- modules.go | 1 + modules_test.go | 6 ++ orm/ModuleFx.go | 4 +- orm/baseModels.go | 1 + orm/db.go | 2 + persistence/gormdatabase/gormzap/gormzap.go | 10 +- persistence/gormdatabase/helpers.go | 17 ---- persistence/gormdatabase/helpers_test.go | 33 ------- persistence/models/dto/HTTPError.go | 4 +- persistence/models/dto/LoginSuccess.go | 6 +- router/middlewares/middlewareJson.go | 5 + router/middlewares/middlewareLogger.go | 2 + router/middlewares/middlewarelogger_test.go | 8 +- router/routes_test.go | 4 +- server.go | 6 +- services/auth/protocols/basicauth/basic.go | 1 + .../auth/protocols/basicauth/basic_test.go | 4 +- services/sessionservice/session.go | 13 +++ services/sessionservice/session_test.go | 16 +++- services/sessionservice/sessionctx.go | 1 + services/sessionservice/sessionctx_test.go | 1 + services/userservice/userservice.go | 1 + services/userservice/userservice_test.go | 1 + test_e2e/go.mod | 4 - test_e2e/go.sum | 88 ------------------ testintegration/asserts.go | 1 + testintegration/crudServiceCommon.go | 1 + testintegration/models/models.go | 2 + testintegration/orm_test.go | 1 - testintegration/preload_conditions_test.go | 4 +- utils/time_test.go | 6 +- validators/email.go | 1 + validators/email_test.go | 3 +- 62 files changed, 298 insertions(+), 392 deletions(-) rename configuration/{ConfigurationHolder.go => Holder.go} (87%) create mode 100644 configuration/defaults.go delete mode 100644 persistence/gormdatabase/helpers.go delete mode 100644 persistence/gormdatabase/helpers_test.go diff --git a/badaas.go b/badaas.go index af29a837..925747a2 100644 --- a/badaas.go +++ b/badaas.go @@ -15,14 +15,14 @@ import ( "github.com/ditrit/verdeter" ) -var BaDaaS = BaDaaSInitializer{} +var BaDaaS = Initializer{} -type BaDaaSInitializer struct { +type Initializer struct { modules []fx.Option } // Allows to select which modules provided by badaas must be added to the application -func (badaas *BaDaaSInitializer) AddModules(modules ...fx.Option) *BaDaaSInitializer { +func (badaas *Initializer) AddModules(modules ...fx.Option) *Initializer { badaas.modules = append(badaas.modules, modules...) return badaas @@ -30,7 +30,7 @@ func (badaas *BaDaaSInitializer) AddModules(modules ...fx.Option) *BaDaaSInitial // Allows to provide constructors to the application // so that the constructed objects will be available via dependency injection -func (badaas *BaDaaSInitializer) Provide(constructors ...any) *BaDaaSInitializer { +func (badaas *Initializer) Provide(constructors ...any) *Initializer { badaas.modules = append(badaas.modules, fx.Provide(constructors...)) return badaas @@ -38,14 +38,14 @@ func (badaas *BaDaaSInitializer) Provide(constructors ...any) *BaDaaSInitializer // Allows to invoke functions when the application starts. // They can take advantage of dependency injection -func (badaas *BaDaaSInitializer) Invoke(funcs ...any) *BaDaaSInitializer { +func (badaas *Initializer) Invoke(funcs ...any) *Initializer { badaas.modules = append(badaas.modules, fx.Invoke(funcs...)) return badaas } // Start the application -func (badaas BaDaaSInitializer) Start() { +func (badaas Initializer) Start() { rootCommand := verdeter.BuildVerdeterCommand(verdeter.VerdeterConfig{ Use: "badaas", Short: "BaDaaS", @@ -61,7 +61,7 @@ func (badaas BaDaaSInitializer) Start() { } // Run the http server for badaas -func (badaas BaDaaSInitializer) runHTTPServer(cmd *cobra.Command, args []string) { +func (badaas Initializer) runHTTPServer(_ *cobra.Command, _ []string) { modules := []fx.Option{ // internal modules configuration.ConfigurationModule, diff --git a/badaas_test.go b/badaas_test.go index cae48d52..f8e3568b 100644 --- a/badaas_test.go +++ b/badaas_test.go @@ -10,7 +10,7 @@ import ( "github.com/ditrit/badaas/configuration" ) -func TestInvokeFunctionsWithProvidedValues(t *testing.T) { +func TestInvokeFunctionsWithProvidedValues(_ *testing.T) { mockObject := mockObject{} mockObject.On("Function", 1).Return(1) @@ -22,7 +22,7 @@ func TestInvokeFunctionsWithProvidedValues(t *testing.T) { viper.Set(configuration.DatabaseSslmodeKey, "disable") viper.Set(configuration.DatabaseRetryKey, 0) - badaas := BaDaaSInitializer{} + badaas := Initializer{} badaas.Provide( newIntValue, ).Invoke( @@ -31,17 +31,17 @@ func TestInvokeFunctionsWithProvidedValues(t *testing.T) { ).Start() } -func TestAddModulesAreExecuted(t *testing.T) { - mockObject := mockObject{} +func TestAddModulesAreExecuted(_ *testing.T) { + mockObjectI := mockObject{} - mockObject.On("Function", 1).Return(1) + mockObjectI.On("Function", 1).Return(1) - badaas := BaDaaSInitializer{} + badaas := Initializer{} badaas.AddModules( fx.Module( "test module", fx.Provide(newIntValue), - fx.Invoke(mockObject.Function), + fx.Invoke(mockObjectI.Function), ), ).Invoke( shutdown, diff --git a/configuration/DatabaseConfiguration.go b/configuration/DatabaseConfiguration.go index 6a950473..47558814 100644 --- a/configuration/DatabaseConfiguration.go +++ b/configuration/DatabaseConfiguration.go @@ -3,9 +3,10 @@ package configuration import ( "time" - "github.com/ditrit/badaas/utils" "github.com/spf13/viper" "go.uber.org/zap" + + "github.com/ditrit/badaas/utils" ) // The config keys regarding the database settings @@ -22,7 +23,7 @@ const ( // Hold the configuration values for the database connection type DatabaseConfiguration interface { - ConfigurationHolder + Holder GetPort() int GetHost() string GetDBName() string @@ -49,6 +50,7 @@ type databaseConfigurationImpl struct { func NewDatabaseConfiguration() DatabaseConfiguration { databaseConfiguration := new(databaseConfigurationImpl) databaseConfiguration.Reload() + return databaseConfiguration } diff --git a/configuration/DatabaseConfigurationKeys.go b/configuration/DatabaseConfigurationKeys.go index 36c3ed37..15a3b3d0 100644 --- a/configuration/DatabaseConfigurationKeys.go +++ b/configuration/DatabaseConfigurationKeys.go @@ -46,13 +46,13 @@ func getDatabaseConfigurationKeys() []KeyDefinition { Name: DatabaseRetryKey, ValType: verdeter.IsUint, Usage: "The number of times badaas tries to establish a connection with the database", - DefaultV: uint(10), + DefaultV: defaultDatabaseRetry, }, { Name: DatabaseRetryDurationKey, ValType: verdeter.IsUint, Usage: "The duration in seconds badaas wait between connection attempts", - DefaultV: uint(5), + DefaultV: defaultDatabaseRetryDuration, }, } } diff --git a/configuration/DatabaseConfiguration_test.go b/configuration/DatabaseConfiguration_test.go index 21d5447c..5a08a0f2 100644 --- a/configuration/DatabaseConfiguration_test.go +++ b/configuration/DatabaseConfiguration_test.go @@ -5,13 +5,14 @@ import ( "testing" "time" - "github.com/ditrit/badaas/configuration" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var databaseConfigurationString = ` @@ -35,6 +36,7 @@ database: func setupViperEnvironment(configurationString string) { viper.Reset() viper.SetConfigType("yaml") + err := viper.ReadConfig(strings.NewReader(configurationString)) if err != nil { panic(err) @@ -43,51 +45,63 @@ func setupViperEnvironment(configurationString string) { func TestDatabaseConfigurationNewDBConfig(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.NotNil(t, databaseConfiguration, "the database configuration should not be nil") } func TestDatabaseConfigurationGetPort(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, 26257, databaseConfiguration.GetPort(), "should be equals") } func TestDatabaseConfigurationGetHost(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "e2e-db-1", databaseConfiguration.GetHost()) } func TestDatabaseConfigurationGetUsername(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "root", databaseConfiguration.GetUsername()) } + func TestDatabaseConfigurationGetPassword(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "postgres", databaseConfiguration.GetPassword()) } + func TestDatabaseConfigurationGetSSLMode(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "disable", databaseConfiguration.GetSSLMode()) } + func TestDatabaseConfigurationGetDBName(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, "badaas_db", databaseConfiguration.GetDBName()) } func TestDatabaseConfigurationGetRetryTime(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() - assert.Equal(t, time.Duration(5*time.Second), databaseConfiguration.GetRetryTime()) + assert.Equal(t, 5*time.Second, databaseConfiguration.GetRetryTime()) } func TestDatabaseConfigurationGetRetry(t *testing.T) { setupViperEnvironment(databaseConfigurationString) + databaseConfiguration := configuration.NewDatabaseConfiguration() assert.Equal(t, uint(10), databaseConfiguration.GetRetry()) } diff --git a/configuration/ConfigurationHolder.go b/configuration/Holder.go similarity index 87% rename from configuration/ConfigurationHolder.go rename to configuration/Holder.go index 95f150e4..a749222b 100644 --- a/configuration/ConfigurationHolder.go +++ b/configuration/Holder.go @@ -3,7 +3,7 @@ package configuration import "go.uber.org/zap" // Every configuration holder must implement this interface -type ConfigurationHolder interface { +type Holder interface { // Reload the values provided by the configuration holder Reload() diff --git a/configuration/HttpServerConfiguration.go b/configuration/HttpServerConfiguration.go index a81346de..a99883aa 100644 --- a/configuration/HttpServerConfiguration.go +++ b/configuration/HttpServerConfiguration.go @@ -20,7 +20,7 @@ const ( // Hold the configuration values for the http server type HTTPServerConfiguration interface { - ConfigurationHolder + Holder GetAddr() string GetHost() string GetPort() int @@ -38,6 +38,7 @@ type hTTPServerConfigurationImpl struct { func NewHTTPServerConfiguration() HTTPServerConfiguration { httpServerConfiguration := new(hTTPServerConfigurationImpl) httpServerConfiguration.Reload() + return httpServerConfiguration } diff --git a/configuration/HttpServerConfiguration_test.go b/configuration/HttpServerConfiguration_test.go index 899e91fe..c793f7b5 100644 --- a/configuration/HttpServerConfiguration_test.go +++ b/configuration/HttpServerConfiguration_test.go @@ -20,31 +20,35 @@ var HTTPServerConfigurationString = `server: ` func TestHTTPServerConfigurationNewHttpServerConfiguration(t *testing.T) { - assert.NotNil(t, configuration.NewHTTPServerConfiguration(), "the contructor for HttpServerConfiguration should not return a nil value") + assert.NotNil(t, configuration.NewHTTPServerConfiguration(), "the constructor for HttpServerConfiguration should not return a nil value") } func TestHTTPServerConfigurationGetPort(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) - HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() - assert.Equal(t, 8000, HTTPServerConfiguration.GetPort()) + + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, 8000, httpServerConfiguration.GetPort()) } func TestHTTPServerConfigurationGetHost(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) - HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() - assert.Equal(t, "0.0.0.0", HTTPServerConfiguration.GetHost()) + + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, "0.0.0.0", httpServerConfiguration.GetHost()) } func TestHTTPServerConfigurationGetAddr(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) - HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() - assert.Equal(t, "0.0.0.0:8000", HTTPServerConfiguration.GetAddr()) + + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, "0.0.0.0:8000", httpServerConfiguration.GetAddr()) } func TestHTTPServerConfigurationGetMaxTimeout(t *testing.T) { setupViperEnvironment(HTTPServerConfigurationString) - HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() - assert.Equal(t, time.Duration(15*time.Second), HTTPServerConfiguration.GetMaxTimeout()) + + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + assert.Equal(t, 15*time.Second, httpServerConfiguration.GetMaxTimeout()) } func TestHTTPServerConfigurationLog(t *testing.T) { @@ -53,8 +57,8 @@ func TestHTTPServerConfigurationLog(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - HTTPServerConfiguration := configuration.NewHTTPServerConfiguration() - HTTPServerConfiguration.Log(observedLogger) + httpServerConfiguration := configuration.NewHTTPServerConfiguration() + httpServerConfiguration.Log(observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] @@ -63,6 +67,6 @@ func TestHTTPServerConfigurationLog(t *testing.T) { assert.ElementsMatch(t, []zap.Field{ {Key: "port", Type: zapcore.Int64Type, Integer: 8000}, {Key: "host", Type: zapcore.StringType, String: "0.0.0.0"}, - {Key: "timeout", Type: zapcore.DurationType, Integer: int64(time.Duration(time.Second * 15))}, + {Key: "timeout", Type: zapcore.DurationType, Integer: int64(time.Second * 15)}, }, log.Context) } diff --git a/configuration/InitializationConfiguration.go b/configuration/InitializationConfiguration.go index 8e365d23..ac70938c 100644 --- a/configuration/InitializationConfiguration.go +++ b/configuration/InitializationConfiguration.go @@ -12,7 +12,7 @@ const ( // Hold the configuration values for the initialization type InitializationConfiguration interface { - ConfigurationHolder + Holder GetAdminPassword() string } @@ -25,6 +25,7 @@ type initializationConfigurationIml struct { func NewInitializationConfiguration() InitializationConfiguration { initializationConfiguration := &initializationConfigurationIml{} initializationConfiguration.Reload() + return initializationConfiguration } diff --git a/configuration/InitializationConfiguration_test.go b/configuration/InitializationConfiguration_test.go index b80fbd9d..b6ffa776 100644 --- a/configuration/InitializationConfiguration_test.go +++ b/configuration/InitializationConfiguration_test.go @@ -3,12 +3,13 @@ package configuration_test import ( "testing" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var initializationConfigurationString = `default: @@ -16,11 +17,12 @@ var initializationConfigurationString = `default: password: admin` func TestInitializationConfigurationInitializationConfiguration(t *testing.T) { - assert.NotNil(t, configuration.NewInitializationConfiguration(), "the contructor for InitializationConfiguration should not return a nil value") + assert.NotNil(t, configuration.NewInitializationConfiguration(), "the constructor for InitializationConfiguration should not return a nil value") } func TestInitializationConfigurationGetInit(t *testing.T) { setupViperEnvironment(initializationConfigurationString) + initializationConfiguration := configuration.NewInitializationConfiguration() assert.Equal(t, "admin", initializationConfiguration.GetAdminPassword()) } diff --git a/configuration/LoggerConfiguration.go b/configuration/LoggerConfiguration.go index 133da8c8..a92ce125 100644 --- a/configuration/LoggerConfiguration.go +++ b/configuration/LoggerConfiguration.go @@ -13,7 +13,7 @@ const ( // Hold the configuration values for the logger type LoggerConfiguration interface { - ConfigurationHolder + Holder GetMode() string GetRequestTemplate() string } @@ -27,6 +27,7 @@ type loggerConfigurationImpl struct { func NewLoggerConfiguration() LoggerConfiguration { loggerConfiguration := new(loggerConfigurationImpl) loggerConfiguration.Reload() + return loggerConfiguration } diff --git a/configuration/LoggerConfigurationKeys.go b/configuration/LoggerConfigurationKeys.go index b5686730..8fc94026 100644 --- a/configuration/LoggerConfigurationKeys.go +++ b/configuration/LoggerConfigurationKeys.go @@ -8,6 +8,7 @@ import ( // Definition of logger configuration keys func getLoggerConfigurationKeys() []KeyDefinition { modeValidator := validators.AuthorizedValues("prod", "dev") + return []KeyDefinition{ { Name: LoggerRequestTemplateKey, diff --git a/configuration/LoggerConfiguration_test.go b/configuration/LoggerConfiguration_test.go index 53b005bb..ca97bf78 100644 --- a/configuration/LoggerConfiguration_test.go +++ b/configuration/LoggerConfiguration_test.go @@ -3,12 +3,13 @@ package configuration_test import ( "testing" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var LoggerConfigurationString = `logger: @@ -18,19 +19,21 @@ var LoggerConfigurationString = `logger: ` func TestLoggerConfigurationNewLoggerConfiguration(t *testing.T) { - assert.NotNil(t, configuration.NewLoggerConfiguration(), "the contructor for LoggerConfiguration should not return a nil value") + assert.NotNil(t, configuration.NewLoggerConfiguration(), "the constructor for LoggerConfiguration should not return a nil value") } func TestLoggerConfigurationLoggerGetMode(t *testing.T) { setupViperEnvironment(LoggerConfigurationString) - LoggerConfiguration := configuration.NewLoggerConfiguration() - assert.Equal(t, "prod", LoggerConfiguration.GetMode()) + + loggerConfiguration := configuration.NewLoggerConfiguration() + assert.Equal(t, "prod", loggerConfiguration.GetMode()) } func TestLoggerConfigurationLoggerRequestTemplate(t *testing.T) { setupViperEnvironment(LoggerConfigurationString) - LoggerConfiguration := configuration.NewLoggerConfiguration() - assert.Equal(t, "{proto} {method} {url}", LoggerConfiguration.GetRequestTemplate()) + + loggerConfiguration := configuration.NewLoggerConfiguration() + assert.Equal(t, "{proto} {method} {url}", loggerConfiguration.GetRequestTemplate()) } func TestLoggerConfigurationLog(t *testing.T) { @@ -39,8 +42,8 @@ func TestLoggerConfigurationLog(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - LoggerConfiguration := configuration.NewLoggerConfiguration() - LoggerConfiguration.Log(observedLogger) + loggerConfiguration := configuration.NewLoggerConfiguration() + loggerConfiguration.Log(observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] diff --git a/configuration/PaginationConfiguration.go b/configuration/PaginationConfiguration.go index 264a8233..f91408d8 100644 --- a/configuration/PaginationConfiguration.go +++ b/configuration/PaginationConfiguration.go @@ -7,7 +7,7 @@ import ( // Hold the configuration values for the pagination type PaginationConfiguration interface { - ConfigurationHolder + Holder GetMaxElemPerPage() uint } @@ -20,6 +20,7 @@ type paginationConfigurationImpl struct { func NewPaginationConfiguration() PaginationConfiguration { paginationConfiguration := new(paginationConfigurationImpl) paginationConfiguration.Reload() + return paginationConfiguration } diff --git a/configuration/PaginationConfiguration_test.go b/configuration/PaginationConfiguration_test.go index aaca2116..b941b247 100644 --- a/configuration/PaginationConfiguration_test.go +++ b/configuration/PaginationConfiguration_test.go @@ -3,24 +3,26 @@ package configuration_test import ( "testing" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var PaginationConfigurationString = `server.pagination.page.max: 12` func TestPaginationConfigurationNewPaginationConfiguration(t *testing.T) { - assert.NotNil(t, configuration.NewPaginationConfiguration(), "the contructor for PaginationConfiguration should not return a nil value") + assert.NotNil(t, configuration.NewPaginationConfiguration(), "the constructor for PaginationConfiguration should not return a nil value") } func TestPaginationConfigurationGetMaxElemPerPage(t *testing.T) { setupViperEnvironment(PaginationConfigurationString) - PaginationConfiguration := configuration.NewPaginationConfiguration() - assert.Equal(t, uint(12), PaginationConfiguration.GetMaxElemPerPage()) + + paginationConfiguration := configuration.NewPaginationConfiguration() + assert.Equal(t, uint(12), paginationConfiguration.GetMaxElemPerPage()) } func TestPaginationConfigurationLog(t *testing.T) { @@ -29,8 +31,8 @@ func TestPaginationConfigurationLog(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - PaginationConfiguration := configuration.NewPaginationConfiguration() - PaginationConfiguration.Log(observedLogger) + paginationConfiguration := configuration.NewPaginationConfiguration() + paginationConfiguration.Log(observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] diff --git a/configuration/ServerConfigurationKeys.go b/configuration/ServerConfigurationKeys.go index 7582984d..869fccf5 100644 --- a/configuration/ServerConfigurationKeys.go +++ b/configuration/ServerConfigurationKeys.go @@ -12,26 +12,26 @@ func getServerConfigurationKeys() []KeyDefinition { Name: ServerTimeoutKey, ValType: verdeter.IsInt, Usage: "Maximum timeout of the http server in second (default is 15s)", - DefaultV: 15, + DefaultV: defaultServerTimeout, }, { Name: ServerHostKey, ValType: verdeter.IsStr, Usage: "Address to bind (default is 0.0.0.0)", - DefaultV: "0.0.0.0", + DefaultV: defaultServerAddress, }, { Name: ServerPortKey, ValType: verdeter.IsInt, Usage: "Port to bind (default is 8000)", - DefaultV: 8000, + DefaultV: defaultServerPort, Validator: &validators.CheckTCPHighPort, }, { Name: ServerPaginationMaxElemPerPage, ValType: verdeter.IsUint, Usage: "The max number of records returned per page", - DefaultV: uint(100), + DefaultV: defaultServerPaginationMaxElemPerPage, }, } } diff --git a/configuration/SessionConfiguration.go b/configuration/SessionConfiguration.go index 1d330a8c..242a7739 100644 --- a/configuration/SessionConfiguration.go +++ b/configuration/SessionConfiguration.go @@ -3,9 +3,10 @@ package configuration import ( "time" - "github.com/ditrit/badaas/utils" "github.com/spf13/viper" "go.uber.org/zap" + + "github.com/ditrit/badaas/utils" ) // The config keys regarding the session handling settings @@ -17,7 +18,7 @@ const ( // Hold the configuration values to handle the sessions type SessionConfiguration interface { - ConfigurationHolder + Holder GetSessionDuration() time.Duration GetPullInterval() time.Duration GetRollDuration() time.Duration @@ -34,6 +35,7 @@ type sessionConfigurationImpl struct { func NewSessionConfiguration() SessionConfiguration { sessionConfiguration := new(sessionConfigurationImpl) sessionConfiguration.Reload() + return sessionConfiguration } diff --git a/configuration/SessionConfigurationKeys.go b/configuration/SessionConfigurationKeys.go index 4a0960f7..7387c816 100644 --- a/configuration/SessionConfigurationKeys.go +++ b/configuration/SessionConfigurationKeys.go @@ -11,19 +11,19 @@ func getSessionConfigurationKeys() []KeyDefinition { Name: SessionDurationKey, ValType: verdeter.IsUint, Usage: "The duration of a user session in seconds", - DefaultV: uint(3600 * 4), // 4 hours by default + DefaultV: defaultSessionDuration, }, { Name: SessionPullIntervalKey, ValType: verdeter.IsUint, Usage: "The refresh interval in seconds. Badaas refresh it's internal session cache periodically", - DefaultV: uint(30), // 30 seconds by default + DefaultV: defaultSessionPullInterval, }, { Name: SessionRollIntervalKey, ValType: verdeter.IsUint, Usage: "The interval in which the user can renew it's session by making a request", - DefaultV: uint(3600), // 1 hour by default + DefaultV: defaultSessionRollInterval, }, } } diff --git a/configuration/SessionConfiguration_test.go b/configuration/SessionConfiguration_test.go index 05a9e101..1d06f183 100644 --- a/configuration/SessionConfiguration_test.go +++ b/configuration/SessionConfiguration_test.go @@ -4,12 +4,13 @@ import ( "testing" "time" - "github.com/ditrit/badaas/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/configuration" ) var SessionConfigurationString = `session: @@ -18,25 +19,28 @@ var SessionConfigurationString = `session: rollDuration: 10 # 10 seconds` func TestSessionConfigurationNewSessionConfiguration(t *testing.T) { - assert.NotNil(t, configuration.NewSessionConfiguration(), "the contructor for PaginationConfiguration should not return a nil value") + assert.NotNil(t, configuration.NewSessionConfiguration(), "the constructor for PaginationConfiguration should not return a nil value") } func TestSessionConfigurationGetSessionDuration(t *testing.T) { setupViperEnvironment(SessionConfigurationString) - SessionConfiguration := configuration.NewSessionConfiguration() - assert.Equal(t, time.Duration(time.Hour), SessionConfiguration.GetSessionDuration()) + + sessionConfiguration := configuration.NewSessionConfiguration() + assert.Equal(t, time.Hour, sessionConfiguration.GetSessionDuration()) } -func TestSessionConfigurationGetPullIntervall(t *testing.T) { +func TestSessionConfigurationGetPullInterval(t *testing.T) { setupViperEnvironment(SessionConfigurationString) - SessionConfiguration := configuration.NewSessionConfiguration() - assert.Equal(t, time.Duration(time.Second*30), SessionConfiguration.GetPullInterval()) + + sessionConfiguration := configuration.NewSessionConfiguration() + assert.Equal(t, time.Second*30, sessionConfiguration.GetPullInterval()) } -func TestSessionConfigurationGetRollIntervall(t *testing.T) { +func TestSessionConfigurationGetRollInterval(t *testing.T) { setupViperEnvironment(SessionConfigurationString) - SessionConfiguration := configuration.NewSessionConfiguration() - assert.Equal(t, time.Duration(time.Second*10), SessionConfiguration.GetRollDuration()) + + sessionConfiguration := configuration.NewSessionConfiguration() + assert.Equal(t, time.Second*10, sessionConfiguration.GetRollDuration()) } func TestSessionConfigurationLog(t *testing.T) { @@ -45,16 +49,16 @@ func TestSessionConfigurationLog(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - PaginationConfiguration := configuration.NewSessionConfiguration() - PaginationConfiguration.Log(observedLogger) + paginationConfiguration := configuration.NewSessionConfiguration() + paginationConfiguration.Log(observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] assert.Equal(t, "Session configuration", log.Message) require.Len(t, log.Context, 3) assert.ElementsMatch(t, []zap.Field{ - {Key: "sessionDuration", Type: zapcore.DurationType, Integer: int64(time.Duration(time.Hour))}, - {Key: "pullInterval", Type: zapcore.DurationType, Integer: int64(time.Duration(time.Second * 30))}, - {Key: "rollDuration", Type: zapcore.DurationType, Integer: int64(time.Duration(time.Second * 10))}, + {Key: "sessionDuration", Type: zapcore.DurationType, Integer: int64(time.Hour)}, + {Key: "pullInterval", Type: zapcore.DurationType, Integer: int64(time.Second * 30)}, + {Key: "rollDuration", Type: zapcore.DurationType, Integer: int64(time.Second * 10)}, }, log.Context) } diff --git a/configuration/defaults.go b/configuration/defaults.go new file mode 100644 index 00000000..50419f7d --- /dev/null +++ b/configuration/defaults.go @@ -0,0 +1,13 @@ +package configuration + +const ( + defaultDatabaseRetry = uint(10) + defaultDatabaseRetryDuration = uint(5) + defaultServerTimeout = 15 + defaultServerAddress = "0.0.0.0" + defaultServerPort = 8000 + defaultServerPaginationMaxElemPerPage = uint(100) + defaultSessionDuration = uint(3600 * 4) // 4 hours + defaultSessionPullInterval = uint(30) // 30 seconds + defaultSessionRollInterval = uint(3600) // 1 hour +) diff --git a/controllers/basicAuth.go b/controllers/basicAuth.go index 7d9205fb..370737ab 100644 --- a/controllers/basicAuth.go +++ b/controllers/basicAuth.go @@ -16,6 +16,8 @@ import ( "github.com/ditrit/badaas/services/userservice" ) +const accessTokenCookieExpirationTime = 48 * time.Hour + // HTTPErrRequestMalformed is sent when the request is malformed var HTTPErrRequestMalformed httperrors.HTTPError = httperrors.NewHTTPError( http.StatusBadRequest, @@ -55,6 +57,7 @@ func NewBasicAuthenticationController( // Log In with username and password func (basicAuthController *basicAuthenticationController) BasicLoginHandler(w http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { var loginJSONStruct dto.UserLoginDTO + err := json.NewDecoder(r.Body).Decode(&loginJSONStruct) if err != nil { return nil, HTTPErrRequestMalformed @@ -87,7 +90,7 @@ func (basicAuthController *basicAuthenticationController) BasicLoginHandler(w ht return nil, herr } - return dto.DTOLoginSuccess{ + return dto.LoginSuccess{ Email: user.Email, ID: user.ID.String(), Username: user.Username, @@ -117,13 +120,15 @@ func createAndSetAccessTokenCookie(w http.ResponseWriter, sessionUUID string) ht HttpOnly: true, SameSite: http.SameSiteNoneMode, // TODO change to http.SameSiteStrictMode in prod Secure: false, // TODO change to true in prod - Expires: time.Now().Add(48 * time.Hour), + Expires: time.Now().Add(accessTokenCookieExpirationTime), } + err := accessToken.Valid() if err != nil { return httperrors.NewInternalServerError("access token error", "unable to create access token", err) } http.SetCookie(w, accessToken) + return nil } diff --git a/controllers/basicAuth_test.go b/controllers/basicAuth_test.go index 254bc011..143e6c11 100644 --- a/controllers/basicAuth_test.go +++ b/controllers/basicAuth_test.go @@ -1,6 +1,7 @@ package controllers_test import ( + "net/http" "net/http/httptest" "strings" "testing" @@ -33,7 +34,7 @@ func Test_BasicLoginHandler_MalformedRequest(t *testing.T) { ) response := httptest.NewRecorder() request := httptest.NewRequest( - "POST", + http.MethodPost, "/login", strings.NewReader("qsdqsdqsd"), ) @@ -53,7 +54,8 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { userService := mocksUserService.NewUserService(t) userService. On("GetUser", loginJSONStruct). - Return(nil, httperrors.AnError) + Return(nil, httperrors.ErrForTests) + sessionService := mocksSessionService.NewSessionService(t) controller := controllers.NewBasicAuthenticationController( @@ -63,7 +65,7 @@ func Test_BasicLoginHandler_UserNotFound(t *testing.T) { ) response := httptest.NewRecorder() request := httptest.NewRequest( - "POST", + http.MethodPost, "/login", strings.NewReader(`{ "email": "bob@email.com", @@ -85,7 +87,7 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { } response := httptest.NewRecorder() request := httptest.NewRequest( - "POST", + http.MethodPost, "/login", strings.NewReader(`{ "email": "bob@email.com", @@ -102,10 +104,11 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { userService. On("GetUser", loginJSONStruct). Return(user, nil) + sessionService := mocksSessionService.NewSessionService(t) sessionService. On("LogUserIn", user). - Return(nil, httperrors.AnError) + Return(nil, httperrors.ErrForTests) controller := controllers.NewBasicAuthenticationController( logger, @@ -127,7 +130,7 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { } response := httptest.NewRecorder() request := httptest.NewRequest( - "POST", + http.MethodPost, "/login", strings.NewReader(`{ "email": "bob@email.com", @@ -146,6 +149,7 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { userService. On("GetUser", loginJSONStruct). Return(user, nil) + sessionService := mocksSessionService.NewSessionService(t) sessionService. On("LogUserIn", user). @@ -159,7 +163,7 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { payload, err := controller.BasicLoginHandler(response, request) assert.NoError(t, err) - assert.Equal(t, payload, dto.DTOLoginSuccess{ + assert.Equal(t, payload, dto.LoginSuccess{ Email: "bob@email.com", ID: user.ID.String(), Username: user.Username, diff --git a/controllers/info.go b/controllers/info.go index 50e58ad3..1c4f95f6 100644 --- a/controllers/info.go +++ b/controllers/info.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/Masterminds/semver/v3" + "github.com/ditrit/badaas/httperrors" ) @@ -29,7 +30,7 @@ func NewInfoController(version *semver.Version) InformationController { } // Return the badaas server information -func (c *infoControllerImpl) Info(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { +func (c *infoControllerImpl) Info(_ http.ResponseWriter, _ *http.Request) (any, httperrors.HTTPError) { return &BadaasServerInfo{ Status: "OK", Version: c.Version.String(), diff --git a/go.mod b/go.mod index 2391c154..5057caba 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/google/uuid v1.3.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 - github.com/jackc/pgconn v1.14.0 github.com/magiconair/properties v1.8.7 github.com/noirbizarre/gonja v0.0.0-20200629003239-4d051fd0be61 github.com/spf13/cobra v1.7.0 @@ -31,10 +30,7 @@ require ( github.com/goph/emperror v0.17.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect diff --git a/go.sum b/go.sum index d87cc7d2..1edeb871 100644 --- a/go.sum +++ b/go.sum @@ -56,11 +56,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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= @@ -86,7 +82,6 @@ github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclK github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -162,47 +157,12 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -212,24 +172,16 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= @@ -256,16 +208,9 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= @@ -282,7 +227,6 @@ github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -293,7 +237,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -305,16 +248,12 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= @@ -322,28 +261,18 @@ go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= go.uber.org/fx v1.19.3 h1:YqMRE4+2IepTYCMOvXqQpRa+QAVdiSTnsHU4XNWBceA= go.uber.org/fx v1.19.3/go.mod h1:w2HrQg26ql9fLK7hlBiZ6JsRUKV+Lj/atT1KCjT8YhM= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -381,7 +310,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -395,7 +323,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -416,8 +343,6 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -437,24 +362,19 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -480,17 +400,11 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -500,7 +414,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -513,14 +426,12 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -555,9 +466,6 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -655,7 +563,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/httperrors/httperrors.go b/httperrors/httperrors.go index 0a09fb2b..8bb408c7 100644 --- a/httperrors/httperrors.go +++ b/httperrors/httperrors.go @@ -10,10 +10,10 @@ import ( "github.com/ditrit/badaas/persistence/models/dto" ) -// AnError is an HTTPError instance useful for testing. If the code does not care +// ErrForTests is an HTTPError instance useful for testing. If the code does not care // about HTTPError specifics, and only needs to return the HTTPError for example, this // HTTPError should be used to make the test code more readable. -var AnError HTTPError = &HTTPErrorImpl{ +var ErrForTests HTTPError = &HTTPErrorImpl{ Status: -1, Err: "TESTING ERROR", Message: "USE ONLY FOR TESTING", @@ -35,6 +35,8 @@ type HTTPError interface { } // Describe an HTTP error +// +//nolint:errname // this name is correct for a type name type HTTPErrorImpl struct { Status int Err string @@ -45,12 +47,15 @@ type HTTPErrorImpl struct { // Convert an HTTPError to a json string func (httpError *HTTPErrorImpl) ToJSON() string { - dto := &dto.DTOHTTPError{ + dto := &dto.HTTPError{ Error: httpError.Err, Message: httpError.Message, Status: http.StatusText(httpError.Status), } + + //nolint:errchkjson // TODO fix it payload, _ := json.Marshal(dto) + return string(payload) } @@ -69,6 +74,7 @@ func (httpError *HTTPErrorImpl) Write(httpResponse http.ResponseWriter, logger * if httpError.toLog && logger != nil { logHTTPError(httpError, logger) } + http.Error(httpResponse, httpError.ToJSON(), httpError.Status) } diff --git a/httperrors/httperrors_test.go b/httperrors/httperrors_test.go index b98b9c77..03ee8390 100644 --- a/httperrors/httperrors_test.go +++ b/httperrors/httperrors_test.go @@ -9,25 +9,27 @@ import ( "net/http/httptest" "testing" - "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/persistence/models/dto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" + + "github.com/ditrit/badaas/httperrors" + "github.com/ditrit/badaas/persistence/models/dto" ) func TestTojson(t *testing.T) { err := "Error while parsing json" message := "The request body was malformed" - error := httperrors.NewHTTPError(http.StatusBadRequest, err, message, nil, true) - assert.NotEmpty(t, error.ToJSON()) - assert.True(t, json.Valid([]byte(error.ToJSON())), "output json is not valid") + herr := httperrors.NewHTTPError(http.StatusBadRequest, err, message, nil, true) + assert.NotEmpty(t, herr.ToJSON()) + assert.True(t, json.Valid([]byte(herr.ToJSON())), "output json is not valid") - // check if is is correctly deserialized + // check if it is correctly deserialized var content map[string]any - json.Unmarshal([]byte(error.ToJSON()), &content) + + json.Unmarshal([]byte(herr.ToJSON()), &content) _, ok := content["err"] assert.True(t, ok, "\"err\" field should be in the json string") _, ok = content["msg"] @@ -38,29 +40,30 @@ func TestTojson(t *testing.T) { assert.Equal(t, err, content["err"].(string)) assert.Equal(t, message, content["msg"].(string)) assert.Equal(t, http.StatusText(http.StatusBadRequest), content["status"].(string)) - assert.True(t, error.Log()) + assert.True(t, herr.Log()) } func TestLog(t *testing.T) { - error := httperrors.NewHTTPError(http.StatusBadRequest, "err", "message", nil, true) - assert.True(t, error.Log()) - error = httperrors.NewHTTPError(http.StatusBadRequest, "err", "message", nil, false) - assert.False(t, error.Log()) + herr := httperrors.NewHTTPError(http.StatusBadRequest, "err", "message", nil, true) + assert.True(t, herr.Log()) + herr = httperrors.NewHTTPError(http.StatusBadRequest, "err", "message", nil, false) + assert.False(t, herr.Log()) } func TestError(t *testing.T) { - error := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) - assert.Contains(t, error.Error(), error.ToJSON()) + herr := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) + assert.Contains(t, herr.Error(), herr.ToJSON()) } func TestWrite(t *testing.T) { res := httptest.NewRecorder() - error := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) - error.Write(res, zap.L()) + herr := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) + herr.Write(res, zap.L()) bodyBytes, err := io.ReadAll(res.Body) assert.Nil(t, err) assert.NotEmpty(t, bodyBytes) - originalBytes := []byte(error.ToJSON()) + + originalBytes := []byte(herr.ToJSON()) // can't use assert.Contains because it only support strings assert.True(t, bytes.Contains(bodyBytes, originalBytes)) @@ -72,8 +75,8 @@ func TestLogger(t *testing.T) { observedLogger := zap.New(observedZapCore) res := httptest.NewRecorder() - error := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) - error.Write(res, observedLogger) + herr := httperrors.NewHTTPError(http.StatusBadRequest, "Error while parsing json", "The request body was malformed", nil, true) + herr.Write(res, observedLogger) require.Equal(t, 1, observedLogs.Len()) log := observedLogs.All()[0] @@ -88,32 +91,35 @@ func TestLogger(t *testing.T) { func TestNewErrorNotFound(t *testing.T) { ressourceName := "file" - error := httperrors.NewErrorNotFound(ressourceName, "main.css is not accessible") - assert.NotNil(t, error) - assert.False(t, error.Log()) - dto := new(dto.DTOHTTPError) - err := json.Unmarshal([]byte(error.ToJSON()), &dto) + herr := httperrors.NewErrorNotFound(ressourceName, "main.css is not accessible") + assert.NotNil(t, herr) + assert.False(t, herr.Log()) + + dto := new(dto.HTTPError) + err := json.Unmarshal([]byte(herr.ToJSON()), &dto) assert.NoError(t, err) assert.Equal(t, http.StatusText(http.StatusNotFound), dto.Status) assert.Equal(t, fmt.Sprintf("%s not found", ressourceName), dto.Error) } func TestNewInternalServerError(t *testing.T) { - error := httperrors.NewInternalServerError("casbin error", "the ressource is not accessible", nil) - assert.NotNil(t, error) - assert.True(t, error.Log()) - dto := new(dto.DTOHTTPError) - err := json.Unmarshal([]byte(error.ToJSON()), &dto) + herr := httperrors.NewInternalServerError("casbin error", "the ressource is not accessible", nil) + assert.NotNil(t, herr) + assert.True(t, herr.Log()) + + dto := new(dto.HTTPError) + err := json.Unmarshal([]byte(herr.ToJSON()), &dto) assert.NoError(t, err) assert.Equal(t, http.StatusText(http.StatusInternalServerError), dto.Status) } func TestNewUnauthorizedError(t *testing.T) { - error := httperrors.NewUnauthorizedError("json unmarshalling", "nil value whatever") - assert.NotNil(t, error) - assert.True(t, error.Log()) - dto := new(dto.DTOHTTPError) - err := json.Unmarshal([]byte(error.ToJSON()), &dto) + herr := httperrors.NewUnauthorizedError("json unmarshalling", "nil value whatever") + assert.NotNil(t, herr) + assert.True(t, herr.Log()) + + dto := new(dto.HTTPError) + err := json.Unmarshal([]byte(herr.ToJSON()), &dto) assert.NoError(t, err) assert.Equal(t, http.StatusText(http.StatusUnauthorized), dto.Status) } diff --git a/logger/log.go b/logger/log.go index 9170f8e4..f296498f 100644 --- a/logger/log.go +++ b/logger/log.go @@ -3,8 +3,9 @@ package logger import ( "log" - "github.com/ditrit/badaas/configuration" "go.uber.org/zap" + + "github.com/ditrit/badaas/configuration" ) const ( @@ -17,18 +18,22 @@ func NewLogger(conf configuration.LoggerConfiguration) *zap.Logger { var config zap.Config if conf.GetMode() == ProductionLogger { config = zap.NewProductionConfig() - log.Printf("Log mode use: %s\n", ProductionLogger) + log.Printf("Log mode use: %s\n", ProductionLogger) } else { config = zap.NewDevelopmentConfig() - log.Printf("Log mode use: %s\n", DevelopmentLogger) + log.Printf("Log mode use: %s\n", DevelopmentLogger) } + config.DisableStacktrace = true + logger, err := config.Build() if err != nil { panic(err) } + logger.Info("The logger was successfully initialized") + return logger } diff --git a/logger/log_test.go b/logger/log_test.go index 873a698c..2271131b 100644 --- a/logger/log_test.go +++ b/logger/log_test.go @@ -3,9 +3,10 @@ package logger import ( "testing" - configurationmocks "github.com/ditrit/badaas/mocks/configuration" "github.com/stretchr/testify/assert" "go.uber.org/zap/zapcore" + + configurationmocks "github.com/ditrit/badaas/mocks/configuration" ) func TestInitializeDevelopmentLogger(t *testing.T) { diff --git a/modules.go b/modules.go index ce6ec2b0..fa817321 100644 --- a/modules.go +++ b/modules.go @@ -50,6 +50,7 @@ func createSuperUser( logger.Sugar().Errorf("failed to save the super admin %w", err) return err } + logger.Sugar().Infof("The superadmin user already exists in database") } diff --git a/modules_test.go b/modules_test.go index 05ce5c53..91976ca8 100644 --- a/modules_test.go +++ b/modules_test.go @@ -18,10 +18,12 @@ func TestCreateSuperUser(t *testing.T) { logger := zap.New(core) initializationConfig := mocksConfiguration.NewInitializationConfiguration(t) initializationConfig.On("GetAdminPassword").Return("adminpassword") + userService := mockUserServices.NewUserService(t) userService. On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). Return(nil, nil) + err := createSuperUser( initializationConfig, logger, @@ -35,10 +37,12 @@ func TestCreateSuperUser_UserExists(t *testing.T) { logger := zap.New(core) initializationConfig := mocksConfiguration.NewInitializationConfiguration(t) initializationConfig.On("GetAdminPassword").Return("adminpassword") + userService := mockUserServices.NewUserService(t) userService. On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). Return(nil, errors.New("user already exist in database")) + err := createSuperUser( initializationConfig, logger, @@ -54,10 +58,12 @@ func TestCreateSuperUser_UserServiceError(t *testing.T) { logger := zap.New(core) initializationConfig := mocksConfiguration.NewInitializationConfiguration(t) initializationConfig.On("GetAdminPassword").Return("adminpassword") + userService := mockUserServices.NewUserService(t) userService. On("NewUser", "admin", "admin-no-reply@badaas.com", "adminpassword"). Return(nil, errors.New("email not valid")) + err := createSuperUser( initializationConfig, logger, diff --git a/orm/ModuleFx.go b/orm/ModuleFx.go index fe1cf33b..4e193776 100644 --- a/orm/ModuleFx.go +++ b/orm/ModuleFx.go @@ -50,10 +50,12 @@ func GetCRUDServiceModule[T Model]() fx.Option { // service fx.Provide(NewCRUDService[T, UIntID]), ) - default: + case KindNotModel: log.Printf("type %T is not a BaDaaS model\n", entity) return fx.Invoke(failNotBaDaaSModel()) } + + return nil } func failNotBaDaaSModel() error { diff --git a/orm/baseModels.go b/orm/baseModels.go index c6349ae3..bca7d931 100644 --- a/orm/baseModels.go +++ b/orm/baseModels.go @@ -36,6 +36,7 @@ func (model *UUIDModel) BeforeCreate(_ *gorm.DB) (err error) { if model.ID == NilUUID { model.ID = NewUUID() } + return nil } diff --git a/orm/db.go b/orm/db.go index 9b05a115..5b47cd75 100644 --- a/orm/db.go +++ b/orm/db.go @@ -31,7 +31,9 @@ func ConnectToDialector( retryTime time.Duration, ) (*gorm.DB, error) { var err error + var database *gorm.DB + for numberRetry := uint(0); numberRetry < retryAmount; numberRetry++ { database, err = gorm.Open(dialector, &gorm.Config{ Logger: gormzap.New(logger), diff --git a/persistence/gormdatabase/gormzap/gormzap.go b/persistence/gormdatabase/gormzap/gormzap.go index 0f7b9563..d380fc6a 100644 --- a/persistence/gormdatabase/gormzap/gormzap.go +++ b/persistence/gormdatabase/gormzap/gormzap.go @@ -13,6 +13,8 @@ import ( gormlogger "gorm.io/gorm/logger" ) +const defaultSlowThreshold = 100 * time.Millisecond + // This type implement the [gorm.io/gorm/logger.Interface] interface. // It is to be used as a replacement for the original logger type Logger struct { @@ -28,7 +30,7 @@ func New(zapLogger *zap.Logger) gormlogger.Interface { return Logger{ ZapLogger: zapLogger, LogLevel: gormlogger.Info, - SlowThreshold: 100 * time.Millisecond, + SlowThreshold: defaultSlowThreshold, SkipCallerLookup: true, IgnoreRecordNotFoundError: true, } @@ -55,6 +57,7 @@ func (l Logger) Info(_ context.Context, str string, args ...interface{}) { if l.LogLevel < gormlogger.Info { return } + l.logger().Sugar().Debugf(str, args...) } @@ -63,6 +66,7 @@ func (l Logger) Warn(_ context.Context, str string, args ...interface{}) { if l.LogLevel < gormlogger.Warn { return } + l.logger().Sugar().Warnf(str, args...) } @@ -71,6 +75,7 @@ func (l Logger) Error(_ context.Context, str string, args ...interface{}) { if l.LogLevel < gormlogger.Error { return } + l.logger().Sugar().Errorf(str, args...) } @@ -82,6 +87,7 @@ func (l Logger) Trace(_ context.Context, begin time.Time, fc func() (string, int elapsed := time.Since(begin) sql, rows := fc() + switch { case err != nil && l.LogLevel >= gormlogger.Error && (!l.IgnoreRecordNotFoundError || !errors.Is(err, gorm.ErrRecordNotFound)): l.logger().Error("trace", zap.Error(err), zap.Duration("elapsed", elapsed), zap.Int64("rows", rows), zap.String("sql", sql)) @@ -101,6 +107,7 @@ var ( func (l Logger) logger() *zap.Logger { for i := 2; i < 15; i++ { _, file, _, ok := runtime.Caller(i) + switch { case !ok: case strings.HasSuffix(file, "_test.go"): @@ -110,5 +117,6 @@ func (l Logger) logger() *zap.Logger { return l.ZapLogger.WithOptions(zap.AddCallerSkip(i)) } } + return l.ZapLogger } diff --git a/persistence/gormdatabase/helpers.go b/persistence/gormdatabase/helpers.go deleted file mode 100644 index c71650a5..00000000 --- a/persistence/gormdatabase/helpers.go +++ /dev/null @@ -1,17 +0,0 @@ -package gormdatabase - -import "github.com/jackc/pgconn" - -func IsDuplicateKeyError(err error) bool { - // unique_violation code is equals to 23505 - return isPostgresError(err, "23505") -} - -func isPostgresError(err error, errCode string) bool { - postgresError, ok := err.(*pgconn.PgError) - if ok { - return postgresError.Code == errCode - - } - return false -} diff --git a/persistence/gormdatabase/helpers_test.go b/persistence/gormdatabase/helpers_test.go deleted file mode 100644 index 59cb2d6e..00000000 --- a/persistence/gormdatabase/helpers_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package gormdatabase - -import ( - "errors" - "testing" - - "github.com/jackc/pgconn" - "github.com/stretchr/testify/assert" -) - -func TestIsDuplicateError(t *testing.T) { - assert.False(t, IsDuplicateKeyError(errors.New("voila"))) - assert.False(t, IsDuplicateKeyError(&pgconn.PgError{ - Code: "235252551514", - })) - assert.True(t, IsDuplicateKeyError(&pgconn.PgError{ - Code: "23505", - })) -} - -func Test_isPostgresError(t *testing.T) { - var postgresErrorAsError error = &pgconn.PgError{Code: "1234"} - assert.True(t, isPostgresError( - postgresErrorAsError, - "1234", - )) - postgresErrorAsError = &pgconn.PgError{Code: ""} - assert.False(t, isPostgresError( - postgresErrorAsError, - "1234", - )) - assert.False(t, isPostgresError(errors.New("a classic error"), "1234")) -} diff --git a/persistence/models/dto/HTTPError.go b/persistence/models/dto/HTTPError.go index 0e352145..e182eadd 100644 --- a/persistence/models/dto/HTTPError.go +++ b/persistence/models/dto/HTTPError.go @@ -1,9 +1,9 @@ package dto -// Data Transfert Object Package +// Data Transfer Object Package // Describe the HTTP Error payload -type DTOHTTPError struct { +type HTTPError struct { Error string `json:"err"` Message string `json:"msg"` Status string `json:"status"` diff --git a/persistence/models/dto/LoginSuccess.go b/persistence/models/dto/LoginSuccess.go index 3601e975..87492bad 100644 --- a/persistence/models/dto/LoginSuccess.go +++ b/persistence/models/dto/LoginSuccess.go @@ -1,8 +1,8 @@ package dto -// DTOLoginSuccess is a dto returned to the client when the authentication is successful. -type DTOLoginSuccess struct { +// LoginSuccess is a dto returned to the client when the authentication is successful. +type LoginSuccess struct { Email string `json:"email"` ID string `json:"id"` Username string `json:"username"` -} \ No newline at end of file +} diff --git a/router/middlewares/middlewareJson.go b/router/middlewares/middlewareJson.go index c503f68e..b8d0ffd9 100644 --- a/router/middlewares/middlewareJson.go +++ b/router/middlewares/middlewareJson.go @@ -37,9 +37,11 @@ func (controller *jsonControllerImpl) Wrap(handler JSONHandler) func(response ht herr.Write(response, controller.logger) return } + if object == nil { return } + payload, err := json.Marshal(object) if err != nil { httperrors.NewInternalServerError( @@ -47,9 +49,12 @@ func (controller *jsonControllerImpl) Wrap(handler JSONHandler) func(response ht "Can't marshall the object returned by the JSON handler", nil, ).Write(response, controller.logger) + return } + response.Header().Set("Content-Type", "application/json") + _, err = response.Write(payload) if err != nil { controller.logger.Error( diff --git a/router/middlewares/middlewareLogger.go b/router/middlewares/middlewareLogger.go index 2d792e4b..88e9a7ec 100644 --- a/router/middlewares/middlewareLogger.go +++ b/router/middlewares/middlewareLogger.go @@ -42,6 +42,7 @@ func NewMiddlewareLogger( if err != nil { return nil, fmt.Errorf("failed to build jinja template from configuration %w", err) } + return &middlewareLoggerImpl{ logger: logger, template: requestLogTemplate, @@ -66,5 +67,6 @@ func getLogMessage(template *exec.Template, r *http.Request) string { "method": r.Method, "url": r.URL.Path, }) + return result } diff --git a/router/middlewares/middlewarelogger_test.go b/router/middlewares/middlewarelogger_test.go index 4d0b2bbe..181816eb 100644 --- a/router/middlewares/middlewarelogger_test.go +++ b/router/middlewares/middlewarelogger_test.go @@ -6,11 +6,12 @@ import ( "net/url" "testing" - configurationmocks "github.com/ditrit/badaas/mocks/configuration" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" + + configurationmocks "github.com/ditrit/badaas/mocks/configuration" ) func TestMiddlewareLogger(t *testing.T) { @@ -18,7 +19,7 @@ func TestMiddlewareLogger(t *testing.T) { observedLogger := zap.New(observedZapCore) req := &http.Request{ - Method: "GET", + Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "localhost", @@ -26,7 +27,8 @@ func TestMiddlewareLogger(t *testing.T) { }, } res := httptest.NewRecorder() - var actuallyRunned bool = false + + actuallyRunned := false // create a handler to use as "next" which will verify the request nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { actuallyRunned = true diff --git a/router/routes_test.go b/router/routes_test.go index fb8b5161..31dbe177 100644 --- a/router/routes_test.go +++ b/router/routes_test.go @@ -31,7 +31,7 @@ func TestAddInfoRoutes(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( - "GET", + http.MethodGet, "/info", nil, ) @@ -61,7 +61,7 @@ func TestAddLoginRoutes(t *testing.T) { response := httptest.NewRecorder() request := httptest.NewRequest( - "POST", + http.MethodPost, "/login", nil, ) diff --git a/server.go b/server.go index e27b767d..f1272b63 100644 --- a/server.go +++ b/server.go @@ -50,6 +50,7 @@ func newHTTPServer( )(router) srv := createServer(handler, httpServerConfig) + lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { ln, err := net.Listen("tcp", srv.Addr) @@ -57,7 +58,10 @@ func newHTTPServer( return err } logger.Sugar().Infof("Ready to serve at %s", srv.Addr) - go srv.Serve(ln) + go func() { + _ = srv.Serve(ln) + }() + return nil }, OnStop: func(ctx context.Context) error { diff --git a/services/auth/protocols/basicauth/basic.go b/services/auth/protocols/basicauth/basic.go index ac2e944d..3c202048 100644 --- a/services/auth/protocols/basicauth/basic.go +++ b/services/auth/protocols/basicauth/basic.go @@ -14,6 +14,7 @@ func SaltAndHashPassword(password string) []byte { []byte(password), cost, ) + return bytes } diff --git a/services/auth/protocols/basicauth/basic_test.go b/services/auth/protocols/basicauth/basic_test.go index c788a018..52e3f6e5 100644 --- a/services/auth/protocols/basicauth/basic_test.go +++ b/services/auth/protocols/basicauth/basic_test.go @@ -3,8 +3,9 @@ package basicauth_test import ( "testing" - "github.com/ditrit/badaas/services/auth/protocols/basicauth" "github.com/stretchr/testify/assert" + + "github.com/ditrit/badaas/services/auth/protocols/basicauth" ) func TestSaltAndHashPassword(t *testing.T) { @@ -18,5 +19,4 @@ func TestCheckUserPassword(t *testing.T) { hash := basicauth.SaltAndHashPassword(password) assert.True(t, basicauth.CheckUserPassword(hash, password), "the password and it's hash should match") assert.False(t, basicauth.CheckUserPassword(hash, "wrong password"), "the password and it's hash should match") - } diff --git a/services/sessionservice/session.go b/services/sessionservice/session.go index bcf67cb6..c3a61dd4 100644 --- a/services/sessionservice/session.go +++ b/services/sessionservice/session.go @@ -59,6 +59,7 @@ func NewSessionService( db: db, } sessionService.init() + return sessionService } @@ -69,6 +70,7 @@ func (sessionService *sessionServiceImpl) IsValid(sessionUUID orm.UUID) (bool, * if sessionInstance == nil { return false, nil } + return true, makeSessionClaims(sessionInstance) } @@ -113,6 +115,7 @@ func (sessionService *sessionServiceImpl) add(session *models.Session) error { // Initialize the session service func (sessionService *sessionServiceImpl) init() { sessionService.cache = make(map[orm.UUID]*models.Session) + go func() { for { sessionService.removeExpired() @@ -138,6 +141,7 @@ func (sessionService *sessionServiceImpl) pullFromDB() { for _, sessionFromDatabase := range sessionsFromDatabase { newSessionCache[sessionFromDatabase.ID] = sessionFromDatabase } + sessionService.cache = newSessionCache sessionService.logger.Debug( "Pulled sessions from DB", @@ -151,6 +155,7 @@ func (sessionService *sessionServiceImpl) removeExpired() { defer sessionService.mutex.Unlock() var i int + for sessionUUID, session := range sessionService.cache { if session.IsExpired() { // Delete the session in the database @@ -165,6 +170,7 @@ func (sessionService *sessionServiceImpl) removeExpired() { i++ } } + sessionService.logger.Debug( "Removed expired session", zap.Int("expiredSessionCount", i), @@ -177,6 +183,7 @@ func (sessionService *sessionServiceImpl) delete(session *models.Session) httper defer sessionService.mutex.Unlock() sessionUUID := session.ID + err := sessionService.sessionRepository.Delete(sessionService.db, session) if err != nil { return httperrors.NewInternalServerError( @@ -185,7 +192,9 @@ func (sessionService *sessionServiceImpl) delete(session *models.Session) httper err, ) } + delete(sessionService.cache, sessionUUID) + return nil } @@ -193,6 +202,7 @@ func (sessionService *sessionServiceImpl) delete(session *models.Session) httper func (sessionService *sessionServiceImpl) RollSession(sessionUUID orm.UUID) httperrors.HTTPError { rollInterval := sessionService.sessionConfiguration.GetRollDuration() sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() + session := sessionService.get(sessionUUID) if session == nil { // no session to roll, no error @@ -208,6 +218,7 @@ func (sessionService *sessionServiceImpl) RollSession(sessionUUID orm.UUID) http defer sessionService.mutex.Unlock() session.ExpiresAt = session.ExpiresAt.Add(sessionDuration) + err := sessionService.sessionRepository.Save(sessionService.db, session) if err != nil { return httperrors.NewDBError(err) @@ -225,10 +236,12 @@ func (sessionService *sessionServiceImpl) RollSession(sessionUUID orm.UUID) http func (sessionService *sessionServiceImpl) LogUserIn(user *models.User) (*models.Session, error) { sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() session := models.NewSession(user.ID, sessionDuration) + err := sessionService.add(session) if err != nil { return nil, err } + return session, nil } diff --git a/services/sessionservice/session_test.go b/services/sessionservice/session_test.go index 42bf0b02..fd25a6a9 100644 --- a/services/sessionservice/session_test.go +++ b/services/sessionservice/session_test.go @@ -51,6 +51,7 @@ func TestLogInUser(t *testing.T) { sessionRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) sessionConfigurationMock.On("GetSessionDuration").Return(time.Minute) + user := &models.User{ Username: "bob", Email: "bob@email.com", @@ -85,6 +86,7 @@ func TestLogInUserDbError(t *testing.T) { func TestIsValid(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) + uuidSample := orm.NewUUID() session := &models.Session{ UUIDModel: orm.UUIDModel{ @@ -110,6 +112,7 @@ func TestIsValid_SessionNotFound(t *testing.T) { sessionRepositoryMock. On("GetByID", gormDB, mock.Anything). Return(nil, errors.New("not-found")) + uuidSample := orm.NewUUID() isValid, _ := service.IsValid(uuidSample) require.False(t, isValid) @@ -118,6 +121,7 @@ func TestIsValid_SessionNotFound(t *testing.T) { func TestLogOutUser(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock.On("Delete", gormDB, mock.Anything).Return(nil) + uuidSample := orm.NewUUID() session := &models.Session{ UUIDModel: orm.UUIDModel{ @@ -137,6 +141,7 @@ func TestLogOutUserDbError(t *testing.T) { sessionRepositoryMock. On("Delete", gormDB, mock.Anything). Return(errors.New("db errors")) + uuidSample := orm.NewUUID() session := &models.Session{ @@ -177,9 +182,11 @@ func TestLogOutUser_SessionNotFound(t *testing.T) { func TestRollSession(t *testing.T) { sessionRepositoryMock, service, _, sessionConfigurationMock := setupTest(t) sessionRepositoryMock.On("Save", gormDB, mock.Anything).Return(nil) + sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) + uuidSample := orm.NewUUID() originalExpirationTime := time.Now().Add(sessionDuration / 5) session := &models.Session{ @@ -200,6 +207,7 @@ func TestRollSession_Expired(t *testing.T) { sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) + uuidSample := orm.NewUUID() originalExpirationTime := time.Now().Add(-time.Hour) session := &models.Session{ @@ -276,8 +284,8 @@ func Test_pullFromDB(t *testing.T) { func Test_pullFromDB_repoError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - sessionRepositoryMock.On("Query", gormDB).Return(nil, httperrors.AnError) - assert.PanicsWithError(t, httperrors.AnError.Error(), func() { service.pullFromDB() }) + sessionRepositoryMock.On("Query", gormDB).Return(nil, httperrors.ErrForTests) + assert.PanicsWithError(t, httperrors.ErrForTests.Error(), func() { service.pullFromDB() }) } func Test_removeExpired(t *testing.T) { @@ -293,6 +301,7 @@ func Test_removeExpired(t *testing.T) { sessionRepositoryMock. On("Delete", gormDB, session). Return(nil) + service.cache[uuidSample] = session service.removeExpired() @@ -317,7 +326,8 @@ func Test_removeExpired_RepositoryError(t *testing.T) { } sessionRepositoryMock. On("Delete", gormDB, session). - Return(httperrors.AnError) + Return(httperrors.ErrForTests) + service.cache[uuidSample] = session assert.Panics(t, func() { service.removeExpired() }) diff --git a/services/sessionservice/sessionctx.go b/services/sessionservice/sessionctx.go index ec7a70ac..80bd6727 100644 --- a/services/sessionservice/sessionctx.go +++ b/services/sessionservice/sessionctx.go @@ -41,5 +41,6 @@ func GetSessionClaimsFromContext(ctx context.Context) *SessionClaims { if !ok { panic("could not extract claims from context") } + return claims } diff --git a/services/sessionservice/sessionctx_test.go b/services/sessionservice/sessionctx_test.go index 129dc101..83be8b35 100644 --- a/services/sessionservice/sessionctx_test.go +++ b/services/sessionservice/sessionctx_test.go @@ -20,5 +20,6 @@ func TestSessionCtx(t *testing.T) { func TestSessionCtxPanic(t *testing.T) { ctx := context.Background() + assert.Panics(t, func() { GetSessionClaimsFromContext(ctx) }) } diff --git a/services/userservice/userservice.go b/services/userservice/userservice.go index 24387de2..d7817bdd 100644 --- a/services/userservice/userservice.go +++ b/services/userservice/userservice.go @@ -57,6 +57,7 @@ func (userService *userServiceImpl) NewUser(username, email, password string) (* Email: sanitizedEmail, Password: basicauth.SaltAndHashPassword(password), } + err = userService.userRepository.Create(userService.db, u) if err != nil { return nil, err diff --git a/services/userservice/userservice_test.go b/services/userservice/userservice_test.go index e76cc2cf..2e004e93 100644 --- a/services/userservice/userservice_test.go +++ b/services/userservice/userservice_test.go @@ -59,6 +59,7 @@ func TestNewUserServiceDatabaseError(t *testing.T) { ).Return( errors.New("database error"), ) + userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@email.com", "1234") assert.Error(t, err) diff --git a/test_e2e/go.mod b/test_e2e/go.mod index f43e61ec..228afd5e 100644 --- a/test_e2e/go.mod +++ b/test_e2e/go.mod @@ -33,11 +33,7 @@ require ( github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.0 // indirect - github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.3.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect diff --git a/test_e2e/go.sum b/test_e2e/go.sum index c0628486..448d4350 100644 --- a/test_e2e/go.sum +++ b/test_e2e/go.sum @@ -68,16 +68,13 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw= github.com/cucumber/godog v0.12.5 h1:FZIy6VCfMbmGHts9qd6UjBMT9abctws/pQYO/ZcwOVs= @@ -232,47 +229,12 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -288,29 +250,21 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALr github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 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 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -363,20 +317,13 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -405,7 +352,6 @@ github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -416,7 +362,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -431,8 +376,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -440,7 +383,6 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -452,26 +394,18 @@ go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -509,7 +443,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -527,7 +460,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -548,8 +480,6 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -569,7 +499,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -580,17 +509,13 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -616,17 +541,11 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -636,7 +555,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -651,14 +569,12 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -694,9 +610,6 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -796,7 +709,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/testintegration/asserts.go b/testintegration/asserts.go index 7f717628..6e514505 100644 --- a/testintegration/asserts.go +++ b/testintegration/asserts.go @@ -22,6 +22,7 @@ func EqualList[T any](ts *suite.Suite, expectedList, actualList []T) { break } } + if j == expectedLen { for _, element := range actualList { log.Println(element) diff --git a/testintegration/crudServiceCommon.go b/testintegration/crudServiceCommon.go index 1fd63488..67a506e2 100644 --- a/testintegration/crudServiceCommon.go +++ b/testintegration/crudServiceCommon.go @@ -52,6 +52,7 @@ func (ts *CRUDServiceCommonIntTestSuite) createSeller(name string, company *mode if company != nil { companyID = &company.ID } + entity := &models.Seller{ Name: name, CompanyID: companyID, diff --git a/testintegration/models/models.go b/testintegration/models/models.go index 0253a68c..9cd06845 100644 --- a/testintegration/models/models.go +++ b/testintegration/models/models.go @@ -42,6 +42,7 @@ func (s *MultiString) Scan(src interface{}) error { case []byte: str := string(typedSrc) *s = strings.Split(str, ",") + return nil default: return fmt.Errorf("failed to scan multistring field - source is not a string, is %T", src) @@ -52,6 +53,7 @@ func (s MultiString) Value() (driver.Value, error) { if len(s) == 0 { return nil, nil } + return strings.Join(s, ","), nil } diff --git a/testintegration/orm_test.go b/testintegration/orm_test.go index 1a11c6ec..213ad174 100644 --- a/testintegration/orm_test.go +++ b/testintegration/orm_test.go @@ -79,7 +79,6 @@ func runORMTestSuites( tsJoinConditions *JoinConditionsIntTestSuite, tsPreloadConditions *PreloadConditionsIntTestSuite, tsOperators *OperatorsIntTestSuite, - db *gorm.DB, shutdowner fx.Shutdowner, ) { suite.Run(tGlobal, tsCRUDRepository) diff --git a/testintegration/preload_conditions_test.go b/testintegration/preload_conditions_test.go index 931bae4b..ee294c04 100644 --- a/testintegration/preload_conditions_test.go +++ b/testintegration/preload_conditions_test.go @@ -766,7 +766,7 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributes() { err := ts.db.Save(seller1).Error ts.Nil(err) - university2 := ts.createUniversity("uni1") + university2 := ts.createUniversity("uni2") seller2 := ts.createSeller("2", company) seller2.University = university2 err = ts.db.Save(seller2).Error @@ -804,7 +804,7 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadMultipleListsAndNestedAttrib err := ts.db.Save(seller1).Error ts.Nil(err) - university2 := ts.createUniversity("uni1") + university2 := ts.createUniversity("uni2") seller2 := ts.createSeller("2", company1) seller2.University = university2 err = ts.db.Save(seller2).Error diff --git a/utils/time_test.go b/utils/time_test.go index 578290ce..6261ca46 100644 --- a/utils/time_test.go +++ b/utils/time_test.go @@ -11,19 +11,19 @@ func TestIntToSecond(t *testing.T) { assert.Equal( t, IntToSecond(20), - time.Duration(20*time.Second), + 20*time.Second, "the duration should be equals", ) assert.Equal( t, IntToSecond(-5), - time.Duration(-5*time.Second), + -5*time.Second, "the duration should be equals", ) assert.Equal( t, IntToSecond(3600), - time.Duration(time.Hour), + time.Hour, "the duration should be equals", ) } diff --git a/validators/email.go b/validators/email.go index 99d9d1e1..b9984c2b 100644 --- a/validators/email.go +++ b/validators/email.go @@ -8,5 +8,6 @@ func ValidEmail(email string) (string, error) { if err != nil { return "", err } + return addr.Address, nil } diff --git a/validators/email_test.go b/validators/email_test.go index c41f0853..ecd84f90 100644 --- a/validators/email_test.go +++ b/validators/email_test.go @@ -3,8 +3,9 @@ package validator_test import ( "testing" - validator "github.com/ditrit/badaas/validators" "github.com/stretchr/testify/assert" + + validator "github.com/ditrit/badaas/validators" ) func TestValidEmail(t *testing.T) { From 5ebe2b769b42ab528b13771b3057975b16913b6a Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 16:12:55 +0200 Subject: [PATCH 80/90] add test e2e to linting --- .github/workflows/CI.yml | 11 ++++++++++- Makefile | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 818afcd6..4c611055 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -31,13 +31,22 @@ jobs: with: go-version: '^1.18' cache: true - - name: golangci-lint + - name: badaas lint uses: golangci/golangci-lint-action@v3 with: version: v1.53.3 skip-cache: true skip-pkg-cache: true skip-build-cache: true + - name: teste2e lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.53.3 + working-directory: test_e2e + args: --config=../.golangci.yml + skip-cache: true + skip-pkg-cache: true + skip-build-cache: true unit-tests: name: Unit tests diff --git a/Makefile b/Makefile index 2d29efc2..9543bfda 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ install_dependencies: lint: golangci-lint run + cd test_e2e && golangci-lint run --config ../.golangci.yml test_unit: gotestsum --format pkgname $(PATHS) From 90829cfcde4744bb81c9509d335d5e9e87ea4feb Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 16:13:11 +0200 Subject: [PATCH 81/90] fix new linting errors in test e2e --- test_e2e/badaas_e2e_test.go | 6 +- test_e2e/go.mod | 2 +- test_e2e/http_support_test.go | 144 +++++++++++++++++++++------------- 3 files changed, 95 insertions(+), 57 deletions(-) diff --git a/test_e2e/badaas_e2e_test.go b/test_e2e/badaas_e2e_test.go index 12cd1c4c..3da722fc 100644 --- a/test_e2e/badaas_e2e_test.go +++ b/test_e2e/badaas_e2e_test.go @@ -77,13 +77,15 @@ func TestMain(_ *testing.M) { func InitializeScenario(ctx *godog.ScenarioContext) { t := &TestContext{} + jar, err := cookiejar.New(nil) if err != nil { panic(err) } + t.httpClient = &http.Client{ Transport: http.DefaultTransport, - Timeout: time.Duration(5 * time.Second), + Timeout: 5 * time.Second, Jar: jar, } @@ -107,5 +109,5 @@ func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^I request "(.+)"$`, t.requestGet) ctx.Step(`^status code is "(\d+)"$`, t.assertStatusCode) ctx.Step(`^response field "(.+)" is "(.+)"$`, t.assertResponseFieldIsEquals) - ctx.Step(`^I request "(.+)" with method "(.+)" with json$`, t.requestWithJson) + ctx.Step(`^I request "(.+)" with method "(.+)" with json$`, t.requestWithJSON) } diff --git a/test_e2e/go.mod b/test_e2e/go.mod index 228afd5e..3bab21bb 100644 --- a/test_e2e/go.mod +++ b/test_e2e/go.mod @@ -7,6 +7,7 @@ replace github.com/ditrit/badaas => ../ require ( github.com/Masterminds/semver/v3 v3.1.1 github.com/cucumber/godog v0.12.5 + github.com/cucumber/messages-go/v16 v16.0.1 github.com/ditrit/badaas v0.0.0 github.com/elliotchance/pie/v2 v2.7.0 github.com/spf13/pflag v1.0.5 @@ -17,7 +18,6 @@ require ( require ( github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect - github.com/cucumber/messages-go/v16 v16.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/ditrit/verdeter v0.4.0 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect diff --git a/test_e2e/http_support_test.go b/test_e2e/http_support_test.go index a53ab405..dedd9a2e 100644 --- a/test_e2e/http_support_test.go +++ b/test_e2e/http_support_test.go @@ -9,34 +9,35 @@ import ( "strings" "github.com/cucumber/godog" + "github.com/cucumber/messages-go/v16" "github.com/elliotchance/pie/v2" ) -const BaseUrl = "http://localhost:8000" +const BaseURL = "http://localhost:8000" func (t *TestContext) requestGet(url string) error { return t.request(url, http.MethodGet, nil, nil) } -func (t *TestContext) requestWithJson(url, method string, jsonTable *godog.Table) error { +func (t *TestContext) requestWithJSON(url, method string, jsonTable *godog.Table) error { return t.request(url, method, nil, jsonTable) } func (t *TestContext) request(url, method string, query map[string]string, jsonTable *godog.Table) error { var payload io.Reader + var err error + if jsonTable != nil { - payload, err = buildJSONFromTable(jsonTable) - if err != nil { - return err - } + payload = buildJSONFromTable(jsonTable) } method, err = checkMethod(method) if err != nil { return err } - request, err := http.NewRequest(method, BaseUrl+url, payload) + + request, err := http.NewRequest(method, BaseURL+url, payload) if err != nil { return fmt.Errorf("failed to build request ERROR=%s", err.Error()) } @@ -45,13 +46,17 @@ func (t *TestContext) request(url, method string, query map[string]string, jsonT for k, v := range query { q.Add(k, v) } + request.URL.RawQuery = q.Encode() response, err := t.httpClient.Do(request) if err != nil { return fmt.Errorf("failed to run request ERROR=%s", err.Error()) } + t.storeResponseInContext(response) + response.Body.Close() + return nil } @@ -68,6 +73,7 @@ func (t *TestContext) assertStatusCode(expectedStatusCode int) error { if t.statusCode != expectedStatusCode { return fmt.Errorf("expect status code %d but is %d", expectedStatusCode, t.statusCode) } + return nil } @@ -80,6 +86,7 @@ func (t *TestContext) assertResponseFieldIsEquals(field string, expectedValue st if !present { return fmt.Errorf("expected response field %s to be %s but it is not present", field, expectedValue) } + jsonMap = intValue.(map[string]any) } @@ -104,12 +111,14 @@ func assertValue(value any, expectedValue string) bool { if err != nil { panic(err) } + return expectedValueInt == value case float64: expectedValueFloat, err := strconv.ParseFloat(expectedValue, 64) if err != nil { panic(err) } + return expectedValueFloat == value default: panic("unsupported format") @@ -119,60 +128,85 @@ func assertValue(value any, expectedValue string) bool { // build a map from a godog.Table func buildMapFromTable(table *godog.Table) (map[string]any, error) { data := make(map[string]any, 0) - for indexRow, row := range table.Rows { - if indexRow == 0 { - for indexCell, cell := range row.Cells { - if cell.Value != []string{"key", "value", "type"}[indexCell] { - return nil, fmt.Errorf("should have %q as first line of the table", "| key | value | type |") - } - } - } else { - key := row.Cells[0].Value - valueAsString := row.Cells[1].Value - valueType := row.Cells[2].Value - - switch valueType { - case stringValueType: - data[key] = valueAsString - case booleanValueType: - boolean, err := strconv.ParseBool(valueAsString) - if err != nil { - return nil, fmt.Errorf("can't parse %q as boolean for key %q", valueAsString, key) - } - data[key] = boolean - case integerValueType: - integer, err := strconv.ParseInt(valueAsString, 10, 64) - if err != nil { - return nil, fmt.Errorf("can't parse %q as integer for key %q", valueAsString, key) - } - data[key] = integer - case floatValueType: - floatingNumber, err := strconv.ParseFloat(valueAsString, 64) - if err != nil { - return nil, fmt.Errorf("can't parse %q as float for key %q", valueAsString, key) - } - data[key] = floatingNumber - case jsonValueType: - jsonMap := map[string]string{} - err := json.Unmarshal([]byte(valueAsString), &jsonMap) - if err != nil { - return nil, fmt.Errorf("can't parse %q as json for key %q", valueAsString, key) - } - data[key] = jsonMap - case nullValueType: - data[key] = nil - default: - return nil, fmt.Errorf("type %q does not exists, please use %v", valueType, []string{stringValueType, booleanValueType, integerValueType, floatValueType, nullValueType}) - } + err := verifyHeader(table.Rows[0]) + if err != nil { + return nil, err + } + + for _, row := range table.Rows[1:] { + key := row.Cells[0].Value + valueAsString := row.Cells[1].Value + valueType := row.Cells[2].Value + + value, err := getTableValue(key, valueAsString, valueType) + if err != nil { + return nil, err } + + data[key] = value } return data, nil } +// Verifies that the header row of a table has the correct format +func verifyHeader(row *messages.PickleTableRow) error { + for indexCell, cell := range row.Cells { + if cell.Value != []string{"key", "value", "type"}[indexCell] { + return fmt.Errorf("should have %q as first line of the table", "| key | value | type |") + } + } + + return nil +} + +// Returns the value present in a table casted to the correct type +func getTableValue(key, valueAsString, valueType string) (any, error) { + switch valueType { + case stringValueType: + return valueAsString, nil + case booleanValueType: + boolean, err := strconv.ParseBool(valueAsString) + if err != nil { + return nil, fmt.Errorf("can't parse %q as boolean for key %q", valueAsString, key) + } + + return boolean, nil + case integerValueType: + integer, err := strconv.ParseInt(valueAsString, 10, 64) + if err != nil { + return nil, fmt.Errorf("can't parse %q as integer for key %q", valueAsString, key) + } + + return integer, nil + case floatValueType: + floatingNumber, err := strconv.ParseFloat(valueAsString, 64) + if err != nil { + return nil, fmt.Errorf("can't parse %q as float for key %q", valueAsString, key) + } + + return floatingNumber, nil + case jsonValueType: + jsonMap := map[string]string{} + + err := json.Unmarshal([]byte(valueAsString), &jsonMap) + if err != nil { + return nil, fmt.Errorf("can't parse %q as json for key %q", valueAsString, key) + } + + return jsonMap, nil + default: + return nil, fmt.Errorf( + "type %q does not exists, please use %v", + valueType, + []string{stringValueType, booleanValueType, integerValueType, floatValueType}, + ) + } +} + // build a json payload in the form of a reader from a godog.Table -func buildJSONFromTable(table *godog.Table) (io.Reader, error) { +func buildJSONFromTable(table *godog.Table) io.Reader { data, err := buildMapFromTable(table) if err != nil { panic("should not return an error") @@ -182,7 +216,8 @@ func buildJSONFromTable(table *godog.Table) (io.Reader, error) { if err != nil { panic("should not return an error") } - return strings.NewReader(string(bytes)), nil + + return strings.NewReader(string(bytes)) } const ( @@ -208,6 +243,7 @@ func checkMethod(method string) (string, error) { http.MethodTrace, } sanitizedMethod := strings.TrimSpace(strings.ToUpper(method)) + if !pie.Contains( allowedMethods, sanitizedMethod, From 0edd34c60d30102e1204f376a0081369c5dbde10 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Wed, 9 Aug 2023 10:02:27 +0200 Subject: [PATCH 82/90] badaas-orm module reorganization --- controllers/basicAuth.go | 4 +- controllers/basicAuth_test.go | 8 +- mocks/orm/BadaasID.go | 25 - mocks/orm/CRUDRepository.go | 27 +- mocks/orm/CRUDService.go | 24 +- mocks/orm/IJoinCondition.go | 75 --- mocks/orm/{ => condition}/Condition.go | 18 +- mocks/orm/condition/JoinCondition.go | 77 +++ mocks/orm/{ => condition}/WhereCondition.go | 40 +- mocks/orm/iFieldIdentifier.go | 74 --- mocks/orm/{ModelID.go => model/ID.go} | 14 +- mocks/orm/{ => model}/Model.go | 0 mocks/orm/{ => operator}/DynamicOperator.go | 34 +- mocks/orm/{ => operator}/Operator.go | 25 +- mocks/orm/query/IFieldIdentifier.go | 74 +++ .../services/sessionservice/SessionService.go | 16 +- orm/ModuleFx.go | 32 +- orm/condition.go | 523 ------------------ orm/condition/collection_preload_condition.go | 66 +++ orm/condition/condition.go | 42 ++ orm/condition/connection_condition.go | 63 +++ orm/condition/container_condition.go | 51 ++ orm/condition/errors.go | 36 ++ orm/condition/field_condition.go | 53 ++ orm/condition/invalid_condition.go | 32 ++ orm/condition/join_condition.go | 206 +++++++ orm/condition/logical.go | 10 + orm/condition/preload_condition.go | 31 ++ orm/condition/where_condition.go | 38 ++ orm/crudRepository.go | 67 +-- orm/crudService.go | 19 +- orm/dynamic/operator.go | 11 +- orm/dynamic/operators.go | 43 +- orm/errors.go | 67 --- orm/errors/errors.go | 22 + orm/logical.go | 22 + orm/{baseModels.go => model/models.go} | 4 +- orm/{ => model}/uuid.go | 2 +- orm/{ => model}/uuid_test.go | 10 +- orm/operator/errors.go | 27 + orm/operator/operator.go | 25 + orm/operator/predicate_operator.go | 28 + .../value_operator.go} | 70 +-- orm/operators.go | 87 +-- orm/orm.go | 4 +- orm/preload.go | 41 -- orm/preload/preload.go | 42 ++ orm/query.go | 134 ----- orm/query/field_identifier.go | 36 ++ orm/query/query.go | 98 ++++ orm/query/table.go | 53 ++ orm/unsafe/condition.go | 26 +- orm/unsafe/operators.go | 50 +- persistence/models/Session.go | 10 +- persistence/models/Session_test.go | 6 +- persistence/models/User.go | 19 +- .../middlewares/middlewareAuthentication.go | 4 +- services/ModuleFx.go | 5 +- services/sessionservice/session.go | 23 +- services/sessionservice/session_test.go | 100 ++-- services/sessionservice/sessionctx.go | 6 +- services/sessionservice/sessionctx_test.go | 6 +- services/userservice/userservice.go | 5 +- services/userservice/userservice_test.go | 16 +- .../conditions/bicycle_conditions.go | 73 +-- .../conditions/brand_conditions.go | 52 +- .../conditions/child_conditions.go | 101 ++-- testintegration/conditions/city_conditions.go | 73 +-- .../conditions/company_conditions.go | 58 +- .../conditions/country_conditions.go | 64 +-- .../conditions/employee_conditions.go | 73 +-- .../conditions/parent1_conditions.go | 64 +-- .../conditions/parent2_conditions.go | 64 +-- .../conditions/parent_parent_conditions.go | 61 +- .../conditions/person_conditions.go | 52 +- .../conditions/phone_conditions.go | 73 +-- .../conditions/product_conditions.go | 142 ++--- testintegration/conditions/sale_conditions.go | 101 ++-- .../conditions/seller_conditions.go | 92 ++- .../conditions/university_conditions.go | 52 +- testintegration/crudRepository.go | 10 +- testintegration/crudServiceCommon.go | 4 +- testintegration/join_conditions_test.go | 42 +- testintegration/models/badaas-orm.go | 30 +- testintegration/models/models.go | 54 +- testintegration/operators_test.go | 5 +- testintegration/preload_conditions_test.go | 52 +- testintegration/where_conditions_test.go | 22 +- 88 files changed, 2079 insertions(+), 2241 deletions(-) delete mode 100644 mocks/orm/BadaasID.go delete mode 100644 mocks/orm/IJoinCondition.go rename mocks/orm/{ => condition}/Condition.go (63%) create mode 100644 mocks/orm/condition/JoinCondition.go rename mocks/orm/{ => condition}/WhereCondition.go (54%) delete mode 100644 mocks/orm/iFieldIdentifier.go rename mocks/orm/{ModelID.go => model/ID.go} (52%) rename mocks/orm/{ => model}/Model.go (100%) rename mocks/orm/{ => operator}/DynamicOperator.go (62%) rename mocks/orm/{ => operator}/Operator.go (63%) create mode 100644 mocks/orm/query/IFieldIdentifier.go delete mode 100644 orm/condition.go create mode 100644 orm/condition/collection_preload_condition.go create mode 100644 orm/condition/condition.go create mode 100644 orm/condition/connection_condition.go create mode 100644 orm/condition/container_condition.go create mode 100644 orm/condition/errors.go create mode 100644 orm/condition/field_condition.go create mode 100644 orm/condition/invalid_condition.go create mode 100644 orm/condition/join_condition.go create mode 100644 orm/condition/logical.go create mode 100644 orm/condition/preload_condition.go create mode 100644 orm/condition/where_condition.go delete mode 100644 orm/errors.go create mode 100644 orm/errors/errors.go create mode 100644 orm/logical.go rename orm/{baseModels.go => model/models.go} (96%) rename orm/{ => model}/uuid.go (99%) rename orm/{ => model}/uuid_test.go (63%) create mode 100644 orm/operator/errors.go create mode 100644 orm/operator/operator.go create mode 100644 orm/operator/predicate_operator.go rename orm/{operator.go => operator/value_operator.go} (54%) delete mode 100644 orm/preload.go create mode 100644 orm/preload/preload.go delete mode 100644 orm/query.go create mode 100644 orm/query/field_identifier.go create mode 100644 orm/query/query.go create mode 100644 orm/query/table.go diff --git a/controllers/basicAuth.go b/controllers/basicAuth.go index 370737ab..b4c3808e 100644 --- a/controllers/basicAuth.go +++ b/controllers/basicAuth.go @@ -10,7 +10,7 @@ import ( "go.uber.org/zap" "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/orm" + badaasORMErrors "github.com/ditrit/badaas/orm/errors" "github.com/ditrit/badaas/persistence/models/dto" "github.com/ditrit/badaas/services/sessionservice" "github.com/ditrit/badaas/services/userservice" @@ -65,7 +65,7 @@ func (basicAuthController *basicAuthenticationController) BasicLoginHandler(w ht user, err := basicAuthController.userService.GetUser(loginJSONStruct) if err != nil { - if errors.Is(err, orm.ErrObjectNotFound) { + if errors.Is(err, badaasORMErrors.ErrObjectNotFound) { return nil, httperrors.NewErrorNotFound( "user", fmt.Sprintf("no user found with email %q", loginJSONStruct.Email), diff --git a/controllers/basicAuth_test.go b/controllers/basicAuth_test.go index 143e6c11..f1f3dc6d 100644 --- a/controllers/basicAuth_test.go +++ b/controllers/basicAuth_test.go @@ -15,7 +15,7 @@ import ( "github.com/ditrit/badaas/httperrors" mocksSessionService "github.com/ditrit/badaas/mocks/services/sessionservice" mocksUserService "github.com/ditrit/badaas/mocks/services/userservice" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" ) @@ -96,7 +96,7 @@ func Test_BasicLoginHandler_LoginFailed(t *testing.T) { ) userService := mocksUserService.NewUserService(t) user := &models.User{ - UUIDModel: orm.UUIDModel{}, + UUIDModel: model.UUIDModel{}, Username: "bob", Email: "bob@email.com", Password: []byte("hash of 1234"), @@ -139,8 +139,8 @@ func Test_BasicLoginHandler_LoginSuccess(t *testing.T) { ) userService := mocksUserService.NewUserService(t) user := &models.User{ - UUIDModel: orm.UUIDModel{ - ID: orm.NilUUID, + UUIDModel: model.UUIDModel{ + ID: model.NilUUID, }, Username: "bob", Email: "bob@email.com", diff --git a/mocks/orm/BadaasID.go b/mocks/orm/BadaasID.go deleted file mode 100644 index cf2c0098..00000000 --- a/mocks/orm/BadaasID.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// BadaasID is an autogenerated mock type for the BadaasID type -type BadaasID struct { - mock.Mock -} - -type mockConstructorTestingTNewBadaasID interface { - mock.TestingT - Cleanup(func()) -} - -// NewBadaasID creates a new instance of BadaasID. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewBadaasID(t mockConstructorTestingTNewBadaasID) *BadaasID { - mock := &BadaasID{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/orm/CRUDRepository.go b/mocks/orm/CRUDRepository.go index d9894c12..27707e61 100644 --- a/mocks/orm/CRUDRepository.go +++ b/mocks/orm/CRUDRepository.go @@ -3,13 +3,16 @@ package mocks import ( - orm "github.com/ditrit/badaas/orm" - mock "github.com/stretchr/testify/mock" + condition "github.com/ditrit/badaas/orm/condition" gorm "gorm.io/gorm" + + mock "github.com/stretchr/testify/mock" + + model "github.com/ditrit/badaas/orm/model" ) // CRUDRepository is an autogenerated mock type for the CRUDRepository type -type CRUDRepository[T orm.Model, ID orm.ModelID] struct { +type CRUDRepository[T model.Model, ID model.ID] struct { mock.Mock } @@ -68,7 +71,7 @@ func (_m *CRUDRepository[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, error) { } // Query provides a mock function with given fields: tx, conditions -func (_m *CRUDRepository[T, ID]) Query(tx *gorm.DB, conditions ...orm.Condition[T]) ([]*T, error) { +func (_m *CRUDRepository[T, ID]) Query(tx *gorm.DB, conditions ...condition.Condition[T]) ([]*T, error) { _va := make([]interface{}, len(conditions)) for _i := range conditions { _va[_i] = conditions[_i] @@ -80,10 +83,10 @@ func (_m *CRUDRepository[T, ID]) Query(tx *gorm.DB, conditions ...orm.Condition[ var r0 []*T var r1 error - if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) ([]*T, error)); ok { + if rf, ok := ret.Get(0).(func(*gorm.DB, ...condition.Condition[T]) ([]*T, error)); ok { return rf(tx, conditions...) } - if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) []*T); ok { + if rf, ok := ret.Get(0).(func(*gorm.DB, ...condition.Condition[T]) []*T); ok { r0 = rf(tx, conditions...) } else { if ret.Get(0) != nil { @@ -91,7 +94,7 @@ func (_m *CRUDRepository[T, ID]) Query(tx *gorm.DB, conditions ...orm.Condition[ } } - if rf, ok := ret.Get(1).(func(*gorm.DB, ...orm.Condition[T]) error); ok { + if rf, ok := ret.Get(1).(func(*gorm.DB, ...condition.Condition[T]) error); ok { r1 = rf(tx, conditions...) } else { r1 = ret.Error(1) @@ -101,7 +104,7 @@ func (_m *CRUDRepository[T, ID]) Query(tx *gorm.DB, conditions ...orm.Condition[ } // QueryOne provides a mock function with given fields: tx, conditions -func (_m *CRUDRepository[T, ID]) QueryOne(tx *gorm.DB, conditions ...orm.Condition[T]) (*T, error) { +func (_m *CRUDRepository[T, ID]) QueryOne(tx *gorm.DB, conditions ...condition.Condition[T]) (*T, error) { _va := make([]interface{}, len(conditions)) for _i := range conditions { _va[_i] = conditions[_i] @@ -113,10 +116,10 @@ func (_m *CRUDRepository[T, ID]) QueryOne(tx *gorm.DB, conditions ...orm.Conditi var r0 *T var r1 error - if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) (*T, error)); ok { + if rf, ok := ret.Get(0).(func(*gorm.DB, ...condition.Condition[T]) (*T, error)); ok { return rf(tx, conditions...) } - if rf, ok := ret.Get(0).(func(*gorm.DB, ...orm.Condition[T]) *T); ok { + if rf, ok := ret.Get(0).(func(*gorm.DB, ...condition.Condition[T]) *T); ok { r0 = rf(tx, conditions...) } else { if ret.Get(0) != nil { @@ -124,7 +127,7 @@ func (_m *CRUDRepository[T, ID]) QueryOne(tx *gorm.DB, conditions ...orm.Conditi } } - if rf, ok := ret.Get(1).(func(*gorm.DB, ...orm.Condition[T]) error); ok { + if rf, ok := ret.Get(1).(func(*gorm.DB, ...condition.Condition[T]) error); ok { r1 = rf(tx, conditions...) } else { r1 = ret.Error(1) @@ -153,7 +156,7 @@ type mockConstructorTestingTNewCRUDRepository interface { } // NewCRUDRepository creates a new instance of CRUDRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCRUDRepository[T orm.Model, ID orm.ModelID](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { +func NewCRUDRepository[T model.Model, ID model.ID](t mockConstructorTestingTNewCRUDRepository) *CRUDRepository[T, ID] { mock := &CRUDRepository[T, ID]{} mock.Mock.Test(t) diff --git a/mocks/orm/CRUDService.go b/mocks/orm/CRUDService.go index 21c4d596..00582134 100644 --- a/mocks/orm/CRUDService.go +++ b/mocks/orm/CRUDService.go @@ -3,12 +3,14 @@ package mocks import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" mock "github.com/stretchr/testify/mock" + + model "github.com/ditrit/badaas/orm/model" ) // CRUDService is an autogenerated mock type for the CRUDService type -type CRUDService[T orm.Model, ID orm.ModelID] struct { +type CRUDService[T model.Model, ID model.ID] struct { mock.Mock } @@ -39,7 +41,7 @@ func (_m *CRUDService[T, ID]) GetByID(id ID) (*T, error) { } // Query provides a mock function with given fields: conditions -func (_m *CRUDService[T, ID]) Query(conditions ...orm.Condition[T]) ([]*T, error) { +func (_m *CRUDService[T, ID]) Query(conditions ...condition.Condition[T]) ([]*T, error) { _va := make([]interface{}, len(conditions)) for _i := range conditions { _va[_i] = conditions[_i] @@ -50,10 +52,10 @@ func (_m *CRUDService[T, ID]) Query(conditions ...orm.Condition[T]) ([]*T, error var r0 []*T var r1 error - if rf, ok := ret.Get(0).(func(...orm.Condition[T]) ([]*T, error)); ok { + if rf, ok := ret.Get(0).(func(...condition.Condition[T]) ([]*T, error)); ok { return rf(conditions...) } - if rf, ok := ret.Get(0).(func(...orm.Condition[T]) []*T); ok { + if rf, ok := ret.Get(0).(func(...condition.Condition[T]) []*T); ok { r0 = rf(conditions...) } else { if ret.Get(0) != nil { @@ -61,7 +63,7 @@ func (_m *CRUDService[T, ID]) Query(conditions ...orm.Condition[T]) ([]*T, error } } - if rf, ok := ret.Get(1).(func(...orm.Condition[T]) error); ok { + if rf, ok := ret.Get(1).(func(...condition.Condition[T]) error); ok { r1 = rf(conditions...) } else { r1 = ret.Error(1) @@ -71,7 +73,7 @@ func (_m *CRUDService[T, ID]) Query(conditions ...orm.Condition[T]) ([]*T, error } // QueryOne provides a mock function with given fields: conditions -func (_m *CRUDService[T, ID]) QueryOne(conditions ...orm.Condition[T]) (*T, error) { +func (_m *CRUDService[T, ID]) QueryOne(conditions ...condition.Condition[T]) (*T, error) { _va := make([]interface{}, len(conditions)) for _i := range conditions { _va[_i] = conditions[_i] @@ -82,10 +84,10 @@ func (_m *CRUDService[T, ID]) QueryOne(conditions ...orm.Condition[T]) (*T, erro var r0 *T var r1 error - if rf, ok := ret.Get(0).(func(...orm.Condition[T]) (*T, error)); ok { + if rf, ok := ret.Get(0).(func(...condition.Condition[T]) (*T, error)); ok { return rf(conditions...) } - if rf, ok := ret.Get(0).(func(...orm.Condition[T]) *T); ok { + if rf, ok := ret.Get(0).(func(...condition.Condition[T]) *T); ok { r0 = rf(conditions...) } else { if ret.Get(0) != nil { @@ -93,7 +95,7 @@ func (_m *CRUDService[T, ID]) QueryOne(conditions ...orm.Condition[T]) (*T, erro } } - if rf, ok := ret.Get(1).(func(...orm.Condition[T]) error); ok { + if rf, ok := ret.Get(1).(func(...condition.Condition[T]) error); ok { r1 = rf(conditions...) } else { r1 = ret.Error(1) @@ -108,7 +110,7 @@ type mockConstructorTestingTNewCRUDService interface { } // NewCRUDService creates a new instance of CRUDService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCRUDService[T orm.Model, ID orm.ModelID](t mockConstructorTestingTNewCRUDService) *CRUDService[T, ID] { +func NewCRUDService[T model.Model, ID model.ID](t mockConstructorTestingTNewCRUDService) *CRUDService[T, ID] { mock := &CRUDService[T, ID]{} mock.Mock.Test(t) diff --git a/mocks/orm/IJoinCondition.go b/mocks/orm/IJoinCondition.go deleted file mode 100644 index e93d30c4..00000000 --- a/mocks/orm/IJoinCondition.go +++ /dev/null @@ -1,75 +0,0 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. - -package mocks - -import ( - orm "github.com/ditrit/badaas/orm" - mock "github.com/stretchr/testify/mock" -) - -// IJoinCondition is an autogenerated mock type for the IJoinCondition type -type IJoinCondition[T orm.Model] struct { - mock.Mock -} - -// ApplyTo provides a mock function with given fields: query, table -func (_m *IJoinCondition[T]) ApplyTo(query *orm.Query, table orm.Table) error { - ret := _m.Called(query, table) - - var r0 error - if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) error); ok { - r0 = rf(query, table) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// InterfaceVerificationMethod provides a mock function with given fields: _a0 -func (_m *IJoinCondition[T]) InterfaceVerificationMethod(_a0 T) { - _m.Called(_a0) -} - -// makesFilter provides a mock function with given fields: -func (_m *IJoinCondition[T]) makesFilter() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// makesPreload provides a mock function with given fields: -func (_m *IJoinCondition[T]) makesPreload() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -type mockConstructorTestingTNewIJoinCondition interface { - mock.TestingT - Cleanup(func()) -} - -// NewIJoinCondition creates a new instance of IJoinCondition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewIJoinCondition[T orm.Model](t mockConstructorTestingTNewIJoinCondition) *IJoinCondition[T] { - mock := &IJoinCondition[T]{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/orm/Condition.go b/mocks/orm/condition/Condition.go similarity index 63% rename from mocks/orm/Condition.go rename to mocks/orm/condition/Condition.go index 9edfd710..55e20f41 100644 --- a/mocks/orm/Condition.go +++ b/mocks/orm/condition/Condition.go @@ -3,22 +3,24 @@ package mocks import ( - orm "github.com/ditrit/badaas/orm" + model "github.com/ditrit/badaas/orm/model" mock "github.com/stretchr/testify/mock" + + query "github.com/ditrit/badaas/orm/query" ) // Condition is an autogenerated mock type for the Condition type -type Condition[T orm.Model] struct { +type Condition[T model.Model] struct { mock.Mock } -// ApplyTo provides a mock function with given fields: query, table -func (_m *Condition[T]) ApplyTo(query *orm.Query, table orm.Table) error { - ret := _m.Called(query, table) +// ApplyTo provides a mock function with given fields: _a0, _a1 +func (_m *Condition[T]) ApplyTo(_a0 *query.Query, _a1 query.Table) error { + ret := _m.Called(_a0, _a1) var r0 error - if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) error); ok { - r0 = rf(query, table) + if rf, ok := ret.Get(0).(func(*query.Query, query.Table) error); ok { + r0 = rf(_a0, _a1) } else { r0 = ret.Error(0) } @@ -37,7 +39,7 @@ type mockConstructorTestingTNewCondition interface { } // NewCondition creates a new instance of Condition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewCondition[T orm.Model](t mockConstructorTestingTNewCondition) *Condition[T] { +func NewCondition[T model.Model](t mockConstructorTestingTNewCondition) *Condition[T] { mock := &Condition[T]{} mock.Mock.Test(t) diff --git a/mocks/orm/condition/JoinCondition.go b/mocks/orm/condition/JoinCondition.go new file mode 100644 index 00000000..8c7e1909 --- /dev/null +++ b/mocks/orm/condition/JoinCondition.go @@ -0,0 +1,77 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + model "github.com/ditrit/badaas/orm/model" + mock "github.com/stretchr/testify/mock" + + query "github.com/ditrit/badaas/orm/query" +) + +// JoinCondition is an autogenerated mock type for the JoinCondition type +type JoinCondition[T model.Model] struct { + mock.Mock +} + +// ApplyTo provides a mock function with given fields: _a0, _a1 +func (_m *JoinCondition[T]) ApplyTo(_a0 *query.Query, _a1 query.Table) error { + ret := _m.Called(_a0, _a1) + + var r0 error + if rf, ok := ret.Get(0).(func(*query.Query, query.Table) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InterfaceVerificationMethod provides a mock function with given fields: _a0 +func (_m *JoinCondition[T]) InterfaceVerificationMethod(_a0 T) { + _m.Called(_a0) +} + +// makesFilter provides a mock function with given fields: +func (_m *JoinCondition[T]) makesFilter() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// makesPreload provides a mock function with given fields: +func (_m *JoinCondition[T]) makesPreload() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +type mockConstructorTestingTNewJoinCondition interface { + mock.TestingT + Cleanup(func()) +} + +// NewJoinCondition creates a new instance of JoinCondition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewJoinCondition[T model.Model](t mockConstructorTestingTNewJoinCondition) *JoinCondition[T] { + mock := &JoinCondition[T]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/orm/WhereCondition.go b/mocks/orm/condition/WhereCondition.go similarity index 54% rename from mocks/orm/WhereCondition.go rename to mocks/orm/condition/WhereCondition.go index cc0ae5c4..cc588411 100644 --- a/mocks/orm/WhereCondition.go +++ b/mocks/orm/condition/WhereCondition.go @@ -3,12 +3,14 @@ package mocks import ( - orm "github.com/ditrit/badaas/orm" + model "github.com/ditrit/badaas/orm/model" mock "github.com/stretchr/testify/mock" + + query "github.com/ditrit/badaas/orm/query" ) // WhereCondition is an autogenerated mock type for the WhereCondition type -type WhereCondition[T orm.Model] struct { +type WhereCondition[T model.Model] struct { mock.Mock } @@ -26,13 +28,13 @@ func (_m *WhereCondition[T]) AffectsDeletedAt() bool { return r0 } -// ApplyTo provides a mock function with given fields: query, table -func (_m *WhereCondition[T]) ApplyTo(query *orm.Query, table orm.Table) error { - ret := _m.Called(query, table) +// ApplyTo provides a mock function with given fields: _a0, _a1 +func (_m *WhereCondition[T]) ApplyTo(_a0 *query.Query, _a1 query.Table) error { + ret := _m.Called(_a0, _a1) var r0 error - if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) error); ok { - r0 = rf(query, table) + if rf, ok := ret.Get(0).(func(*query.Query, query.Table) error); ok { + r0 = rf(_a0, _a1) } else { r0 = ret.Error(0) } @@ -40,32 +42,32 @@ func (_m *WhereCondition[T]) ApplyTo(query *orm.Query, table orm.Table) error { return r0 } -// GetSQL provides a mock function with given fields: query, table -func (_m *WhereCondition[T]) GetSQL(query *orm.Query, table orm.Table) (string, []interface{}, error) { - ret := _m.Called(query, table) +// GetSQL provides a mock function with given fields: _a0, table +func (_m *WhereCondition[T]) GetSQL(_a0 *query.Query, table query.Table) (string, []interface{}, error) { + ret := _m.Called(_a0, table) var r0 string var r1 []interface{} var r2 error - if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) (string, []interface{}, error)); ok { - return rf(query, table) + if rf, ok := ret.Get(0).(func(*query.Query, query.Table) (string, []interface{}, error)); ok { + return rf(_a0, table) } - if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) string); ok { - r0 = rf(query, table) + if rf, ok := ret.Get(0).(func(*query.Query, query.Table) string); ok { + r0 = rf(_a0, table) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(*orm.Query, orm.Table) []interface{}); ok { - r1 = rf(query, table) + if rf, ok := ret.Get(1).(func(*query.Query, query.Table) []interface{}); ok { + r1 = rf(_a0, table) } else { if ret.Get(1) != nil { r1 = ret.Get(1).([]interface{}) } } - if rf, ok := ret.Get(2).(func(*orm.Query, orm.Table) error); ok { - r2 = rf(query, table) + if rf, ok := ret.Get(2).(func(*query.Query, query.Table) error); ok { + r2 = rf(_a0, table) } else { r2 = ret.Error(2) } @@ -84,7 +86,7 @@ type mockConstructorTestingTNewWhereCondition interface { } // NewWhereCondition creates a new instance of WhereCondition. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewWhereCondition[T orm.Model](t mockConstructorTestingTNewWhereCondition) *WhereCondition[T] { +func NewWhereCondition[T model.Model](t mockConstructorTestingTNewWhereCondition) *WhereCondition[T] { mock := &WhereCondition[T]{} mock.Mock.Test(t) diff --git a/mocks/orm/iFieldIdentifier.go b/mocks/orm/iFieldIdentifier.go deleted file mode 100644 index bf5ded50..00000000 --- a/mocks/orm/iFieldIdentifier.go +++ /dev/null @@ -1,74 +0,0 @@ -// Code generated by mockery v2.20.0. DO NOT EDIT. - -package mocks - -import ( - orm "github.com/ditrit/badaas/orm" - mock "github.com/stretchr/testify/mock" - - reflect "reflect" -) - -// iFieldIdentifier is an autogenerated mock type for the iFieldIdentifier type -type iFieldIdentifier struct { - mock.Mock -} - -// ColumnName provides a mock function with given fields: query, table -func (_m *iFieldIdentifier) ColumnName(query *orm.Query, table orm.Table) string { - ret := _m.Called(query, table) - - var r0 string - if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) string); ok { - r0 = rf(query, table) - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// ColumnSQL provides a mock function with given fields: query, table -func (_m *iFieldIdentifier) ColumnSQL(query *orm.Query, table orm.Table) string { - ret := _m.Called(query, table) - - var r0 string - if rf, ok := ret.Get(0).(func(*orm.Query, orm.Table) string); ok { - r0 = rf(query, table) - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// GetModelType provides a mock function with given fields: -func (_m *iFieldIdentifier) GetModelType() reflect.Type { - ret := _m.Called() - - var r0 reflect.Type - if rf, ok := ret.Get(0).(func() reflect.Type); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(reflect.Type) - } - } - - return r0 -} - -type mockConstructorTestingTnewIFieldIdentifier interface { - mock.TestingT - Cleanup(func()) -} - -// newIFieldIdentifier creates a new instance of iFieldIdentifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func newIFieldIdentifier(t mockConstructorTestingTnewIFieldIdentifier) *iFieldIdentifier { - mock := &iFieldIdentifier{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/orm/ModelID.go b/mocks/orm/model/ID.go similarity index 52% rename from mocks/orm/ModelID.go rename to mocks/orm/model/ID.go index dfaee03d..11bdedeb 100644 --- a/mocks/orm/ModelID.go +++ b/mocks/orm/model/ID.go @@ -4,13 +4,13 @@ package mocks import mock "github.com/stretchr/testify/mock" -// ModelID is an autogenerated mock type for the ModelID type -type ModelID struct { +// ID is an autogenerated mock type for the ID type +type ID struct { mock.Mock } // IsNil provides a mock function with given fields: -func (_m *ModelID) IsNil() bool { +func (_m *ID) IsNil() bool { ret := _m.Called() var r0 bool @@ -23,14 +23,14 @@ func (_m *ModelID) IsNil() bool { return r0 } -type mockConstructorTestingTNewModelID interface { +type mockConstructorTestingTNewID interface { mock.TestingT Cleanup(func()) } -// NewModelID creates a new instance of ModelID. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewModelID(t mockConstructorTestingTNewModelID) *ModelID { - mock := &ModelID{} +// NewID creates a new instance of ID. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewID(t mockConstructorTestingTNewID) *ID { + mock := &ID{} mock.Mock.Test(t) t.Cleanup(func() { mock.AssertExpectations(t) }) diff --git a/mocks/orm/Model.go b/mocks/orm/model/Model.go similarity index 100% rename from mocks/orm/Model.go rename to mocks/orm/model/Model.go diff --git a/mocks/orm/DynamicOperator.go b/mocks/orm/operator/DynamicOperator.go similarity index 62% rename from mocks/orm/DynamicOperator.go rename to mocks/orm/operator/DynamicOperator.go index 3615106a..6efab947 100644 --- a/mocks/orm/DynamicOperator.go +++ b/mocks/orm/operator/DynamicOperator.go @@ -3,8 +3,10 @@ package mocks import ( - orm "github.com/ditrit/badaas/orm" + operator "github.com/ditrit/badaas/orm/operator" mock "github.com/stretchr/testify/mock" + + query "github.com/ditrit/badaas/orm/query" ) // DynamicOperator is an autogenerated mock type for the DynamicOperator type @@ -18,47 +20,47 @@ func (_m *DynamicOperator[T]) InterfaceVerificationMethod(_a0 T) { } // SelectJoin provides a mock function with given fields: valueNumber, joinNumber -func (_m *DynamicOperator[T]) SelectJoin(valueNumber uint, joinNumber uint) orm.DynamicOperator[T] { +func (_m *DynamicOperator[T]) SelectJoin(valueNumber uint, joinNumber uint) operator.DynamicOperator[T] { ret := _m.Called(valueNumber, joinNumber) - var r0 orm.DynamicOperator[T] - if rf, ok := ret.Get(0).(func(uint, uint) orm.DynamicOperator[T]); ok { + var r0 operator.DynamicOperator[T] + if rf, ok := ret.Get(0).(func(uint, uint) operator.DynamicOperator[T]); ok { r0 = rf(valueNumber, joinNumber) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(orm.DynamicOperator[T]) + r0 = ret.Get(0).(operator.DynamicOperator[T]) } } return r0 } -// ToSQL provides a mock function with given fields: query, columnName -func (_m *DynamicOperator[T]) ToSQL(query *orm.Query, columnName string) (string, []interface{}, error) { - ret := _m.Called(query, columnName) +// ToSQL provides a mock function with given fields: _a0, columnName +func (_m *DynamicOperator[T]) ToSQL(_a0 *query.Query, columnName string) (string, []interface{}, error) { + ret := _m.Called(_a0, columnName) var r0 string var r1 []interface{} var r2 error - if rf, ok := ret.Get(0).(func(*orm.Query, string) (string, []interface{}, error)); ok { - return rf(query, columnName) + if rf, ok := ret.Get(0).(func(*query.Query, string) (string, []interface{}, error)); ok { + return rf(_a0, columnName) } - if rf, ok := ret.Get(0).(func(*orm.Query, string) string); ok { - r0 = rf(query, columnName) + if rf, ok := ret.Get(0).(func(*query.Query, string) string); ok { + r0 = rf(_a0, columnName) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(*orm.Query, string) []interface{}); ok { - r1 = rf(query, columnName) + if rf, ok := ret.Get(1).(func(*query.Query, string) []interface{}); ok { + r1 = rf(_a0, columnName) } else { if ret.Get(1) != nil { r1 = ret.Get(1).([]interface{}) } } - if rf, ok := ret.Get(2).(func(*orm.Query, string) error); ok { - r2 = rf(query, columnName) + if rf, ok := ret.Get(2).(func(*query.Query, string) error); ok { + r2 = rf(_a0, columnName) } else { r2 = ret.Error(2) } diff --git a/mocks/orm/Operator.go b/mocks/orm/operator/Operator.go similarity index 63% rename from mocks/orm/Operator.go rename to mocks/orm/operator/Operator.go index f950024a..1efcc6bd 100644 --- a/mocks/orm/Operator.go +++ b/mocks/orm/operator/Operator.go @@ -3,8 +3,9 @@ package mocks import ( - orm "github.com/ditrit/badaas/orm" mock "github.com/stretchr/testify/mock" + + query "github.com/ditrit/badaas/orm/query" ) // Operator is an autogenerated mock type for the Operator type @@ -17,32 +18,32 @@ func (_m *Operator[T]) InterfaceVerificationMethod(_a0 T) { _m.Called(_a0) } -// ToSQL provides a mock function with given fields: query, columnName -func (_m *Operator[T]) ToSQL(query *orm.Query, columnName string) (string, []interface{}, error) { - ret := _m.Called(query, columnName) +// ToSQL provides a mock function with given fields: _a0, columnName +func (_m *Operator[T]) ToSQL(_a0 *query.Query, columnName string) (string, []interface{}, error) { + ret := _m.Called(_a0, columnName) var r0 string var r1 []interface{} var r2 error - if rf, ok := ret.Get(0).(func(*orm.Query, string) (string, []interface{}, error)); ok { - return rf(query, columnName) + if rf, ok := ret.Get(0).(func(*query.Query, string) (string, []interface{}, error)); ok { + return rf(_a0, columnName) } - if rf, ok := ret.Get(0).(func(*orm.Query, string) string); ok { - r0 = rf(query, columnName) + if rf, ok := ret.Get(0).(func(*query.Query, string) string); ok { + r0 = rf(_a0, columnName) } else { r0 = ret.Get(0).(string) } - if rf, ok := ret.Get(1).(func(*orm.Query, string) []interface{}); ok { - r1 = rf(query, columnName) + if rf, ok := ret.Get(1).(func(*query.Query, string) []interface{}); ok { + r1 = rf(_a0, columnName) } else { if ret.Get(1) != nil { r1 = ret.Get(1).([]interface{}) } } - if rf, ok := ret.Get(2).(func(*orm.Query, string) error); ok { - r2 = rf(query, columnName) + if rf, ok := ret.Get(2).(func(*query.Query, string) error); ok { + r2 = rf(_a0, columnName) } else { r2 = ret.Error(2) } diff --git a/mocks/orm/query/IFieldIdentifier.go b/mocks/orm/query/IFieldIdentifier.go new file mode 100644 index 00000000..d5a4a2d6 --- /dev/null +++ b/mocks/orm/query/IFieldIdentifier.go @@ -0,0 +1,74 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + query "github.com/ditrit/badaas/orm/query" + mock "github.com/stretchr/testify/mock" + + reflect "reflect" +) + +// IFieldIdentifier is an autogenerated mock type for the IFieldIdentifier type +type IFieldIdentifier struct { + mock.Mock +} + +// ColumnName provides a mock function with given fields: _a0, table +func (_m *IFieldIdentifier) ColumnName(_a0 *query.Query, table query.Table) string { + ret := _m.Called(_a0, table) + + var r0 string + if rf, ok := ret.Get(0).(func(*query.Query, query.Table) string); ok { + r0 = rf(_a0, table) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// ColumnSQL provides a mock function with given fields: _a0, table +func (_m *IFieldIdentifier) ColumnSQL(_a0 *query.Query, table query.Table) string { + ret := _m.Called(_a0, table) + + var r0 string + if rf, ok := ret.Get(0).(func(*query.Query, query.Table) string); ok { + r0 = rf(_a0, table) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetModelType provides a mock function with given fields: +func (_m *IFieldIdentifier) GetModelType() reflect.Type { + ret := _m.Called() + + var r0 reflect.Type + if rf, ok := ret.Get(0).(func() reflect.Type); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(reflect.Type) + } + } + + return r0 +} + +type mockConstructorTestingTNewIFieldIdentifier interface { + mock.TestingT + Cleanup(func()) +} + +// NewIFieldIdentifier creates a new instance of IFieldIdentifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewIFieldIdentifier(t mockConstructorTestingTNewIFieldIdentifier) *IFieldIdentifier { + mock := &IFieldIdentifier{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/services/sessionservice/SessionService.go b/mocks/services/sessionservice/SessionService.go index fdf023f2..44c7f52d 100644 --- a/mocks/services/sessionservice/SessionService.go +++ b/mocks/services/sessionservice/SessionService.go @@ -6,9 +6,9 @@ import ( httperrors "github.com/ditrit/badaas/httperrors" mock "github.com/stretchr/testify/mock" - models "github.com/ditrit/badaas/persistence/models" + model "github.com/ditrit/badaas/orm/model" - orm "github.com/ditrit/badaas/orm" + models "github.com/ditrit/badaas/persistence/models" sessionservice "github.com/ditrit/badaas/services/sessionservice" ) @@ -19,21 +19,21 @@ type SessionService struct { } // IsValid provides a mock function with given fields: sessionUUID -func (_m *SessionService) IsValid(sessionUUID orm.UUID) (bool, *sessionservice.SessionClaims) { +func (_m *SessionService) IsValid(sessionUUID model.UUID) (bool, *sessionservice.SessionClaims) { ret := _m.Called(sessionUUID) var r0 bool var r1 *sessionservice.SessionClaims - if rf, ok := ret.Get(0).(func(orm.UUID) (bool, *sessionservice.SessionClaims)); ok { + if rf, ok := ret.Get(0).(func(model.UUID) (bool, *sessionservice.SessionClaims)); ok { return rf(sessionUUID) } - if rf, ok := ret.Get(0).(func(orm.UUID) bool); ok { + if rf, ok := ret.Get(0).(func(model.UUID) bool); ok { r0 = rf(sessionUUID) } else { r0 = ret.Get(0).(bool) } - if rf, ok := ret.Get(1).(func(orm.UUID) *sessionservice.SessionClaims); ok { + if rf, ok := ret.Get(1).(func(model.UUID) *sessionservice.SessionClaims); ok { r1 = rf(sessionUUID) } else { if ret.Get(1) != nil { @@ -87,11 +87,11 @@ func (_m *SessionService) LogUserOut(sessionClaims *sessionservice.SessionClaims } // RollSession provides a mock function with given fields: _a0 -func (_m *SessionService) RollSession(_a0 orm.UUID) httperrors.HTTPError { +func (_m *SessionService) RollSession(_a0 model.UUID) httperrors.HTTPError { ret := _m.Called(_a0) var r0 httperrors.HTTPError - if rf, ok := ret.Get(0).(func(orm.UUID) httperrors.HTTPError); ok { + if rf, ok := ret.Get(0).(func(model.UUID) httperrors.HTTPError); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { diff --git a/orm/ModuleFx.go b/orm/ModuleFx.go index 4e193776..13b4ed78 100644 --- a/orm/ModuleFx.go +++ b/orm/ModuleFx.go @@ -6,6 +6,8 @@ import ( "reflect" "go.uber.org/fx" + + "github.com/ditrit/badaas/orm/model" ) type GetModelsResult struct { @@ -24,7 +26,7 @@ var AutoMigrate = fx.Module( ), ) -func GetCRUDServiceModule[T Model]() fx.Option { +func GetCRUDServiceModule[T model.Model]() fx.Option { entity := *new(T) moduleName := fmt.Sprintf( @@ -34,23 +36,23 @@ func GetCRUDServiceModule[T Model]() fx.Option { kind := getModelKind(entity) switch kind { - case KindUUIDModel: + case kindUUIDModel: return fx.Module( moduleName, // repository - fx.Provide(NewCRUDRepository[T, UUID]), + fx.Provide(NewCRUDRepository[T, model.UUID]), // service - fx.Provide(NewCRUDService[T, UUID]), + fx.Provide(NewCRUDService[T, model.UUID]), ) - case KindUIntModel: + case kindUIntModel: return fx.Module( moduleName, // repository - fx.Provide(NewCRUDRepository[T, UIntID]), + fx.Provide(NewCRUDRepository[T, model.UIntID]), // service - fx.Provide(NewCRUDService[T, UIntID]), + fx.Provide(NewCRUDService[T, model.UIntID]), ) - case KindNotModel: + case kindNotModel: log.Printf("type %T is not a BaDaaS model\n", entity) return fx.Invoke(failNotBaDaaSModel()) } @@ -65,25 +67,25 @@ func failNotBaDaaSModel() error { type modelKind uint const ( - KindUUIDModel modelKind = iota - KindUIntModel - KindNotModel + kindUUIDModel modelKind = iota + kindUIntModel + kindNotModel ) -func getModelKind(entity Model) modelKind { +func getModelKind(entity model.Model) modelKind { entityType := getEntityType(entity) _, isUUIDModel := entityType.FieldByName("UUIDModel") if isUUIDModel { - return KindUUIDModel + return kindUUIDModel } _, isUIntModel := entityType.FieldByName("UIntModel") if isUIntModel { - return KindUIntModel + return kindUIntModel } - return KindNotModel + return kindNotModel } // Get the reflect.Type of any entity or pointer to entity diff --git a/orm/condition.go b/orm/condition.go deleted file mode 100644 index f14c197f..00000000 --- a/orm/condition.go +++ /dev/null @@ -1,523 +0,0 @@ -package orm - -import ( - "fmt" - "reflect" - "strings" - - "github.com/elliotchance/pie/v2" - "gorm.io/gorm" - - "github.com/ditrit/badaas/orm/sql" -) - -const deletedAtField = "DeletedAt" - -type Condition[T Model] interface { - // Applies the condition to the "query" - // using the "tableName" as name for the table holding - // the data for object of type T - ApplyTo(query *Query, table Table) error - - // This method is necessary to get the compiler to verify - // that an object is of type Condition[T], - // since if no method receives by parameter a type T, - // any other Condition[T2] would also be considered a Condition[T]. - InterfaceVerificationMethod(T) -} - -// Conditions that can be used in a where clause -// (or in a on of a join) -type WhereCondition[T Model] interface { - Condition[T] - - // Get the sql string and values to use in the query - GetSQL(query *Query, table Table) (string, []any, error) - - // Returns true if the DeletedAt column if affected by the condition - // If no condition affects the DeletedAt, the verification that it's null will be added automatically - AffectsDeletedAt() bool -} - -// Condition that contains a internal condition. -// Example: NOT (internal condition) -type ContainerCondition[T Model] struct { - ConnectionCondition WhereCondition[T] - Prefix sql.Operator -} - -func (condition ContainerCondition[T]) InterfaceVerificationMethod(_ T) { - // This method is necessary to get the compiler to verify - // that an object is of type Condition[T] -} - -func (condition ContainerCondition[T]) ApplyTo(query *Query, table Table) error { - return ApplyWhereCondition[T](condition, query, table) -} - -func (condition ContainerCondition[T]) GetSQL(query *Query, table Table) (string, []any, error) { - sqlString, values, err := condition.ConnectionCondition.GetSQL(query, table) - if err != nil { - return "", nil, err - } - - sqlString = condition.Prefix.String() + " (" + sqlString + ")" - - return sqlString, values, nil -} - -func (condition ContainerCondition[T]) AffectsDeletedAt() bool { - return condition.ConnectionCondition.AffectsDeletedAt() -} - -// Condition that contains a internal condition. -// Example: NOT (internal condition) -func NewContainerCondition[T Model](prefix sql.Operator, conditions ...WhereCondition[T]) WhereCondition[T] { - if len(conditions) == 0 { - return NewInvalidCondition[T](emptyConditionsError[T](prefix)) - } - - return ContainerCondition[T]{ - Prefix: prefix, - ConnectionCondition: And(conditions...), - } -} - -// Condition that connects multiple conditions. -// Example: condition1 AND condition2 -type ConnectionCondition[T Model] struct { - Connector sql.Operator - Conditions []WhereCondition[T] -} - -func (condition ConnectionCondition[T]) InterfaceVerificationMethod(_ T) { - // This method is necessary to get the compiler to verify - // that an object is of type Condition[T] -} - -func (condition ConnectionCondition[T]) ApplyTo(query *Query, table Table) error { - return ApplyWhereCondition[T](condition, query, table) -} - -func (condition ConnectionCondition[T]) GetSQL(query *Query, table Table) (string, []any, error) { - sqlStrings := []string{} - values := []any{} - - for _, internalCondition := range condition.Conditions { - internalSQLString, internalValues, err := internalCondition.GetSQL(query, table) - if err != nil { - return "", nil, err - } - - sqlStrings = append(sqlStrings, internalSQLString) - - values = append(values, internalValues...) - } - - return strings.Join( - sqlStrings, - " "+condition.Connector.String()+" ", - ), values, nil -} - -func (condition ConnectionCondition[T]) AffectsDeletedAt() bool { - return pie.Any(condition.Conditions, func(internalCondition WhereCondition[T]) bool { - return internalCondition.AffectsDeletedAt() - }) -} - -// Condition that connects multiple conditions. -// Example: condition1 AND condition2 -func NewConnectionCondition[T Model](connector sql.Operator, conditions ...WhereCondition[T]) WhereCondition[T] { - return ConnectionCondition[T]{ - Connector: connector, - Conditions: conditions, - } -} - -type iFieldIdentifier interface { - ColumnName(query *Query, table Table) string - ColumnSQL(query *Query, table Table) string - GetModelType() reflect.Type -} - -type FieldIdentifier[T any] struct { - Column string - Field string - ColumnPrefix string - ModelType reflect.Type -} - -func (fieldID FieldIdentifier[T]) GetModelType() reflect.Type { - return fieldID.ModelType -} - -// Returns the name of the column in which the field is saved in the table -func (fieldID FieldIdentifier[T]) ColumnName(query *Query, table Table) string { - columnName := fieldID.Column - if columnName == "" { - columnName = query.ColumnName(table, fieldID.Field) - } - - // add column prefix and table name once we know the column name - return fieldID.ColumnPrefix + columnName -} - -// Returns the SQL to get the value of the field in the table -func (fieldID FieldIdentifier[T]) ColumnSQL(query *Query, table Table) string { - return table.Alias + "." + fieldID.ColumnName(query, table) -} - -// Condition used to the preload the attributes of a model -type PreloadCondition[T Model] struct { - Fields []iFieldIdentifier -} - -func (condition PreloadCondition[T]) InterfaceVerificationMethod(_ T) { - // This method is necessary to get the compiler to verify - // that an object is of type Condition[T] -} - -func (condition PreloadCondition[T]) ApplyTo(query *Query, table Table) error { - for _, fieldID := range condition.Fields { - query.AddSelect(table, fieldID) - } - - return nil -} - -// Condition used to the preload the attributes of a model -func NewPreloadCondition[T Model](fields ...iFieldIdentifier) PreloadCondition[T] { - return PreloadCondition[T]{ - Fields: fields, - } -} - -// Condition used to the preload a collection of models of a model -type CollectionPreloadCondition[T1, T2 Model] struct { - CollectionField string - NestedPreloads []IJoinCondition[T2] -} - -func (condition CollectionPreloadCondition[T1, T2]) InterfaceVerificationMethod(_ T1) { - // This method is necessary to get the compiler to verify - // that an object is of type Condition[T1] -} - -func (condition CollectionPreloadCondition[T1, T2]) ApplyTo(query *Query, _ Table) error { - if len(condition.NestedPreloads) == 0 { - query.Preload(condition.CollectionField) - return nil - } - - query.Preload( - condition.CollectionField, - func(db *gorm.DB) *gorm.DB { - preloadsAsCondition := pie.Map( - condition.NestedPreloads, - func(joinCondition IJoinCondition[T2]) Condition[T2] { - return joinCondition - }, - ) - - preloadInternalQuery, err := NewQuery(db, preloadsAsCondition) - if err != nil { - _ = db.AddError(err) - return db - } - - return preloadInternalQuery.gormDB - }, - ) - - return nil -} - -// Condition used to the preload a collection of models of a model -func NewCollectionPreloadCondition[T1 Model, T2 Model](collectionField string, nestedPreloads []IJoinCondition[T2]) Condition[T1] { - if pie.Any(nestedPreloads, func(nestedPreload IJoinCondition[T2]) bool { - return !nestedPreload.makesPreload() || nestedPreload.makesFilter() - }) { - return NewInvalidCondition[T1](onlyPreloadsAllowedError[T1](collectionField)) - } - - return CollectionPreloadCondition[T1, T2]{ - CollectionField: collectionField, - NestedPreloads: nestedPreloads, - } -} - -// Condition that verifies the value of a field, -// using the Operator -type FieldCondition[TObject Model, TAtribute any] struct { - FieldIdentifier FieldIdentifier[TAtribute] - Operator Operator[TAtribute] -} - -func (condition FieldCondition[TObject, TAtribute]) InterfaceVerificationMethod(_ TObject) { - // This method is necessary to get the compiler to verify - // that an object is of type Condition[T] -} - -// Returns a gorm Where condition that can be used -// to filter that the Field as a value of Value -func (condition FieldCondition[TObject, TAtribute]) ApplyTo(query *Query, table Table) error { - return ApplyWhereCondition[TObject](condition, query, table) -} - -func ApplyWhereCondition[T Model](condition WhereCondition[T], query *Query, table Table) error { - sql, values, err := condition.GetSQL(query, table) - if err != nil { - return err - } - - if condition.AffectsDeletedAt() { - query.Unscoped() - } - - query.Where( - sql, - values..., - ) - - return nil -} - -func (condition FieldCondition[TObject, TAtribute]) AffectsDeletedAt() bool { - return condition.FieldIdentifier.Field == deletedAtField -} - -func (condition FieldCondition[TObject, TAtribute]) GetSQL(query *Query, table Table) (string, []any, error) { - sqlString, values, err := condition.Operator.ToSQL( - query, - condition.FieldIdentifier.ColumnSQL(query, table), - ) - if err != nil { - return "", nil, conditionOperatorError[TObject](err, condition) - } - - return sqlString, values, nil -} - -// Interface of a join condition that joins T with any other model -type IJoinCondition[T Model] interface { - Condition[T] - - // Returns true if this condition or any nested condition makes a preload - makesPreload() bool - - // Returns true if the condition of nay nested condition applies a filter (has where conditions) - makesFilter() bool -} - -// Condition that joins with other table -type JoinCondition[T1 Model, T2 Model] struct { - T1Field string - T2Field string - RelationField string - Conditions []Condition[T2] - // condition to preload T1 in case T2 any nested object is preloaded by user - T1PreloadCondition PreloadCondition[T1] -} - -func (condition JoinCondition[T1, T2]) InterfaceVerificationMethod(_ T1) { - // This method is necessary to get the compiler to verify - // that an object is of type Condition[T] -} - -// Returns true if this condition or any nested condition makes a preload -func (condition JoinCondition[T1, T2]) makesPreload() bool { - _, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) - - return t2PreloadCondition != nil || pie.Any(joinConditions, func(cond IJoinCondition[T2]) bool { - return cond.makesPreload() - }) -} - -// Returns true if the condition of nay nested condition applies a filter (has where conditions) -// -//nolint:unused // is used -func (condition JoinCondition[T1, T2]) makesFilter() bool { - whereConditions, joinConditions, _ := divideConditionsByType(condition.Conditions) - - return len(whereConditions) != 0 || pie.Any(joinConditions, func(cond IJoinCondition[T2]) bool { - return cond.makesFilter() - }) -} - -// Applies a join between the tables of T1 and T2 -// previousTableName is the name of the table of T1 -// It also applies the nested conditions -func (condition JoinCondition[T1, T2]) ApplyTo(query *Query, t1Table Table) error { - whereConditions, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) - - t2Model := *new(T2) - - // get the sql to do the join with T2 - t2Table, err := t1Table.DeliverTable(query, t2Model, condition.RelationField) - if err != nil { - return err - } - - makesPreload := condition.makesPreload() - joinQuery := condition.getSQLJoin( - query, - t1Table, - t2Table, - len(whereConditions) == 0 && makesPreload, - ) - - query.AddConcernedModel( - t2Model, - t2Table, - ) - - // apply WhereConditions to the join in the "on" clause - connectionCondition := And(whereConditions...) - - onQuery, onValues, err := connectionCondition.GetSQL(query, t2Table) - if err != nil { - return err - } - - if onQuery != "" { - joinQuery += " AND " + onQuery - } - - if !connectionCondition.AffectsDeletedAt() { - joinQuery += fmt.Sprintf( - " AND %s.deleted_at IS NULL", - t2Table.Alias, - ) - } - - // add the join to the query - query.Joins(joinQuery, onValues...) - - // apply T1 preload condition - // if this condition has a T2 preload condition - // or any nested join condition has a preload condition - // and this is not first level (T1 is the type of the repository) - // because T1 is always loaded in that case - if makesPreload && !t1Table.IsInitial() { - err = condition.T1PreloadCondition.ApplyTo(query, t1Table) - if err != nil { - return err - } - } - - // apply T2 preload condition - if t2PreloadCondition != nil { - err = t2PreloadCondition.ApplyTo(query, t2Table) - if err != nil { - return err - } - } - - // apply nested joins - for _, joinCondition := range joinConditions { - err = joinCondition.ApplyTo(query, t2Table) - if err != nil { - return err - } - } - - return nil -} - -// Returns the SQL string to do a join between T1 and T2 -// taking into account that the ID attribute necessary to do it -// can be either in T1's or T2's table. -func (condition JoinCondition[T1, T2]) getSQLJoin( - query *Query, - t1Table Table, - t2Table Table, - isLeftJoin bool, -) string { - joinString := "INNER JOIN" - if isLeftJoin { - joinString = "LEFT JOIN" - } - - return fmt.Sprintf( - `%[6]s %[1]s %[2]s ON %[2]s.%[3]s = %[4]s.%[5]s - `, - t2Table.Name, - t2Table.Alias, - query.ColumnName(t2Table, condition.T2Field), - t1Table.Alias, - query.ColumnName(t1Table, condition.T1Field), - joinString, - ) -} - -// Divides a list of conditions by its type: WhereConditions and JoinConditions -func divideConditionsByType[T Model]( - conditions []Condition[T], -) (whereConditions []WhereCondition[T], joinConditions []IJoinCondition[T], preloadCondition *PreloadCondition[T]) { - for _, condition := range conditions { - possibleWhereCondition, ok := condition.(WhereCondition[T]) - if ok { - whereConditions = append(whereConditions, possibleWhereCondition) - continue - } - - possiblePreloadCondition, ok := condition.(PreloadCondition[T]) - if ok { - preloadCondition = &possiblePreloadCondition - continue - } - - possibleJoinCondition, ok := condition.(IJoinCondition[T]) - if ok { - joinConditions = append(joinConditions, possibleJoinCondition) - continue - } - } - - return -} - -// Condition used to returns an error when the query is executed -type InvalidCondition[T any] struct { - Err error -} - -func (condition InvalidCondition[T]) InterfaceVerificationMethod(_ T) { - // This method is necessary to get the compiler to verify - // that an object is of type Condition[T] -} - -func (condition InvalidCondition[T]) ApplyTo(_ *Query, _ Table) error { - return condition.Err -} - -func (condition InvalidCondition[T]) GetSQL(_ *Query, _ Table) (string, []any, error) { - return "", nil, condition.Err -} - -func (condition InvalidCondition[T]) AffectsDeletedAt() bool { - return false -} - -// Condition used to returns an error when the query is executed -func NewInvalidCondition[T any](err error) InvalidCondition[T] { - return InvalidCondition[T]{ - Err: err, - } -} - -// Logical Operators -// ref: https://www.postgresql.org/docs/current/functions-logical.html - -func And[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { - return NewConnectionCondition(sql.And, conditions...) -} - -func Or[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { - return NewConnectionCondition(sql.Or, conditions...) -} - -func Not[T Model](conditions ...WhereCondition[T]) WhereCondition[T] { - return NewContainerCondition(sql.Not, conditions...) -} diff --git a/orm/condition/collection_preload_condition.go b/orm/condition/collection_preload_condition.go new file mode 100644 index 00000000..9816639e --- /dev/null +++ b/orm/condition/collection_preload_condition.go @@ -0,0 +1,66 @@ +package condition + +import ( + "github.com/elliotchance/pie/v2" + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/query" +) + +// Condition used to the preload a collection of models of a model +type collectionPreloadCondition[T1, T2 model.Model] struct { + CollectionField string + NestedPreloads []JoinCondition[T2] +} + +func (condition collectionPreloadCondition[T1, T2]) InterfaceVerificationMethod(_ T1) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T1] +} + +func (condition collectionPreloadCondition[T1, T2]) ApplyTo(queryV *query.Query, _ query.Table) error { + if len(condition.NestedPreloads) == 0 { + queryV.Preload(condition.CollectionField) + return nil + } + + queryV.Preload( + condition.CollectionField, + func(db *gorm.DB) *gorm.DB { + preloadsAsCondition := pie.Map( + condition.NestedPreloads, + func(joinCondition JoinCondition[T2]) Condition[T2] { + return joinCondition + }, + ) + + preloadQuery, err := ApplyConditions[T2](db, preloadsAsCondition) + if err != nil { + _ = db.AddError(err) + return db + } + + return preloadQuery.GormDB + }, + ) + + return nil +} + +// Condition used to the preload a collection of models of a model +func NewCollectionPreloadCondition[T1, T2 model.Model]( + collectionField string, + nestedPreloads []JoinCondition[T2], +) Condition[T1] { + if pie.Any(nestedPreloads, func(nestedPreload JoinCondition[T2]) bool { + return !nestedPreload.makesPreload() || nestedPreload.makesFilter() + }) { + return newInvalidCondition[T1](onlyPreloadsAllowedError[T1](collectionField)) + } + + return collectionPreloadCondition[T1, T2]{ + CollectionField: collectionField, + NestedPreloads: nestedPreloads, + } +} diff --git a/orm/condition/condition.go b/orm/condition/condition.go new file mode 100644 index 00000000..67aced3a --- /dev/null +++ b/orm/condition/condition.go @@ -0,0 +1,42 @@ +package condition + +import ( + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/query" +) + +type Condition[T model.Model] interface { + // Applies the condition to the "query" + // using the table holding + // the data for object of type T + ApplyTo(*query.Query, query.Table) error + + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T], + // since if no method receives by parameter a type T, + // any other Condition[T2] would also be considered a Condition[T]. + InterfaceVerificationMethod(T) +} + +// Create a Query to which the conditions are applied +func ApplyConditions[T model.Model](db *gorm.DB, conditions []Condition[T]) (*query.Query, error) { + model := *new(T) + + initialTable, err := query.NewTable(db, model) + if err != nil { + return nil, err + } + + query := query.NewQuery(db, model, initialTable) + + for _, condition := range conditions { + err := condition.ApplyTo(query, initialTable) + if err != nil { + return nil, err + } + } + + return query, nil +} diff --git a/orm/condition/connection_condition.go b/orm/condition/connection_condition.go new file mode 100644 index 00000000..6b2a2815 --- /dev/null +++ b/orm/condition/connection_condition.go @@ -0,0 +1,63 @@ +package condition + +import ( + "strings" + + "github.com/elliotchance/pie/v2" + + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/query" + "github.com/ditrit/badaas/orm/sql" +) + +// Condition that connects multiple conditions. +// Example: condition1 AND condition2 +type connectionCondition[T model.Model] struct { + Connector sql.Operator + Conditions []WhereCondition[T] +} + +func (condition connectionCondition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition connectionCondition[T]) ApplyTo(query *query.Query, table query.Table) error { + return ApplyWhereCondition[T](condition, query, table) +} + +func (condition connectionCondition[T]) GetSQL(query *query.Query, table query.Table) (string, []any, error) { + sqlStrings := []string{} + values := []any{} + + for _, internalCondition := range condition.Conditions { + internalSQLString, internalValues, err := internalCondition.GetSQL(query, table) + if err != nil { + return "", nil, err + } + + sqlStrings = append(sqlStrings, internalSQLString) + + values = append(values, internalValues...) + } + + return strings.Join( + sqlStrings, + " "+condition.Connector.String()+" ", + ), values, nil +} + +func (condition connectionCondition[T]) AffectsDeletedAt() bool { + return pie.Any(condition.Conditions, func(internalCondition WhereCondition[T]) bool { + return internalCondition.AffectsDeletedAt() + }) +} + +// Condition that connects multiple conditions. +// Example: condition1 AND condition2 +func NewConnectionCondition[T model.Model](connector sql.Operator, conditions ...WhereCondition[T]) WhereCondition[T] { + return connectionCondition[T]{ + Connector: connector, + Conditions: conditions, + } +} diff --git a/orm/condition/container_condition.go b/orm/condition/container_condition.go new file mode 100644 index 00000000..61f7b823 --- /dev/null +++ b/orm/condition/container_condition.go @@ -0,0 +1,51 @@ +package condition + +import ( + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/query" + "github.com/ditrit/badaas/orm/sql" +) + +// Condition that contains a internal condition. +// Example: NOT (internal condition) +type containerCondition[T model.Model] struct { + ConnectionCondition WhereCondition[T] + Prefix sql.Operator +} + +func (condition containerCondition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition containerCondition[T]) ApplyTo(query *query.Query, table query.Table) error { + return ApplyWhereCondition[T](condition, query, table) +} + +func (condition containerCondition[T]) GetSQL(query *query.Query, table query.Table) (string, []any, error) { + sqlString, values, err := condition.ConnectionCondition.GetSQL(query, table) + if err != nil { + return "", nil, err + } + + sqlString = condition.Prefix.String() + " (" + sqlString + ")" + + return sqlString, values, nil +} + +func (condition containerCondition[T]) AffectsDeletedAt() bool { + return condition.ConnectionCondition.AffectsDeletedAt() +} + +// Condition that contains a internal condition. +// Example: NOT (internal condition) +func NewContainerCondition[T model.Model](prefix sql.Operator, conditions ...WhereCondition[T]) WhereCondition[T] { + if len(conditions) == 0 { + return newInvalidCondition[T](emptyConditionsError[T](prefix)) + } + + return containerCondition[T]{ + Prefix: prefix, + ConnectionCondition: And(conditions...), + } +} diff --git a/orm/condition/errors.go b/orm/condition/errors.go new file mode 100644 index 00000000..aafcf390 --- /dev/null +++ b/orm/condition/errors.go @@ -0,0 +1,36 @@ +package condition + +import ( + "fmt" + + "github.com/ditrit/badaas/orm/errors" + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/sql" +) + +func conditionOperatorError[TObject model.Model, TAtribute any](operatorErr error, condition fieldCondition[TObject, TAtribute]) error { + return fmt.Errorf( + "%w; model: %T, field: %s", + operatorErr, + *new(TObject), + condition.FieldIdentifier.Field, + ) +} + +func emptyConditionsError[T model.Model](connector sql.Operator) error { + return fmt.Errorf( + "%w; connector: %s; model: %T", + errors.ErrEmptyConditions, + connector.Name(), + *new(T), + ) +} + +func onlyPreloadsAllowedError[T model.Model](fieldName string) error { + return fmt.Errorf( + "%w; model: %T, field: %s", + errors.ErrOnlyPreloadsAllowed, + *new(T), + fieldName, + ) +} diff --git a/orm/condition/field_condition.go b/orm/condition/field_condition.go new file mode 100644 index 00000000..4759870c --- /dev/null +++ b/orm/condition/field_condition.go @@ -0,0 +1,53 @@ +package condition + +import ( + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/operator" + "github.com/ditrit/badaas/orm/query" +) + +const deletedAtField = "DeletedAt" + +// Condition that verifies the value of a field, +// using the Operator +type fieldCondition[TObject model.Model, TAtribute any] struct { + FieldIdentifier query.FieldIdentifier[TAtribute] + Operator operator.Operator[TAtribute] +} + +func (condition fieldCondition[TObject, TAtribute]) InterfaceVerificationMethod(_ TObject) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +// Returns a gorm Where condition that can be used +// to filter that the Field as a value of Value +func (condition fieldCondition[TObject, TAtribute]) ApplyTo(query *query.Query, table query.Table) error { + return ApplyWhereCondition[TObject](condition, query, table) +} + +func (condition fieldCondition[TObject, TAtribute]) AffectsDeletedAt() bool { + return condition.FieldIdentifier.Field == deletedAtField +} + +func (condition fieldCondition[TObject, TAtribute]) GetSQL(query *query.Query, table query.Table) (string, []any, error) { + sqlString, values, err := condition.Operator.ToSQL( + query, + condition.FieldIdentifier.ColumnSQL(query, table), + ) + if err != nil { + return "", nil, conditionOperatorError[TObject](err, condition) + } + + return sqlString, values, nil +} + +func NewFieldCondition[TObject model.Model, TAtribute any]( + fieldIdentifier query.FieldIdentifier[TAtribute], + operator operator.Operator[TAtribute], +) WhereCondition[TObject] { + return fieldCondition[TObject, TAtribute]{ + FieldIdentifier: fieldIdentifier, + Operator: operator, + } +} diff --git a/orm/condition/invalid_condition.go b/orm/condition/invalid_condition.go new file mode 100644 index 00000000..d400fccb --- /dev/null +++ b/orm/condition/invalid_condition.go @@ -0,0 +1,32 @@ +package condition + +import "github.com/ditrit/badaas/orm/query" + +// Condition used to returns an error when the query is executed +type invalidCondition[T any] struct { + Err error +} + +func (condition invalidCondition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition invalidCondition[T]) ApplyTo(_ *query.Query, _ query.Table) error { + return condition.Err +} + +func (condition invalidCondition[T]) GetSQL(_ *query.Query, _ query.Table) (string, []any, error) { + return "", nil, condition.Err +} + +func (condition invalidCondition[T]) AffectsDeletedAt() bool { + return false +} + +// Condition used to returns an error when the query is executed +func newInvalidCondition[T any](err error) invalidCondition[T] { + return invalidCondition[T]{ + Err: err, + } +} diff --git a/orm/condition/join_condition.go b/orm/condition/join_condition.go new file mode 100644 index 00000000..68be060d --- /dev/null +++ b/orm/condition/join_condition.go @@ -0,0 +1,206 @@ +package condition + +import ( + "fmt" + + "github.com/elliotchance/pie/v2" + + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/query" +) + +// Condition that joins T with any other model +type JoinCondition[T model.Model] interface { + Condition[T] + + // Returns true if this condition or any nested condition makes a preload + makesPreload() bool + + // Returns true if the condition of nay nested condition applies a filter (has where conditions) + makesFilter() bool +} + +// Condition that joins T with any other model +func NewJoinCondition[T1, T2 model.Model]( + conditions []Condition[T2], + relationField string, + t1Field string, + t1PreloadCondition Condition[T1], + t2Field string, +) JoinCondition[T1] { + return joinConditionImpl[T1, T2]{ + Conditions: conditions, + RelationField: relationField, + T1Field: t1Field, + T1PreloadCondition: t1PreloadCondition, + T2Field: t2Field, + } +} + +// Implementation of join condition +type joinConditionImpl[T1, T2 model.Model] struct { + T1Field string + T2Field string + RelationField string + Conditions []Condition[T2] + // condition to preload T1 in case T2 any nested object is preloaded by user + T1PreloadCondition Condition[T1] +} + +func (condition joinConditionImpl[T1, T2]) InterfaceVerificationMethod(_ T1) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +// Returns true if this condition or any nested condition makes a preload +func (condition joinConditionImpl[T1, T2]) makesPreload() bool { + _, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) + + return t2PreloadCondition != nil || pie.Any(joinConditions, func(cond JoinCondition[T2]) bool { + return cond.makesPreload() + }) +} + +// Returns true if the condition of nay nested condition applies a filter (has where conditions) +// +//nolint:unused // is used +func (condition joinConditionImpl[T1, T2]) makesFilter() bool { + whereConditions, joinConditions, _ := divideConditionsByType(condition.Conditions) + + return len(whereConditions) != 0 || pie.Any(joinConditions, func(cond JoinCondition[T2]) bool { + return cond.makesFilter() + }) +} + +// Applies a join between the tables of T1 and T2 +// previousTableName is the name of the table of T1 +// It also applies the nested conditions +func (condition joinConditionImpl[T1, T2]) ApplyTo(query *query.Query, t1Table query.Table) error { + whereConditions, joinConditions, t2PreloadCondition := divideConditionsByType(condition.Conditions) + + t2Model := *new(T2) + + // get the sql to do the join with T2 + t2Table, err := t1Table.DeliverTable(query, t2Model, condition.RelationField) + if err != nil { + return err + } + + makesPreload := condition.makesPreload() + joinQuery := condition.getSQLJoin( + query, + t1Table, + t2Table, + len(whereConditions) == 0 && makesPreload, + ) + + query.AddConcernedModel( + t2Model, + t2Table, + ) + + // apply WhereConditions to the join in the "on" clause + connectionCondition := And(whereConditions...) + + onQuery, onValues, err := connectionCondition.GetSQL(query, t2Table) + if err != nil { + return err + } + + if onQuery != "" { + joinQuery += " AND " + onQuery + } + + if !connectionCondition.AffectsDeletedAt() { + joinQuery += fmt.Sprintf( + " AND %s.deleted_at IS NULL", + t2Table.Alias, + ) + } + + // add the join to the query + query.Joins(joinQuery, onValues...) + + // apply T1 preload condition + // if this condition has a T2 preload condition + // or any nested join condition has a preload condition + // and this is not first level (T1 is the type of the repository) + // because T1 is always loaded in that case + if makesPreload && !t1Table.IsInitial() { + err = condition.T1PreloadCondition.ApplyTo(query, t1Table) + if err != nil { + return err + } + } + + // apply T2 preload condition + if t2PreloadCondition != nil { + err = t2PreloadCondition.ApplyTo(query, t2Table) + if err != nil { + return err + } + } + + // apply nested joins + for _, joinCondition := range joinConditions { + err = joinCondition.ApplyTo(query, t2Table) + if err != nil { + return err + } + } + + return nil +} + +// Returns the SQL string to do a join between T1 and T2 +// taking into account that the ID attribute necessary to do it +// can be either in T1's or T2's table. +func (condition joinConditionImpl[T1, T2]) getSQLJoin( + query *query.Query, + t1Table query.Table, + t2Table query.Table, + isLeftJoin bool, +) string { + joinString := "INNER JOIN" + if isLeftJoin { + joinString = "LEFT JOIN" + } + + return fmt.Sprintf( + `%[6]s %[1]s %[2]s ON %[2]s.%[3]s = %[4]s.%[5]s + `, + t2Table.Name, + t2Table.Alias, + query.ColumnName(t2Table, condition.T2Field), + t1Table.Alias, + query.ColumnName(t1Table, condition.T1Field), + joinString, + ) +} + +// Divides a list of conditions by its type: WhereConditions and JoinConditions +func divideConditionsByType[T model.Model]( + conditions []Condition[T], +) (whereConditions []WhereCondition[T], joinConditions []JoinCondition[T], preload *preloadCondition[T]) { + for _, condition := range conditions { + possibleWhereCondition, ok := condition.(WhereCondition[T]) + if ok { + whereConditions = append(whereConditions, possibleWhereCondition) + continue + } + + possiblePreloadCondition, ok := condition.(preloadCondition[T]) + if ok { + preload = &possiblePreloadCondition + continue + } + + possibleJoinCondition, ok := condition.(JoinCondition[T]) + if ok { + joinConditions = append(joinConditions, possibleJoinCondition) + continue + } + } + + return +} diff --git a/orm/condition/logical.go b/orm/condition/logical.go new file mode 100644 index 00000000..e5edb34b --- /dev/null +++ b/orm/condition/logical.go @@ -0,0 +1,10 @@ +package condition + +import ( + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/sql" +) + +func And[T model.Model](conditions ...WhereCondition[T]) WhereCondition[T] { + return NewConnectionCondition(sql.And, conditions...) +} diff --git a/orm/condition/preload_condition.go b/orm/condition/preload_condition.go new file mode 100644 index 00000000..3f2ef1ac --- /dev/null +++ b/orm/condition/preload_condition.go @@ -0,0 +1,31 @@ +package condition + +import ( + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/query" +) + +// Condition used to the preload the attributes of a model +type preloadCondition[T model.Model] struct { + Fields []query.IFieldIdentifier +} + +func (condition preloadCondition[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Condition[T] +} + +func (condition preloadCondition[T]) ApplyTo(query *query.Query, table query.Table) error { + for _, fieldID := range condition.Fields { + query.AddSelect(table, fieldID) + } + + return nil +} + +// Condition used to the preload the attributes of a model +func NewPreloadCondition[T model.Model](fields ...query.IFieldIdentifier) Condition[T] { + return preloadCondition[T]{ + Fields: fields, + } +} diff --git a/orm/condition/where_condition.go b/orm/condition/where_condition.go new file mode 100644 index 00000000..1eb42ec9 --- /dev/null +++ b/orm/condition/where_condition.go @@ -0,0 +1,38 @@ +package condition + +import ( + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/query" +) + +// Conditions that can be used in a where clause +// (or in a on of a join) +type WhereCondition[T model.Model] interface { + Condition[T] + + // Get the sql string and values to use in the query + GetSQL(query *query.Query, table query.Table) (string, []any, error) + + // Returns true if the DeletedAt column if affected by the condition + // If no condition affects the DeletedAt, the verification that it's null will be added automatically + AffectsDeletedAt() bool +} + +// apply WhereCondition of any type on the query +func ApplyWhereCondition[T model.Model](condition WhereCondition[T], query *query.Query, table query.Table) error { + sql, values, err := condition.GetSQL(query, table) + if err != nil { + return err + } + + if condition.AffectsDeletedAt() { + query.Unscoped() + } + + query.Where( + sql, + values..., + ) + + return nil +} diff --git a/orm/crudRepository.go b/orm/crudRepository.go index a0e1eb51..90af79ad 100644 --- a/orm/crudRepository.go +++ b/orm/crudRepository.go @@ -1,16 +1,16 @@ package orm import ( - "errors" - "sync" - "gorm.io/gorm" - "gorm.io/gorm/schema" + + "github.com/ditrit/badaas/orm/condition" + "github.com/ditrit/badaas/orm/errors" + "github.com/ditrit/badaas/orm/model" ) // Generic CRUD Repository // T can be any model whose identifier attribute is of type ID -type CRUDRepository[T Model, ID ModelID] interface { +type CRUDRepository[T model.Model, ID model.ID] interface { // Create model "model" inside transaction "tx" Create(tx *gorm.DB, entity *T) error @@ -20,10 +20,10 @@ type CRUDRepository[T Model, ID ModelID] interface { // Get only one model that match "conditions" inside transaction "tx" // or returns error if 0 or more than 1 are found. - QueryOne(tx *gorm.DB, conditions ...Condition[T]) (*T, error) + QueryOne(tx *gorm.DB, conditions ...condition.Condition[T]) (*T, error) // Get the list of models that match "conditions" inside transaction "tx" - Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) + Query(tx *gorm.DB, conditions ...condition.Condition[T]) ([]*T, error) // Save model "model" inside transaction "tx" Save(tx *gorm.DB, entity *T) error @@ -32,38 +32,33 @@ type CRUDRepository[T Model, ID ModelID] interface { Delete(tx *gorm.DB, entity *T) error } -var ( - ErrMoreThanOneObjectFound = errors.New("found more that one object that meet the requested conditions") - ErrObjectNotFound = errors.New("no object exists that meets the requested conditions") -) - // Implementation of the Generic CRUD Repository -type CRUDRepositoryImpl[T Model, ID ModelID] struct { +type crudRepositoryImpl[T model.Model, ID model.ID] struct { CRUDRepository[T, ID] } // Constructor of the Generic CRUD Repository -func NewCRUDRepository[T Model, ID ModelID]() CRUDRepository[T, ID] { - return &CRUDRepositoryImpl[T, ID]{} +func NewCRUDRepository[T model.Model, ID model.ID]() CRUDRepository[T, ID] { + return &crudRepositoryImpl[T, ID]{} } -// Create object "entity" inside transaction "tx" -func (repository *CRUDRepositoryImpl[T, ID]) Create(tx *gorm.DB, entity *T) error { - return tx.Create(entity).Error +// Create model "model" inside transaction "tx" +func (repository *crudRepositoryImpl[T, ID]) Create(tx *gorm.DB, model *T) error { + return tx.Create(model).Error } -// Delete object "entity" inside transaction "tx" -func (repository *CRUDRepositoryImpl[T, ID]) Delete(tx *gorm.DB, entity *T) error { - return tx.Delete(entity).Error +// Delete model "model" inside transaction "tx" +func (repository *crudRepositoryImpl[T, ID]) Delete(tx *gorm.DB, model *T) error { + return tx.Delete(model).Error } -// Save object "entity" inside transaction "tx" -func (repository *CRUDRepositoryImpl[T, ID]) Save(tx *gorm.DB, entity *T) error { - return tx.Save(entity).Error +// Save model "model" inside transaction "tx" +func (repository *crudRepositoryImpl[T, ID]) Save(tx *gorm.DB, model *T) error { + return tx.Save(model).Error } // Get a model by its ID -func (repository *CRUDRepositoryImpl[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, error) { +func (repository *crudRepositoryImpl[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, error) { var model T err := tx.First(&model, "id = ?", id).Error @@ -76,7 +71,7 @@ func (repository *CRUDRepositoryImpl[T, ID]) GetByID(tx *gorm.DB, id ID) (*T, er // Get only one model that match "conditions" inside transaction "tx" // or returns error if 0 or more than 1 are found. -func (repository *CRUDRepositoryImpl[T, ID]) QueryOne(tx *gorm.DB, conditions ...Condition[T]) (*T, error) { +func (repository *crudRepositoryImpl[T, ID]) QueryOne(tx *gorm.DB, conditions ...condition.Condition[T]) (*T, error) { entities, err := repository.Query(tx, conditions...) if err != nil { return nil, err @@ -86,15 +81,15 @@ func (repository *CRUDRepositoryImpl[T, ID]) QueryOne(tx *gorm.DB, conditions .. case len(entities) == 1: return entities[0], nil case len(entities) == 0: - return nil, ErrObjectNotFound + return nil, errors.ErrObjectNotFound default: - return nil, ErrMoreThanOneObjectFound + return nil, errors.ErrMoreThanOneObjectFound } } // Get the list of models that match "conditions" inside transaction "tx" -func (repository *CRUDRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...Condition[T]) ([]*T, error) { - query, err := NewQuery(tx, conditions) +func (repository *crudRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...condition.Condition[T]) ([]*T, error) { + query, err := condition.ApplyConditions(tx, conditions) if err != nil { return nil, err } @@ -105,15 +100,3 @@ func (repository *CRUDRepositoryImpl[T, ID]) Query(tx *gorm.DB, conditions ...Co return entities, err } - -// Get the name of the table in "db" in which the data for "entity" is saved -// returns error is table name can not be found by gorm, -// probably because the type of "entity" is not registered using AddModel -func getTableName(db *gorm.DB, entity any) (string, error) { - schemaName, err := schema.Parse(entity, &sync.Map{}, db.NamingStrategy) - if err != nil { - return "", err - } - - return schemaName.Table, nil -} diff --git a/orm/crudService.go b/orm/crudService.go index 579cc44f..defd618a 100644 --- a/orm/crudService.go +++ b/orm/crudService.go @@ -2,32 +2,35 @@ package orm import ( "gorm.io/gorm" + + "github.com/ditrit/badaas/orm/condition" + "github.com/ditrit/badaas/orm/model" ) // T can be any model whose identifier attribute is of type ID -type CRUDService[T Model, ID ModelID] interface { +type CRUDService[T model.Model, ID model.ID] interface { // Get the model of type T that has the "id" GetByID(id ID) (*T, error) // Get only one model that match "conditions" // or return error if 0 or more than 1 are found. - QueryOne(conditions ...Condition[T]) (*T, error) + QueryOne(conditions ...condition.Condition[T]) (*T, error) // Get the list of models that match "conditions" - Query(conditions ...Condition[T]) ([]*T, error) + Query(conditions ...condition.Condition[T]) ([]*T, error) } // check interface compliance -var _ CRUDService[UUIDModel, UUID] = (*crudServiceImpl[UUIDModel, UUID])(nil) +var _ CRUDService[model.UUIDModel, model.UUID] = (*crudServiceImpl[model.UUIDModel, model.UUID])(nil) // Implementation of the CRUD Service -type crudServiceImpl[T Model, ID ModelID] struct { +type crudServiceImpl[T model.Model, ID model.ID] struct { CRUDService[T, ID] db *gorm.DB repository CRUDRepository[T, ID] } -func NewCRUDService[T Model, ID ModelID]( +func NewCRUDService[T model.Model, ID model.ID]( db *gorm.DB, repository CRUDRepository[T, ID], ) CRUDService[T, ID] { @@ -44,11 +47,11 @@ func (service *crudServiceImpl[T, ID]) GetByID(id ID) (*T, error) { // Get only one model that match "conditions" // or return error if 0 or more than 1 are found. -func (service *crudServiceImpl[T, ID]) QueryOne(conditions ...Condition[T]) (*T, error) { +func (service *crudServiceImpl[T, ID]) QueryOne(conditions ...condition.Condition[T]) (*T, error) { return service.repository.QueryOne(service.db, conditions...) } // Get the list of models that match "conditions" -func (service *crudServiceImpl[T, ID]) Query(conditions ...Condition[T]) ([]*T, error) { +func (service *crudServiceImpl[T, ID]) Query(conditions ...condition.Condition[T]) ([]*T, error) { return service.repository.Query(service.db, conditions...) } diff --git a/orm/dynamic/operator.go b/orm/dynamic/operator.go index 1e0cb81e..35468b81 100644 --- a/orm/dynamic/operator.go +++ b/orm/dynamic/operator.go @@ -1,13 +1,14 @@ package dynamic import ( - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/operator" + "github.com/ditrit/badaas/orm/query" "github.com/ditrit/badaas/orm/sql" ) -func NewValueOperator[T any]( +func newValueOperator[T any]( sqlOperator sql.Operator, - field orm.FieldIdentifier[T], -) *orm.ValueOperator[T] { - return orm.NewValueOperator[T](sqlOperator, field) + field query.FieldIdentifier[T], +) *operator.ValueOperator[T] { + return operator.NewValueOperator[T](sqlOperator, field) } diff --git a/orm/dynamic/operators.go b/orm/dynamic/operators.go index 2fb68eba..ec30eacf 100644 --- a/orm/dynamic/operators.go +++ b/orm/dynamic/operators.go @@ -1,7 +1,8 @@ package dynamic import ( - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/operator" + "github.com/ditrit/badaas/orm/query" "github.com/ditrit/badaas/orm/sql" ) @@ -9,59 +10,59 @@ import ( // ref: https://www.postgresql.org/docs/current/functions-comparison.html // EqualTo -func Eq[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { - return NewValueOperator(sql.Eq, field) +func Eq[T any](field query.FieldIdentifier[T]) operator.DynamicOperator[T] { + return newValueOperator(sql.Eq, field) } // NotEqualTo -func NotEq[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { - return NewValueOperator(sql.NotEq, field) +func NotEq[T any](field query.FieldIdentifier[T]) operator.DynamicOperator[T] { + return newValueOperator(sql.NotEq, field) } // LessThan -func Lt[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { - return NewValueOperator(sql.Lt, field) +func Lt[T any](field query.FieldIdentifier[T]) operator.DynamicOperator[T] { + return newValueOperator(sql.Lt, field) } // LessThanOrEqualTo -func LtOrEq[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { - return NewValueOperator(sql.LtOrEq, field) +func LtOrEq[T any](field query.FieldIdentifier[T]) operator.DynamicOperator[T] { + return newValueOperator(sql.LtOrEq, field) } // GreaterThan -func Gt[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { - return NewValueOperator(sql.Gt, field) +func Gt[T any](field query.FieldIdentifier[T]) operator.DynamicOperator[T] { + return newValueOperator(sql.Gt, field) } // GreaterThanOrEqualTo -func GtOrEq[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { - return NewValueOperator(sql.GtOrEq, field) +func GtOrEq[T any](field query.FieldIdentifier[T]) operator.DynamicOperator[T] { + return newValueOperator(sql.GtOrEq, field) } // Comparison Predicates // ref: https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE // Equivalent to field1 < value < field2 -func Between[T any](field1, field2 orm.FieldIdentifier[T]) orm.DynamicOperator[T] { +func Between[T any](field1, field2 query.FieldIdentifier[T]) operator.DynamicOperator[T] { return newBetweenOperator(sql.Between, field1, field2) } // Equivalent to NOT (field1 < value < field2) -func NotBetween[T any](field1, field2 orm.FieldIdentifier[T]) orm.DynamicOperator[T] { +func NotBetween[T any](field1, field2 query.FieldIdentifier[T]) operator.DynamicOperator[T] { return newBetweenOperator(sql.NotBetween, field1, field2) } -func newBetweenOperator[T any](sqlOperator sql.Operator, field1, field2 orm.FieldIdentifier[T]) orm.DynamicOperator[T] { - operator := NewValueOperator(sqlOperator, field1) +func newBetweenOperator[T any](sqlOperator sql.Operator, field1, field2 query.FieldIdentifier[T]) operator.DynamicOperator[T] { + operator := newValueOperator(sqlOperator, field1) return operator.AddOperation(sql.And, field2) } // Boolean Comparison Predicates -func IsDistinct[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { - return NewValueOperator(sql.IsDistinct, field) +func IsDistinct[T any](field query.FieldIdentifier[T]) operator.DynamicOperator[T] { + return newValueOperator(sql.IsDistinct, field) } -func IsNotDistinct[T any](field orm.FieldIdentifier[T]) orm.DynamicOperator[T] { - return NewValueOperator(sql.IsNotDistinct, field) +func IsNotDistinct[T any](field query.FieldIdentifier[T]) operator.DynamicOperator[T] { + return newValueOperator(sql.IsNotDistinct, field) } diff --git a/orm/errors.go b/orm/errors.go deleted file mode 100644 index e6cda9d9..00000000 --- a/orm/errors.go +++ /dev/null @@ -1,67 +0,0 @@ -package orm - -import ( - "errors" - "fmt" - - "github.com/ditrit/badaas/orm/sql" -) - -// operators - -var ( - ErrFieldModelNotConcerned = errors.New("field's model is not concerned by the query (not joined)") - ErrJoinMustBeSelected = errors.New("field's model is joined more than once, select which one you want to use with SelectJoin") -) - -func OperatorError(err error, sqlOperator sql.Operator) error { - return fmt.Errorf("%w; operator: %s", err, sqlOperator.Name()) -} - -func fieldModelNotConcernedError(field iFieldIdentifier, sqlOperator sql.Operator) error { - return OperatorError(fmt.Errorf("%w; not concerned model: %s", - ErrFieldModelNotConcerned, - field.GetModelType(), - ), sqlOperator) -} - -func joinMustBeSelectedError(field iFieldIdentifier, sqlOperator sql.Operator) error { - return OperatorError(fmt.Errorf("%w; joined multiple times model: %s", - ErrJoinMustBeSelected, - field.GetModelType(), - ), sqlOperator) -} - -// conditions - -var ( - ErrEmptyConditions = errors.New("condition must have at least one inner condition") - ErrOnlyPreloadsAllowed = errors.New("only conditions that do a preload are allowed") -) - -func conditionOperatorError[TObject Model, TAtribute any](operatorErr error, condition FieldCondition[TObject, TAtribute]) error { - return fmt.Errorf( - "%w; model: %T, field: %s", - operatorErr, - *new(TObject), - condition.FieldIdentifier.Field, - ) -} - -func emptyConditionsError[T Model](connector sql.Operator) error { - return fmt.Errorf( - "%w; connector: %s; model: %T", - ErrEmptyConditions, - connector.Name(), - *new(T), - ) -} - -func onlyPreloadsAllowedError[T Model](fieldName string) error { - return fmt.Errorf( - "%w; model: %T, field: %s", - ErrOnlyPreloadsAllowed, - *new(T), - fieldName, - ) -} diff --git a/orm/errors/errors.go b/orm/errors/errors.go new file mode 100644 index 00000000..f6a92918 --- /dev/null +++ b/orm/errors/errors.go @@ -0,0 +1,22 @@ +package errors + +import ( + "errors" +) + +var ( + // operators + ErrFieldModelNotConcerned = errors.New("field's model is not concerned by the query (not joined)") + ErrJoinMustBeSelected = errors.New("field's model is joined more than once, select which one you want to use with SelectJoin") + + // conditions + ErrEmptyConditions = errors.New("condition must have at least one inner condition") + ErrOnlyPreloadsAllowed = errors.New("only conditions that do a preload are allowed") + + // crud + ErrMoreThanOneObjectFound = errors.New("found more that one object that meet the requested conditions") + ErrObjectNotFound = errors.New("no object exists that meets the requested conditions") + + // preload + ErrRelationNotLoaded = errors.New("relation not loaded") +) diff --git a/orm/logical.go b/orm/logical.go new file mode 100644 index 00000000..82f0fa6a --- /dev/null +++ b/orm/logical.go @@ -0,0 +1,22 @@ +package orm + +import ( + "github.com/ditrit/badaas/orm/condition" + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/sql" +) + +// Logical Operators +// ref: https://www.postgresql.org/docs/current/functions-logical.html + +func And[T model.Model](conditions ...condition.WhereCondition[T]) condition.WhereCondition[T] { + return condition.And(conditions...) +} + +func Or[T model.Model](conditions ...condition.WhereCondition[T]) condition.WhereCondition[T] { + return condition.NewConnectionCondition(sql.Or, conditions...) +} + +func Not[T model.Model](conditions ...condition.WhereCondition[T]) condition.WhereCondition[T] { + return condition.NewContainerCondition(sql.Not, conditions...) +} diff --git a/orm/baseModels.go b/orm/model/models.go similarity index 96% rename from orm/baseModels.go rename to orm/model/models.go index bca7d931..502c7a64 100644 --- a/orm/baseModels.go +++ b/orm/model/models.go @@ -1,4 +1,4 @@ -package orm +package model import ( "time" @@ -7,7 +7,7 @@ import ( ) // supported types for model identifier -type ModelID interface { +type ID interface { UIntID | UUID IsNil() bool diff --git a/orm/uuid.go b/orm/model/uuid.go similarity index 99% rename from orm/uuid.go rename to orm/model/uuid.go index 54b0c5c1..4fe59302 100644 --- a/orm/uuid.go +++ b/orm/model/uuid.go @@ -1,4 +1,4 @@ -package orm +package model import ( "context" diff --git a/orm/uuid_test.go b/orm/model/uuid_test.go similarity index 63% rename from orm/uuid_test.go rename to orm/model/uuid_test.go index 1e6828ec..26ed7f7d 100644 --- a/orm/uuid_test.go +++ b/orm/model/uuid_test.go @@ -1,4 +1,4 @@ -package orm_test +package model_test import ( "testing" @@ -6,18 +6,18 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" ) func TestParseCorrectUUID(t *testing.T) { uuidString := uuid.New().String() - uuid, err := orm.ParseUUID(uuidString) + uuid, err := model.ParseUUID(uuidString) assert.Nil(t, err) assert.Equal(t, uuidString, uuid.String()) } func TestParseIncorrectUUID(t *testing.T) { - uid, err := orm.ParseUUID("not uuid") + uid, err := model.ParseUUID("not uuid") assert.Error(t, err) - assert.Equal(t, orm.NilUUID, uid) + assert.Equal(t, model.NilUUID, uid) } diff --git a/orm/operator/errors.go b/orm/operator/errors.go new file mode 100644 index 00000000..56383c2e --- /dev/null +++ b/orm/operator/errors.go @@ -0,0 +1,27 @@ +package operator + +import ( + "fmt" + + "github.com/ditrit/badaas/orm/errors" + "github.com/ditrit/badaas/orm/query" + "github.com/ditrit/badaas/orm/sql" +) + +func operatorError(err error, sqlOperator sql.Operator) error { + return fmt.Errorf("%w; operator: %s", err, sqlOperator.Name()) +} + +func fieldModelNotConcernedError(field query.IFieldIdentifier, sqlOperator sql.Operator) error { + return operatorError(fmt.Errorf("%w; not concerned model: %s", + errors.ErrFieldModelNotConcerned, + field.GetModelType(), + ), sqlOperator) +} + +func joinMustBeSelectedError(field query.IFieldIdentifier, sqlOperator sql.Operator) error { + return operatorError(fmt.Errorf("%w; joined multiple times model: %s", + errors.ErrJoinMustBeSelected, + field.GetModelType(), + ), sqlOperator) +} diff --git a/orm/operator/operator.go b/orm/operator/operator.go new file mode 100644 index 00000000..6579c0cd --- /dev/null +++ b/orm/operator/operator.go @@ -0,0 +1,25 @@ +package operator + +import "github.com/ditrit/badaas/orm/query" + +type Operator[T any] interface { + // Transform the Operator to a SQL string and a list of values to use in the query + // columnName is used by the operator to determine which is the objective column. + ToSQL(query *query.Query, columnName string) (string, []any, error) + + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T], + // since if no method receives by parameter a type T, + // any other Operator[T2] would also be considered a Operator[T]. + InterfaceVerificationMethod(T) +} + +type DynamicOperator[T any] interface { + Operator[T] + + // Allows to choose which number of join use + // for the value in position "valueNumber" + // when the value is a field and its model is joined more than once. + // Does nothing if the valueNumber is bigger than the amount of values. + SelectJoin(valueNumber, joinNumber uint) DynamicOperator[T] +} diff --git a/orm/operator/predicate_operator.go b/orm/operator/predicate_operator.go new file mode 100644 index 00000000..5093d3d2 --- /dev/null +++ b/orm/operator/predicate_operator.go @@ -0,0 +1,28 @@ +package operator + +import ( + "fmt" + + "github.com/ditrit/badaas/orm/query" +) + +// Operator that verifies a predicate +// Example: value IS TRUE +type PredicateOperator[T any] struct { + SQLOperator string +} + +func (operator PredicateOperator[T]) InterfaceVerificationMethod(_ T) { + // This method is necessary to get the compiler to verify + // that an object is of type Operator[T] +} + +func (operator PredicateOperator[T]) ToSQL(_ *query.Query, columnName string) (string, []any, error) { + return fmt.Sprintf("%s %s", columnName, operator.SQLOperator), []any{}, nil +} + +func NewPredicateOperator[T any](sqlOperator string) PredicateOperator[T] { + return PredicateOperator[T]{ + SQLOperator: sqlOperator, + } +} diff --git a/orm/operator.go b/orm/operator/value_operator.go similarity index 54% rename from orm/operator.go rename to orm/operator/value_operator.go index 7acd984c..b2277cba 100644 --- a/orm/operator.go +++ b/orm/operator/value_operator.go @@ -1,33 +1,12 @@ -package orm +package operator import ( "fmt" + "github.com/ditrit/badaas/orm/query" "github.com/ditrit/badaas/orm/sql" ) -type Operator[T any] interface { - // Transform the Operator to a SQL string and a list of values to use in the query - // columnName is used by the operator to determine which is the objective column. - ToSQL(query *Query, columnName string) (string, []any, error) - - // This method is necessary to get the compiler to verify - // that an object is of type Operator[T], - // since if no method receives by parameter a type T, - // any other Operator[T2] would also be considered a Operator[T]. - InterfaceVerificationMethod(T) -} - -type DynamicOperator[T any] interface { - Operator[T] - - // Allows to choose which number of join use - // for the value in position "valueNumber" - // when the value is a field and its model is joined more than once. - // Does nothing if the valueNumber is bigger than the amount of values. - SelectJoin(valueNumber, joinNumber uint) DynamicOperator[T] -} - const undefinedJoinNumber = -1 // Operator that compares the value of the column against a fixed value @@ -44,6 +23,10 @@ type operation struct { JoinNumber int } +func NewValueOperator[T any](sqlOperator sql.Operator, value any) *ValueOperator[T] { + return new(ValueOperator[T]).AddOperation(sqlOperator, value) +} + func (operator ValueOperator[T]) InterfaceVerificationMethod(_ T) { // This method is necessary to get the compiler to verify // that an object is of type Operator[T] @@ -65,19 +48,19 @@ func (operator *ValueOperator[T]) SelectJoin(operationNumber, joinNumber uint) D return operator } -func (operator ValueOperator[T]) ToSQL(query *Query, columnName string) (string, []any, error) { +func (operator ValueOperator[T]) ToSQL(queryV *query.Query, columnName string) (string, []any, error) { operationString := columnName values := []any{} // add each operation to the sql for _, operation := range operator.Operations { - field, isField := operation.Value.(iFieldIdentifier) + field, isField := operation.Value.(query.IFieldIdentifier) if isField { // if the value of the operation is a field, // verify that this field is concerned by the query // (a join was performed with the model to which this field belongs) // and get the alias of the table of this model. - modelTable, err := getModelTable(query, field, operation.JoinNumber, operation.SQLOperator) + modelTable, err := getModelTable(queryV, field, operation.JoinNumber, operation.SQLOperator) if err != nil { return "", nil, err } @@ -85,7 +68,7 @@ func (operator ValueOperator[T]) ToSQL(query *Query, columnName string) (string, operationString += fmt.Sprintf( " %s %s", operation.SQLOperator, - field.ColumnSQL(query, modelTable), + field.ColumnSQL(queryV, modelTable), ) } else { operationString += " " + operation.SQLOperator.String() + " ?" @@ -96,10 +79,10 @@ func (operator ValueOperator[T]) ToSQL(query *Query, columnName string) (string, return operationString, values, nil } -func getModelTable(query *Query, field iFieldIdentifier, joinNumber int, sqlOperator sql.Operator) (Table, error) { - modelTables := query.GetTables(field.GetModelType()) +func getModelTable(queryV *query.Query, field query.IFieldIdentifier, joinNumber int, sqlOperator sql.Operator) (query.Table, error) { + modelTables := queryV.GetTables(field.GetModelType()) if modelTables == nil { - return Table{}, fieldModelNotConcernedError(field, sqlOperator) + return query.Table{}, fieldModelNotConcernedError(field, sqlOperator) } if len(modelTables) == 1 { @@ -107,16 +90,12 @@ func getModelTable(query *Query, field iFieldIdentifier, joinNumber int, sqlOper } if joinNumber == undefinedJoinNumber { - return Table{}, joinMustBeSelectedError(field, sqlOperator) + return query.Table{}, joinMustBeSelectedError(field, sqlOperator) } return modelTables[joinNumber], nil } -func NewValueOperator[T any](sqlOperator sql.Operator, value any) *ValueOperator[T] { - return new(ValueOperator[T]).AddOperation(sqlOperator, value) -} - func (operator *ValueOperator[T]) AddOperation(sqlOperator sql.Operator, value any) *ValueOperator[T] { operator.Operations = append( operator.Operations, @@ -129,24 +108,3 @@ func (operator *ValueOperator[T]) AddOperation(sqlOperator sql.Operator, value a return operator } - -// Operator that verifies a predicate -// Example: value IS TRUE -type PredicateOperator[T any] struct { - SQLOperator string -} - -func (operator PredicateOperator[T]) InterfaceVerificationMethod(_ T) { - // This method is necessary to get the compiler to verify - // that an object is of type Operator[T] -} - -func (operator PredicateOperator[T]) ToSQL(_ *Query, columnName string) (string, []any, error) { - return fmt.Sprintf("%s %s", columnName, operator.SQLOperator), []any{}, nil -} - -func NewPredicateOperator[T any](sqlOperator string) PredicateOperator[T] { - return PredicateOperator[T]{ - SQLOperator: sqlOperator, - } -} diff --git a/orm/operators.go b/orm/operators.go index 24333420..426e3998 100644 --- a/orm/operators.go +++ b/orm/operators.go @@ -1,6 +1,7 @@ package orm import ( + "github.com/ditrit/badaas/orm/operator" "github.com/ditrit/badaas/orm/sql" ) @@ -9,119 +10,119 @@ import ( // EqualTo // IsNotDistinct must be used in cases where value can be NULL -func Eq[T any](value T) Operator[T] { - return NewValueOperator[T](sql.Eq, value) +func Eq[T any](value T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.Eq, value) } // NotEqualTo // IsDistinct must be used in cases where value can be NULL -func NotEq[T any](value T) Operator[T] { - return NewValueOperator[T](sql.NotEq, value) +func NotEq[T any](value T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.NotEq, value) } // LessThan -func Lt[T any](value T) Operator[T] { - return NewValueOperator[T](sql.Lt, value) +func Lt[T any](value T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.Lt, value) } // LessThanOrEqualTo -func LtOrEq[T any](value T) Operator[T] { - return NewValueOperator[T](sql.LtOrEq, value) +func LtOrEq[T any](value T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.LtOrEq, value) } // GreaterThan -func Gt[T any](value T) Operator[T] { - return NewValueOperator[T](sql.Gt, value) +func Gt[T any](value T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.Gt, value) } // GreaterThanOrEqualTo -func GtOrEq[T any](value T) Operator[T] { - return NewValueOperator[T](sql.GtOrEq, value) +func GtOrEq[T any](value T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.GtOrEq, value) } // Comparison Predicates // refs: https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE // Equivalent to v1 < value < v2 -func Between[T any](v1 T, v2 T) Operator[T] { +func Between[T any](v1 T, v2 T) operator.Operator[T] { return newBetweenOperator(sql.Between, v1, v2) } // Equivalent to NOT (v1 < value < v2) -func NotBetween[T any](v1 T, v2 T) Operator[T] { +func NotBetween[T any](v1 T, v2 T) operator.Operator[T] { return newBetweenOperator(sql.NotBetween, v1, v2) } -func newBetweenOperator[T any](sqlOperator sql.Operator, v1 T, v2 T) Operator[T] { - operator := NewValueOperator[T](sqlOperator, v1) +func newBetweenOperator[T any](sqlOperator sql.Operator, v1 T, v2 T) operator.Operator[T] { + operator := operator.NewValueOperator[T](sqlOperator, v1) return operator.AddOperation(sql.And, v2) } -func IsNull[T any]() PredicateOperator[T] { - return NewPredicateOperator[T]("IS NULL") +func IsNull[T any]() operator.Operator[T] { + return operator.NewPredicateOperator[T]("IS NULL") } -func IsNotNull[T any]() PredicateOperator[T] { - return NewPredicateOperator[T]("IS NOT NULL") +func IsNotNull[T any]() operator.Operator[T] { + return operator.NewPredicateOperator[T]("IS NOT NULL") } // Boolean Comparison Predicates -func IsTrue() PredicateOperator[bool] { - return NewPredicateOperator[bool]("IS TRUE") +func IsTrue() operator.Operator[bool] { + return operator.NewPredicateOperator[bool]("IS TRUE") } -func IsNotTrue() PredicateOperator[bool] { - return NewPredicateOperator[bool]("IS NOT TRUE") +func IsNotTrue() operator.Operator[bool] { + return operator.NewPredicateOperator[bool]("IS NOT TRUE") } -func IsFalse() PredicateOperator[bool] { - return NewPredicateOperator[bool]("IS FALSE") +func IsFalse() operator.Operator[bool] { + return operator.NewPredicateOperator[bool]("IS FALSE") } -func IsNotFalse() PredicateOperator[bool] { - return NewPredicateOperator[bool]("IS NOT FALSE") +func IsNotFalse() operator.Operator[bool] { + return operator.NewPredicateOperator[bool]("IS NOT FALSE") } -func IsUnknown() PredicateOperator[bool] { - return NewPredicateOperator[bool]("IS UNKNOWN") +func IsUnknown() operator.Operator[bool] { + return operator.NewPredicateOperator[bool]("IS UNKNOWN") } -func IsNotUnknown() PredicateOperator[bool] { - return NewPredicateOperator[bool]("IS NOT UNKNOWN") +func IsNotUnknown() operator.Operator[bool] { + return operator.NewPredicateOperator[bool]("IS NOT UNKNOWN") } -func IsDistinct[T any](value T) Operator[T] { - return NewValueOperator[T](sql.IsDistinct, value) +func IsDistinct[T any](value T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.IsDistinct, value) } -func IsNotDistinct[T any](value T) Operator[T] { - return NewValueOperator[T](sql.IsNotDistinct, value) +func IsNotDistinct[T any](value T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.IsNotDistinct, value) } // Row and Array Comparisons -func ArrayIn[T any](values ...T) Operator[T] { - return NewValueOperator[T](sql.ArrayIn, values) +func ArrayIn[T any](values ...T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.ArrayIn, values) } -func ArrayNotIn[T any](values ...T) Operator[T] { - return NewValueOperator[T](sql.ArrayNotIn, values) +func ArrayNotIn[T any](values ...T) operator.Operator[T] { + return operator.NewValueOperator[T](sql.ArrayNotIn, values) } // Pattern Matching type LikeOperator struct { - ValueOperator[string] + operator.ValueOperator[string] } func NewLikeOperator(sqlOperator sql.Operator, pattern string) LikeOperator { return LikeOperator{ - ValueOperator: *NewValueOperator[string](sqlOperator, pattern), + ValueOperator: *operator.NewValueOperator[string](sqlOperator, pattern), } } -func (operator LikeOperator) Escape(escape rune) ValueOperator[string] { +func (operator LikeOperator) Escape(escape rune) operator.ValueOperator[string] { return *operator.AddOperation(sql.Escape, string(escape)) } diff --git a/orm/orm.go b/orm/orm.go index 638330ef..a9e7b9bd 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -3,9 +3,11 @@ package orm import ( "github.com/elliotchance/pie/v2" "gorm.io/gorm" + + "github.com/ditrit/badaas/orm/model" ) -func GetCRUD[T Model, ID ModelID](db *gorm.DB) (CRUDService[T, ID], CRUDRepository[T, ID]) { +func GetCRUD[T model.Model, ID model.ID](db *gorm.DB) (CRUDService[T, ID], CRUDRepository[T, ID]) { repository := NewCRUDRepository[T, ID]() return NewCRUDService(db, repository), repository } diff --git a/orm/preload.go b/orm/preload.go deleted file mode 100644 index 7e0ad5aa..00000000 --- a/orm/preload.go +++ /dev/null @@ -1,41 +0,0 @@ -package orm - -import "errors" - -var ErrRelationNotLoaded = errors.New("relation not loaded") - -func VerifyStructLoaded[T Model](toVerify *T) (*T, error) { - if toVerify == nil || !(*toVerify).IsLoaded() { - return nil, ErrRelationNotLoaded - } - - return toVerify, nil -} - -func VerifyPointerLoaded[TModel Model, TID ModelID](id *TID, toVerify *TModel) (*TModel, error) { - // when the pointer to the object is nil - // but the id pointer indicates that the relation is not nil - if id != nil && toVerify == nil { - return nil, ErrRelationNotLoaded - } - - return toVerify, nil -} - -func VerifyPointerWithIDLoaded[TModel Model, TID ModelID](id TID, toVerify *TModel) (*TModel, error) { - // when the pointer to the object is nil - // but the id indicates that the relation is not nil - if !id.IsNil() && toVerify == nil { - return nil, ErrRelationNotLoaded - } - - return toVerify, nil -} - -func VerifyCollectionLoaded[T Model](collection *[]T) ([]T, error) { - if collection == nil { - return nil, ErrRelationNotLoaded - } - - return *collection, nil -} diff --git a/orm/preload/preload.go b/orm/preload/preload.go new file mode 100644 index 00000000..4e181a21 --- /dev/null +++ b/orm/preload/preload.go @@ -0,0 +1,42 @@ +package preload + +import ( + "github.com/ditrit/badaas/orm/errors" + "github.com/ditrit/badaas/orm/model" +) + +func VerifyStructLoaded[T model.Model](toVerify *T) (*T, error) { + if toVerify == nil || !(*toVerify).IsLoaded() { + return nil, errors.ErrRelationNotLoaded + } + + return toVerify, nil +} + +func VerifyPointerLoaded[TModel model.Model, TID model.ID](id *TID, toVerify *TModel) (*TModel, error) { + // when the pointer to the object is nil + // but the id pointer indicates that the relation is not nil + if id != nil && toVerify == nil { + return nil, errors.ErrRelationNotLoaded + } + + return toVerify, nil +} + +func VerifyPointerWithIDLoaded[TModel model.Model, TID model.ID](id TID, toVerify *TModel) (*TModel, error) { + // when the pointer to the object is nil + // but the id indicates that the relation is not nil + if !id.IsNil() && toVerify == nil { + return nil, errors.ErrRelationNotLoaded + } + + return toVerify, nil +} + +func VerifyCollectionLoaded[T model.Model](collection *[]T) ([]T, error) { + if collection == nil { + return nil, errors.ErrRelationNotLoaded + } + + return *collection, nil +} diff --git a/orm/query.go b/orm/query.go deleted file mode 100644 index 3ed913ec..00000000 --- a/orm/query.go +++ /dev/null @@ -1,134 +0,0 @@ -package orm - -import ( - "fmt" - "reflect" - - "gorm.io/gorm" -) - -type Table struct { - Name string - Alias string - Initial bool -} - -// Returns true if the Table is the initial table in a query -func (t Table) IsInitial() bool { - return t.Initial -} - -// Returns the related Table corresponding to the model -func (t Table) DeliverTable(query *Query, model Model, relationName string) (Table, error) { - // get the name of the table for the model - tableName, err := getTableName(query.gormDB, model) - if err != nil { - return Table{}, err - } - - // add a suffix to avoid tables with the same name when joining - // the same table more than once - tableAlias := relationName - if !t.IsInitial() { - tableAlias = t.Alias + "__" + relationName - } - - return Table{ - Name: tableName, - Alias: tableAlias, - Initial: false, - }, nil -} - -type Query struct { - gormDB *gorm.DB - concernedModels map[reflect.Type][]Table -} - -func (query *Query) AddSelect(table Table, fieldID iFieldIdentifier) { - columnName := fieldID.ColumnName(query, table) - - query.gormDB.Statement.Selects = append( - query.gormDB.Statement.Selects, - fmt.Sprintf( - "%[1]s.%[2]s AS \"%[1]s__%[2]s\"", // name used by gorm to load the fields inside the models - table.Alias, - columnName, - ), - ) -} - -func (query *Query) Preload(preloadQuery string, args ...interface{}) { - query.gormDB = query.gormDB.Preload(preloadQuery, args...) -} - -func (query *Query) Unscoped() { - query.gormDB = query.gormDB.Unscoped() -} - -func (query *Query) Where(whereQuery interface{}, args ...interface{}) { - query.gormDB = query.gormDB.Where(whereQuery, args...) -} - -func (query *Query) Joins(joinQuery string, args ...interface{}) { - query.gormDB = query.gormDB.Joins(joinQuery, args...) -} - -func (query *Query) Find(dest interface{}, conds ...interface{}) error { - query.gormDB = query.gormDB.Find(dest, conds...) - - return query.gormDB.Error -} - -func (query *Query) AddConcernedModel(model Model, table Table) { - tableList, isPresent := query.concernedModels[reflect.TypeOf(model)] - if !isPresent { - query.concernedModels[reflect.TypeOf(model)] = []Table{table} - } else { - tableList = append(tableList, table) - query.concernedModels[reflect.TypeOf(model)] = tableList - } -} - -func (query *Query) GetTables(modelType reflect.Type) []Table { - tableList, isPresent := query.concernedModels[modelType] - if !isPresent { - return nil - } - - return tableList -} - -func (query Query) ColumnName(table Table, fieldName string) string { - return query.gormDB.NamingStrategy.ColumnName(table.Name, fieldName) -} - -func NewQuery[T Model](db *gorm.DB, conditions []Condition[T]) (*Query, error) { - model := *new(T) - - initialTableName, err := getTableName(db, model) - if err != nil { - return nil, err - } - - initialTable := Table{ - Name: initialTableName, - Alias: initialTableName, - Initial: true, - } - - query := &Query{ - gormDB: db.Select(initialTableName + ".*"), - concernedModels: map[reflect.Type][]Table{}, - } - query.AddConcernedModel(model, initialTable) - - for _, condition := range conditions { - err = condition.ApplyTo(query, initialTable) - if err != nil { - return nil, err - } - } - - return query, nil -} diff --git a/orm/query/field_identifier.go b/orm/query/field_identifier.go new file mode 100644 index 00000000..3e5363c8 --- /dev/null +++ b/orm/query/field_identifier.go @@ -0,0 +1,36 @@ +package query + +import "reflect" + +type IFieldIdentifier interface { + ColumnName(query *Query, table Table) string + ColumnSQL(query *Query, table Table) string + GetModelType() reflect.Type +} + +type FieldIdentifier[T any] struct { + Column string + Field string + ColumnPrefix string + ModelType reflect.Type +} + +func (fieldID FieldIdentifier[T]) GetModelType() reflect.Type { + return fieldID.ModelType +} + +// Returns the name of the column in which the field is saved in the table +func (fieldID FieldIdentifier[T]) ColumnName(query *Query, table Table) string { + columnName := fieldID.Column + if columnName == "" { + columnName = query.ColumnName(table, fieldID.Field) + } + + // add column prefix and table name once we know the column name + return fieldID.ColumnPrefix + columnName +} + +// Returns the SQL to get the value of the field in the table +func (fieldID FieldIdentifier[T]) ColumnSQL(query *Query, table Table) string { + return table.Alias + "." + fieldID.ColumnName(query, table) +} diff --git a/orm/query/query.go b/orm/query/query.go new file mode 100644 index 00000000..d67bfe26 --- /dev/null +++ b/orm/query/query.go @@ -0,0 +1,98 @@ +package query + +import ( + "fmt" + "reflect" + "sync" + + "gorm.io/gorm" + "gorm.io/gorm/schema" + + "github.com/ditrit/badaas/orm/model" +) + +type Query struct { + GormDB *gorm.DB + concernedModels map[reflect.Type][]Table +} + +func (query *Query) AddSelect(table Table, fieldID IFieldIdentifier) { + columnName := fieldID.ColumnName(query, table) + + query.GormDB.Statement.Selects = append( + query.GormDB.Statement.Selects, + fmt.Sprintf( + "%[1]s.%[2]s AS \"%[1]s__%[2]s\"", // name used by gorm to load the fields inside the models + table.Alias, + columnName, + ), + ) +} + +func (query *Query) Preload(preloadQuery string, args ...interface{}) { + query.GormDB = query.GormDB.Preload(preloadQuery, args...) +} + +func (query *Query) Unscoped() { + query.GormDB = query.GormDB.Unscoped() +} + +func (query *Query) Where(whereQuery interface{}, args ...interface{}) { + query.GormDB = query.GormDB.Where(whereQuery, args...) +} + +func (query *Query) Joins(joinQuery string, args ...interface{}) { + query.GormDB = query.GormDB.Joins(joinQuery, args...) +} + +func (query *Query) Find(dest interface{}, conds ...interface{}) error { + query.GormDB = query.GormDB.Find(dest, conds...) + + return query.GormDB.Error +} + +func (query *Query) AddConcernedModel(model model.Model, table Table) { + tableList, isPresent := query.concernedModels[reflect.TypeOf(model)] + if !isPresent { + query.concernedModels[reflect.TypeOf(model)] = []Table{table} + } else { + tableList = append(tableList, table) + query.concernedModels[reflect.TypeOf(model)] = tableList + } +} + +func (query *Query) GetTables(modelType reflect.Type) []Table { + tableList, isPresent := query.concernedModels[modelType] + if !isPresent { + return nil + } + + return tableList +} + +func (query Query) ColumnName(table Table, fieldName string) string { + return query.GormDB.NamingStrategy.ColumnName(table.Name, fieldName) +} + +func NewQuery(db *gorm.DB, initialModel model.Model, initialTable Table) *Query { + query := &Query{ + GormDB: db.Select(initialTable.Name + ".*"), + concernedModels: map[reflect.Type][]Table{}, + } + + query.AddConcernedModel(initialModel, initialTable) + + return query +} + +// Get the name of the table in "db" in which the data for "entity" is saved +// returns error is table name can not be found by gorm, +// probably because the type of "entity" is not registered using AddModel +func getTableName(db *gorm.DB, entity any) (string, error) { + schemaName, err := schema.Parse(entity, &sync.Map{}, db.NamingStrategy) + if err != nil { + return "", err + } + + return schemaName.Table, nil +} diff --git a/orm/query/table.go b/orm/query/table.go new file mode 100644 index 00000000..d674365f --- /dev/null +++ b/orm/query/table.go @@ -0,0 +1,53 @@ +package query + +import ( + "gorm.io/gorm" + + "github.com/ditrit/badaas/orm/model" +) + +type Table struct { + Name string + Alias string + Initial bool +} + +// Returns true if the Table is the initial table in a query +func (t Table) IsInitial() bool { + return t.Initial +} + +// Returns the related Table corresponding to the model +func (t Table) DeliverTable(query *Query, model model.Model, relationName string) (Table, error) { + // get the name of the table for the model + tableName, err := getTableName(query.GormDB, model) + if err != nil { + return Table{}, err + } + + // add a suffix to avoid tables with the same name when joining + // the same table more than once + tableAlias := relationName + if !t.IsInitial() { + tableAlias = t.Alias + "__" + relationName + } + + return Table{ + Name: tableName, + Alias: tableAlias, + Initial: false, + }, nil +} + +func NewTable(db *gorm.DB, model model.Model) (Table, error) { + initialTableName, err := getTableName(db, model) + if err != nil { + return Table{}, err + } + + return Table{ + Name: initialTableName, + Alias: initialTableName, + Initial: true, + }, nil +} diff --git a/orm/unsafe/condition.go b/orm/unsafe/condition.go index 5ba19e58..eed24fe3 100644 --- a/orm/unsafe/condition.go +++ b/orm/unsafe/condition.go @@ -3,41 +3,43 @@ package unsafe import ( "fmt" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/condition" + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/query" ) // Condition that can be used to express conditions that are not supported (yet?) by badaas-orm // Example: table1.columnX = table2.columnY -type Condition[T orm.Model] struct { +type unsafeCondition[T model.Model] struct { SQLCondition string Values []any } -func (condition Condition[T]) InterfaceVerificationMethod(_ T) { +func (unsafeCondition unsafeCondition[T]) InterfaceVerificationMethod(_ T) { // This method is necessary to get the compiler to verify // that an object is of type Condition[T] } -func (condition Condition[T]) ApplyTo(query *orm.Query, table orm.Table) error { - return orm.ApplyWhereCondition[T](condition, query, table) +func (unsafeCondition unsafeCondition[T]) ApplyTo(queryV *query.Query, table query.Table) error { + return condition.ApplyWhereCondition[T](unsafeCondition, queryV, table) } -func (condition Condition[T]) GetSQL(_ *orm.Query, table orm.Table) (string, []any, error) { +func (unsafeCondition unsafeCondition[T]) GetSQL(_ *query.Query, table query.Table) (string, []any, error) { return fmt.Sprintf( - condition.SQLCondition, + unsafeCondition.SQLCondition, table.Alias, - ), condition.Values, nil + ), unsafeCondition.Values, nil } -func (condition Condition[T]) AffectsDeletedAt() bool { +func (unsafeCondition unsafeCondition[T]) AffectsDeletedAt() bool { return false } // Condition that can be used to express conditions that are not supported (yet?) by badaas-orm // Example: table1.columnX = table2.columnY -func NewCondition[T orm.Model](condition string, values ...any) Condition[T] { - return Condition[T]{ - SQLCondition: condition, +func NewCondition[T model.Model](sqlCondition string, values ...any) condition.Condition[T] { + return unsafeCondition[T]{ + SQLCondition: sqlCondition, Values: values, } } diff --git a/orm/unsafe/operators.go b/orm/unsafe/operators.go index cda4fa2b..6f612bfd 100644 --- a/orm/unsafe/operators.go +++ b/orm/unsafe/operators.go @@ -1,7 +1,7 @@ package unsafe import ( - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/operator" "github.com/ditrit/badaas/orm/sql" ) @@ -9,69 +9,69 @@ import ( // ref: https://www.postgresql.org/docs/current/functions-comparison.html // EqualTo -func Eq[T any](value any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.Eq, value) +func Eq[T any](value any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.Eq, value) } // NotEqualTo -func NotEq[T any](value any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.NotEq, value) +func NotEq[T any](value any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.NotEq, value) } // LessThan -func Lt[T any](value any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.Lt, value) +func Lt[T any](value any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.Lt, value) } // LessThanOrEqualTo -func LtOrEq[T any](value any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.LtOrEq, value) +func LtOrEq[T any](value any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.LtOrEq, value) } // GreaterThan -func Gt[T any](value any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.Gt, value) +func Gt[T any](value any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.Gt, value) } // GreaterThanOrEqualTo -func GtOrEq[T any](value any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.GtOrEq, value) +func GtOrEq[T any](value any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.GtOrEq, value) } // Comparison Predicates // ref: https://www.postgresql.org/docs/current/functions-comparison.html#FUNCTIONS-COMPARISON-PRED-TABLE // Equivalent to v1 < value < v2 -func Between[T any](v1, v2 any) orm.DynamicOperator[T] { +func Between[T any](v1, v2 any) operator.DynamicOperator[T] { return newBetweenOperator[T](sql.Between, v1, v2) } // Equivalent to NOT (v1 < value < v2) -func NotBetween[T any](v1, v2 any) orm.DynamicOperator[T] { +func NotBetween[T any](v1, v2 any) operator.DynamicOperator[T] { return newBetweenOperator[T](sql.NotBetween, v1, v2) } -func newBetweenOperator[T any](sqlOperator sql.Operator, v1, v2 any) orm.DynamicOperator[T] { - operator := orm.NewValueOperator[T](sqlOperator, v1) +func newBetweenOperator[T any](sqlOperator sql.Operator, v1, v2 any) operator.DynamicOperator[T] { + operator := operator.NewValueOperator[T](sqlOperator, v1) return operator.AddOperation(sql.And, v2) } // Boolean Comparison Predicates -func IsDistinct[T any](value any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.IsDistinct, value) +func IsDistinct[T any](value any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.IsDistinct, value) } -func IsNotDistinct[T any](value any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.IsNotDistinct, value) +func IsNotDistinct[T any](value any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.IsNotDistinct, value) } // Row and Array Comparisons -func ArrayIn[T any](values ...any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.ArrayIn, values) +func ArrayIn[T any](values ...any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.ArrayIn, values) } -func ArrayNotIn[T any](values ...any) orm.DynamicOperator[T] { - return orm.NewValueOperator[T](sql.ArrayNotIn, values) +func ArrayNotIn[T any](values ...any) operator.DynamicOperator[T] { + return operator.NewValueOperator[T](sql.ArrayNotIn, values) } diff --git a/persistence/models/Session.go b/persistence/models/Session.go index 0b92fee9..2ea2acb3 100644 --- a/persistence/models/Session.go +++ b/persistence/models/Session.go @@ -3,18 +3,18 @@ package models import ( "time" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" ) // Represent a user session type Session struct { - orm.UUIDModel - UserID orm.UUID `gorm:"not null"` - ExpiresAt time.Time `gorm:"not null"` + model.UUIDModel + UserID model.UUID `gorm:"not null"` + ExpiresAt time.Time `gorm:"not null"` } // Create a new session -func NewSession(userID orm.UUID, sessionDuration time.Duration) *Session { +func NewSession(userID model.UUID, sessionDuration time.Duration) *Session { return &Session{ UserID: userID, ExpiresAt: time.Now().Add(sessionDuration), diff --git a/persistence/models/Session_test.go b/persistence/models/Session_test.go index 7d0746e9..fb92d2a2 100644 --- a/persistence/models/Session_test.go +++ b/persistence/models/Session_test.go @@ -6,14 +6,14 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/persistence/models" ) func TestNewSession(t *testing.T) { - sessionInstance := models.NewSession(orm.NilUUID, time.Second) + sessionInstance := models.NewSession(model.NilUUID, time.Second) assert.NotNil(t, sessionInstance) - assert.Equal(t, orm.NilUUID, sessionInstance.UserID) + assert.Equal(t, model.NilUUID, sessionInstance.UserID) } func TestExpired(t *testing.T) { diff --git a/persistence/models/User.go b/persistence/models/User.go index 4eb76d51..6b1960a1 100644 --- a/persistence/models/User.go +++ b/persistence/models/User.go @@ -1,10 +1,15 @@ package models -import "github.com/ditrit/badaas/orm" +import ( + "github.com/ditrit/badaas/orm/condition" + "github.com/ditrit/badaas/orm/model" + "github.com/ditrit/badaas/orm/operator" + "github.com/ditrit/badaas/orm/query" +) // Represents a user type User struct { - orm.UUIDModel + model.UUIDModel Username string `gorm:"not null"` Email string `gorm:"unique;not null"` @@ -12,11 +17,11 @@ type User struct { Password []byte `gorm:"not null"` } -func UserEmailCondition(operator orm.Operator[string]) orm.WhereCondition[User] { - return orm.FieldCondition[User, string]{ - FieldIdentifier: orm.FieldIdentifier[string]{ +func UserEmailCondition(operator operator.Operator[string]) condition.WhereCondition[User] { + return condition.NewFieldCondition[User, string]( + query.FieldIdentifier[string]{ Field: "Email", }, - Operator: operator, - } + operator, + ) } diff --git a/router/middlewares/middlewareAuthentication.go b/router/middlewares/middlewareAuthentication.go index 66f5a956..c6d8c763 100644 --- a/router/middlewares/middlewareAuthentication.go +++ b/router/middlewares/middlewareAuthentication.go @@ -6,7 +6,7 @@ import ( "go.uber.org/zap" "github.com/ditrit/badaas/httperrors" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/services/sessionservice" ) @@ -43,7 +43,7 @@ func (authenticationMiddleware *authenticationMiddleware) Handle(next http.Handl return } - extractedUUID, err := orm.ParseUUID(accessTokenCookie.Value) + extractedUUID, err := model.ParseUUID(accessTokenCookie.Value) if err != nil { NotAuthenticated.Write(response, authenticationMiddleware.logger) return diff --git a/services/ModuleFx.go b/services/ModuleFx.go index 0259a5b8..af368823 100644 --- a/services/ModuleFx.go +++ b/services/ModuleFx.go @@ -4,6 +4,7 @@ import ( "go.uber.org/fx" "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/services/sessionservice" "github.com/ditrit/badaas/services/userservice" @@ -14,8 +15,8 @@ var AuthServiceModule = fx.Module( // models fx.Provide(getAuthModels), // repositories - fx.Provide(orm.NewCRUDRepository[models.Session, orm.UUID]), - fx.Provide(orm.NewCRUDRepository[models.User, orm.UUID]), + fx.Provide(orm.NewCRUDRepository[models.Session, model.UUID]), + fx.Provide(orm.NewCRUDRepository[models.User, model.UUID]), // services fx.Provide(userservice.NewUserService), diff --git a/services/sessionservice/session.go b/services/sessionservice/session.go index c3a61dd4..2b0c3a80 100644 --- a/services/sessionservice/session.go +++ b/services/sessionservice/session.go @@ -11,6 +11,7 @@ import ( "github.com/ditrit/badaas/configuration" "github.com/ditrit/badaas/httperrors" "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/persistence/models" ) @@ -24,9 +25,9 @@ var ( // SessionService handle sessions type SessionService interface { - IsValid(sessionUUID orm.UUID) (bool, *SessionClaims) + IsValid(sessionUUID model.UUID) (bool, *SessionClaims) // TODO services should not work with httperrors - RollSession(orm.UUID) httperrors.HTTPError + RollSession(model.UUID) httperrors.HTTPError LogUserIn(user *models.User) (*models.Session, error) LogUserOut(sessionClaims *SessionClaims) httperrors.HTTPError } @@ -36,8 +37,8 @@ var _ SessionService = (*sessionServiceImpl)(nil) // The SessionService concrete interface type sessionServiceImpl struct { - sessionRepository orm.CRUDRepository[models.Session, orm.UUID] - cache map[orm.UUID]*models.Session + sessionRepository orm.CRUDRepository[models.Session, model.UUID] + cache map[model.UUID]*models.Session mutex sync.Mutex logger *zap.Logger sessionConfiguration configuration.SessionConfiguration @@ -47,12 +48,12 @@ type sessionServiceImpl struct { // The SessionService constructor func NewSessionService( logger *zap.Logger, - sessionRepository orm.CRUDRepository[models.Session, orm.UUID], + sessionRepository orm.CRUDRepository[models.Session, model.UUID], sessionConfiguration configuration.SessionConfiguration, db *gorm.DB, ) SessionService { sessionService := &sessionServiceImpl{ - cache: make(map[orm.UUID]*models.Session), + cache: make(map[model.UUID]*models.Session), logger: logger, sessionRepository: sessionRepository, sessionConfiguration: sessionConfiguration, @@ -65,7 +66,7 @@ func NewSessionService( // Return true if the session exists and is still valid. // A instance of SessionClaims is returned to be added to the request context if the conditions previously mentioned are met. -func (sessionService *sessionServiceImpl) IsValid(sessionUUID orm.UUID) (bool, *SessionClaims) { +func (sessionService *sessionServiceImpl) IsValid(sessionUUID model.UUID) (bool, *SessionClaims) { sessionInstance := sessionService.get(sessionUUID) if sessionInstance == nil { return false, nil @@ -76,7 +77,7 @@ func (sessionService *sessionServiceImpl) IsValid(sessionUUID orm.UUID) (bool, * // Get a session from cache // return nil if not found -func (sessionService *sessionServiceImpl) get(sessionUUID orm.UUID) *models.Session { +func (sessionService *sessionServiceImpl) get(sessionUUID model.UUID) *models.Session { sessionService.mutex.Lock() defer sessionService.mutex.Unlock() @@ -114,7 +115,7 @@ func (sessionService *sessionServiceImpl) add(session *models.Session) error { // Initialize the session service func (sessionService *sessionServiceImpl) init() { - sessionService.cache = make(map[orm.UUID]*models.Session) + sessionService.cache = make(map[model.UUID]*models.Session) go func() { for { @@ -137,7 +138,7 @@ func (sessionService *sessionServiceImpl) pullFromDB() { panic(err) } - newSessionCache := make(map[orm.UUID]*models.Session) + newSessionCache := make(map[model.UUID]*models.Session) for _, sessionFromDatabase := range sessionsFromDatabase { newSessionCache[sessionFromDatabase.ID] = sessionFromDatabase } @@ -199,7 +200,7 @@ func (sessionService *sessionServiceImpl) delete(session *models.Session) httper } // Roll a session. If the session is close to expiration, extend its duration. -func (sessionService *sessionServiceImpl) RollSession(sessionUUID orm.UUID) httperrors.HTTPError { +func (sessionService *sessionServiceImpl) RollSession(sessionUUID model.UUID) httperrors.HTTPError { rollInterval := sessionService.sessionConfiguration.GetRollDuration() sessionDuration := sessionService.sessionConfiguration.GetSessionDuration() diff --git a/services/sessionservice/session_test.go b/services/sessionservice/session_test.go index fd25a6a9..e847fad0 100644 --- a/services/sessionservice/session_test.go +++ b/services/sessionservice/session_test.go @@ -16,7 +16,7 @@ import ( "github.com/ditrit/badaas/httperrors" configurationMocks "github.com/ditrit/badaas/mocks/configuration" ormMocks "github.com/ditrit/badaas/mocks/orm" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/persistence/models" ) @@ -26,19 +26,19 @@ var gormDB *gorm.DB func setupTest( t *testing.T, ) ( - *ormMocks.CRUDRepository[models.Session, orm.UUID], + *ormMocks.CRUDRepository[models.Session, model.UUID], *sessionServiceImpl, *observer.ObservedLogs, *configurationMocks.SessionConfiguration, ) { core, logs := observer.New(zap.DebugLevel) logger := zap.New(core) - sessionRepositoryMock := ormMocks.NewCRUDRepository[models.Session, orm.UUID](t) + sessionRepositoryMock := ormMocks.NewCRUDRepository[models.Session, model.UUID](t) sessionConfiguration := configurationMocks.NewSessionConfiguration(t) service := &sessionServiceImpl{ sessionRepository: sessionRepositoryMock, logger: logger, - cache: make(map[orm.UUID]*models.Session), + cache: make(map[model.UUID]*models.Session), sessionConfiguration: sessionConfiguration, db: gormDB, } @@ -87,22 +87,22 @@ func TestIsValid(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() session := &models.Session{ - UUIDModel: orm.UUIDModel{ + UUIDModel: model.UUIDModel{ ID: uuidSample, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } err := service.add(session) require.NoError(t, err) assert.Len(t, service.cache, 1) - assert.Equal(t, orm.NilUUID, service.cache[uuidSample].UserID) + assert.Equal(t, model.NilUUID, service.cache[uuidSample].UserID) isValid, claims := service.IsValid(uuidSample) require.True(t, isValid) assert.Equal(t, *claims, SessionClaims{ - UserID: orm.NilUUID, + UserID: model.NilUUID, SessionUUID: uuidSample, }) } @@ -113,7 +113,7 @@ func TestIsValid_SessionNotFound(t *testing.T) { On("GetByID", gormDB, mock.Anything). Return(nil, errors.New("not-found")) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() isValid, _ := service.IsValid(uuidSample) require.False(t, isValid) } @@ -122,12 +122,12 @@ func TestLogOutUser(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) sessionRepositoryMock.On("Delete", gormDB, mock.Anything).Return(nil) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() session := &models.Session{ - UUIDModel: orm.UUIDModel{ + UUIDModel: model.UUIDModel{ ID: uuidSample, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session @@ -142,13 +142,13 @@ func TestLogOutUserDbError(t *testing.T) { On("Delete", gormDB, mock.Anything). Return(errors.New("db errors")) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() session := &models.Session{ - UUIDModel: orm.UUIDModel{ + UUIDModel: model.UUIDModel{ ID: uuidSample, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session @@ -163,17 +163,17 @@ func TestLogOutUser_SessionNotFound(t *testing.T) { On("GetByID", gormDB, mock.Anything). Return(nil, errors.New("not-found")) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() session := &models.Session{ - UUIDModel: orm.UUIDModel{ - ID: orm.NilUUID, + UUIDModel: model.UUIDModel{ + ID: model.NilUUID, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } service.cache[uuidSample] = session sessionClaims := makeSessionClaims(session) - sessionClaims.SessionUUID = orm.NilUUID + sessionClaims.SessionUUID = model.NilUUID err := service.LogUserOut(sessionClaims) require.Error(t, err) assert.Len(t, service.cache, 1) @@ -187,13 +187,13 @@ func TestRollSession(t *testing.T) { sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() originalExpirationTime := time.Now().Add(sessionDuration / 5) session := &models.Session{ - UUIDModel: orm.UUIDModel{ - ID: orm.NilUUID, + UUIDModel: model.UUIDModel{ + ID: model.NilUUID, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session @@ -208,13 +208,13 @@ func TestRollSession_Expired(t *testing.T) { sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() originalExpirationTime := time.Now().Add(-time.Hour) session := &models.Session{ - UUIDModel: orm.UUIDModel{ + UUIDModel: model.UUIDModel{ ID: uuidSample, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session @@ -228,13 +228,13 @@ func TestRollSession_falseUUID(t *testing.T) { sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration / 4) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() originalExpirationTime := time.Now().Add(-time.Hour) session := &models.Session{ - UUIDModel: orm.UUIDModel{ - ID: orm.NilUUID, + UUIDModel: model.UUIDModel{ + ID: model.NilUUID, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: originalExpirationTime, } service.cache[uuidSample] = session @@ -243,31 +243,31 @@ func TestRollSession_falseUUID(t *testing.T) { On("GetByID", gormDB, mock.Anything). Return(nil, errors.New("not-found")) - err := service.RollSession(orm.NewUUID()) + err := service.RollSession(model.NewUUID()) require.NoError(t, err) } func TestRollSession_sessionNotFound(t *testing.T) { sessionRepositoryMock, service, _, sessionConfigurationMock := setupTest(t) sessionRepositoryMock. - On("GetByID", gormDB, orm.NilUUID). + On("GetByID", gormDB, model.NilUUID). Return(nil, errors.New("not-found")) sessionDuration := time.Minute sessionConfigurationMock.On("GetSessionDuration").Return(sessionDuration) sessionConfigurationMock.On("GetRollDuration").Return(sessionDuration) - err := service.RollSession(orm.NilUUID) + err := service.RollSession(model.NilUUID) require.NoError(t, err) } func Test_pullFromDB(t *testing.T) { sessionRepositoryMock, service, logs, _ := setupTest(t) session := &models.Session{ - UUIDModel: orm.UUIDModel{ - ID: orm.NilUUID, + UUIDModel: model.UUIDModel{ + ID: model.NilUUID, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: time.Now().Add(time.Hour), } sessionRepositoryMock.On("Query", gormDB).Return([]*models.Session{session}, nil) @@ -290,12 +290,12 @@ func Test_pullFromDB_repoError(t *testing.T) { func Test_removeExpired(t *testing.T) { sessionRepositoryMock, service, logs, _ := setupTest(t) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() session := &models.Session{ - UUIDModel: orm.UUIDModel{ - ID: orm.NilUUID, + UUIDModel: model.UUIDModel{ + ID: model.NilUUID, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. @@ -316,12 +316,12 @@ func Test_removeExpired(t *testing.T) { func Test_removeExpired_RepositoryError(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() session := &models.Session{ - UUIDModel: orm.UUIDModel{ - ID: orm.NilUUID, + UUIDModel: model.UUIDModel{ + ID: model.NilUUID, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. @@ -335,12 +335,12 @@ func Test_removeExpired_RepositoryError(t *testing.T) { func Test_get(t *testing.T) { sessionRepositoryMock, service, _, _ := setupTest(t) - uuidSample := orm.NewUUID() + uuidSample := model.NewUUID() session := &models.Session{ - UUIDModel: orm.UUIDModel{ - ID: orm.NilUUID, + UUIDModel: model.UUIDModel{ + ID: model.NilUUID, }, - UserID: orm.NilUUID, + UserID: model.NilUUID, ExpiresAt: time.Now().Add(-time.Hour), } sessionRepositoryMock. diff --git a/services/sessionservice/sessionctx.go b/services/sessionservice/sessionctx.go index 80bd6727..b1a08eef 100644 --- a/services/sessionservice/sessionctx.go +++ b/services/sessionservice/sessionctx.go @@ -3,14 +3,14 @@ package sessionservice import ( "context" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/persistence/models" ) // The session claims passed in the request context type SessionClaims struct { - UserID orm.UUID - SessionUUID orm.UUID + UserID model.UUID + SessionUUID model.UUID } // Unique claim key type diff --git a/services/sessionservice/sessionctx_test.go b/services/sessionservice/sessionctx_test.go index 83be8b35..64b9518f 100644 --- a/services/sessionservice/sessionctx_test.go +++ b/services/sessionservice/sessionctx_test.go @@ -7,15 +7,15 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" ) func TestSessionCtx(t *testing.T) { ctx := context.Background() - sessionClaims := &SessionClaims{orm.NilUUID, orm.UUID(uuid.New())} + sessionClaims := &SessionClaims{model.NilUUID, model.UUID(uuid.New())} ctx = SetSessionClaimsContext(ctx, sessionClaims) claims := GetSessionClaimsFromContext(ctx) - assert.Equal(t, orm.NilUUID, claims.UserID) + assert.Equal(t, model.NilUUID, claims.UserID) } func TestSessionCtxPanic(t *testing.T) { diff --git a/services/userservice/userservice.go b/services/userservice/userservice.go index d7817bdd..0d67bc68 100644 --- a/services/userservice/userservice.go +++ b/services/userservice/userservice.go @@ -8,6 +8,7 @@ import ( "gorm.io/gorm" "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" "github.com/ditrit/badaas/services/auth/protocols/basicauth" @@ -27,7 +28,7 @@ var _ UserService = (*userServiceImpl)(nil) // The UserService concrete implementation type userServiceImpl struct { - userRepository orm.CRUDRepository[models.User, orm.UUID] + userRepository orm.CRUDRepository[models.User, model.UUID] logger *zap.Logger db *gorm.DB } @@ -35,7 +36,7 @@ type userServiceImpl struct { // UserService constructor func NewUserService( logger *zap.Logger, - userRepository orm.CRUDRepository[models.User, orm.UUID], + userRepository orm.CRUDRepository[models.User, model.UUID], db *gorm.DB, ) UserService { return &userServiceImpl{ diff --git a/services/userservice/userservice_test.go b/services/userservice/userservice_test.go index 2e004e93..7f43802e 100644 --- a/services/userservice/userservice_test.go +++ b/services/userservice/userservice_test.go @@ -14,6 +14,8 @@ import ( ormMocks "github.com/ditrit/badaas/mocks/orm" "github.com/ditrit/badaas/orm" + badaasORMErrors "github.com/ditrit/badaas/orm/errors" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" "github.com/ditrit/badaas/services/userservice" @@ -26,7 +28,7 @@ func TestNewUserService(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, model.UUID](t) userRepositoryMock.On("Create", gormDB, mock.Anything).Return(nil) userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) @@ -53,7 +55,7 @@ func TestNewUserServiceDatabaseError(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, model.UUID](t) userRepositoryMock.On( "Create", gormDB, mock.Anything, ).Return( @@ -74,7 +76,7 @@ func TestNewUserServiceEmailNotValid(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, model.UUID](t) userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) user, err := userService.NewUser("bob", "bob@", "1234") @@ -90,7 +92,7 @@ func TestGetUser(t *testing.T) { observedZapCore, observedLogs := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, model.UUID](t) userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) userRepositoryMock.On( "Create", gormDB, mock.Anything, @@ -125,13 +127,13 @@ func TestGetUserNoUserFound(t *testing.T) { observedZapCore, _ := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, model.UUID](t) userService := userservice.NewUserService(observedLogger, userRepositoryMock, gormDB) userRepositoryMock.On( "QueryOne", gormDB, models.UserEmailCondition(orm.Eq("bobnotfound@email.com")), ).Return( &models.User{}, - orm.ErrObjectNotFound, + badaasORMErrors.ErrObjectNotFound, ) userFound, err := userService.GetUser(dto.UserLoginDTO{Email: "bobnotfound@email.com", Password: "1234"}) @@ -145,7 +147,7 @@ func TestGetUserWrongPassword(t *testing.T) { observedZapCore, _ := observer.New(zap.DebugLevel) observedLogger := zap.New(observedZapCore) - userRepositoryMock := ormMocks.NewCRUDRepository[models.User, orm.UUID](t) + userRepositoryMock := ormMocks.NewCRUDRepository[models.User, model.UUID](t) userRepositoryMock.On( "Create", gormDB, mock.Anything, ).Return(nil) diff --git a/testintegration/conditions/bicycle_conditions.go b/testintegration/conditions/bicycle_conditions.go index 876f9250..9cf42a0c 100644 --- a/testintegration/conditions/bicycle_conditions.go +++ b/testintegration/conditions/bicycle_conditions.go @@ -2,94 +2,73 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var bicycleType = reflect.TypeOf(*new(models.Bicycle)) -var BicycleIdField = orm.FieldIdentifier[orm.UUID]{ +var BicycleIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: bicycleType, } -func BicycleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Bicycle] { - return orm.FieldCondition[models.Bicycle, orm.UUID]{ - FieldIdentifier: BicycleIdField, - Operator: operator, - } +func BicycleId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Bicycle] { + return condition.NewFieldCondition[models.Bicycle, model.UUID](BicycleIdField, operator) } -var BicycleCreatedAtField = orm.FieldIdentifier[time.Time]{ +var BicycleCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: bicycleType, } -func BicycleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { - return orm.FieldCondition[models.Bicycle, time.Time]{ - FieldIdentifier: BicycleCreatedAtField, - Operator: operator, - } +func BicycleCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Bicycle] { + return condition.NewFieldCondition[models.Bicycle, time.Time](BicycleCreatedAtField, operator) } -var BicycleUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var BicycleUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: bicycleType, } -func BicycleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { - return orm.FieldCondition[models.Bicycle, time.Time]{ - FieldIdentifier: BicycleUpdatedAtField, - Operator: operator, - } +func BicycleUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Bicycle] { + return condition.NewFieldCondition[models.Bicycle, time.Time](BicycleUpdatedAtField, operator) } -var BicycleDeletedAtField = orm.FieldIdentifier[time.Time]{ +var BicycleDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: bicycleType, } -func BicycleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Bicycle] { - return orm.FieldCondition[models.Bicycle, time.Time]{ - FieldIdentifier: BicycleDeletedAtField, - Operator: operator, - } +func BicycleDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Bicycle] { + return condition.NewFieldCondition[models.Bicycle, time.Time](BicycleDeletedAtField, operator) } -var BicycleNameField = orm.FieldIdentifier[string]{ +var BicycleNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: bicycleType, } -func BicycleName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { - return orm.FieldCondition[models.Bicycle, string]{ - FieldIdentifier: BicycleNameField, - Operator: operator, - } +func BicycleName(operator operator.Operator[string]) condition.WhereCondition[models.Bicycle] { + return condition.NewFieldCondition[models.Bicycle, string](BicycleNameField, operator) } -func BicycleOwner(conditions ...orm.Condition[models.Person]) orm.IJoinCondition[models.Bicycle] { - return orm.JoinCondition[models.Bicycle, models.Person]{ - Conditions: conditions, - RelationField: "Owner", - T1Field: "OwnerName", - T1PreloadCondition: BicyclePreloadAttributes, - T2Field: "Name", - } +func BicycleOwner(conditions ...condition.Condition[models.Person]) condition.JoinCondition[models.Bicycle] { + return condition.NewJoinCondition[models.Bicycle, models.Person](conditions, "Owner", "OwnerName", BicyclePreloadAttributes, "Name") } var BicyclePreloadOwner = BicycleOwner(PersonPreloadAttributes) -var BicycleOwnerNameField = orm.FieldIdentifier[string]{ +var BicycleOwnerNameField = query.FieldIdentifier[string]{ Field: "OwnerName", ModelType: bicycleType, } -func BicycleOwnerName(operator orm.Operator[string]) orm.WhereCondition[models.Bicycle] { - return orm.FieldCondition[models.Bicycle, string]{ - FieldIdentifier: BicycleOwnerNameField, - Operator: operator, - } +func BicycleOwnerName(operator operator.Operator[string]) condition.WhereCondition[models.Bicycle] { + return condition.NewFieldCondition[models.Bicycle, string](BicycleOwnerNameField, operator) } -var BicyclePreloadAttributes = orm.NewPreloadCondition[models.Bicycle](BicycleIdField, BicycleCreatedAtField, BicycleUpdatedAtField, BicycleDeletedAtField, BicycleNameField, BicycleOwnerNameField) -var BicyclePreloadRelations = []orm.Condition[models.Bicycle]{BicyclePreloadOwner} +var BicyclePreloadAttributes = condition.NewPreloadCondition[models.Bicycle](BicycleIdField, BicycleCreatedAtField, BicycleUpdatedAtField, BicycleDeletedAtField, BicycleNameField, BicycleOwnerNameField) +var BicyclePreloadRelations = []condition.Condition[models.Bicycle]{BicyclePreloadOwner} diff --git a/testintegration/conditions/brand_conditions.go b/testintegration/conditions/brand_conditions.go index 3b9518b2..7fe41c47 100644 --- a/testintegration/conditions/brand_conditions.go +++ b/testintegration/conditions/brand_conditions.go @@ -2,71 +2,59 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var brandType = reflect.TypeOf(*new(models.Brand)) -var BrandIdField = orm.FieldIdentifier[orm.UIntID]{ +var BrandIdField = query.FieldIdentifier[model.UIntID]{ Field: "ID", ModelType: brandType, } -func BrandId(operator orm.Operator[orm.UIntID]) orm.WhereCondition[models.Brand] { - return orm.FieldCondition[models.Brand, orm.UIntID]{ - FieldIdentifier: BrandIdField, - Operator: operator, - } +func BrandId(operator operator.Operator[model.UIntID]) condition.WhereCondition[models.Brand] { + return condition.NewFieldCondition[models.Brand, model.UIntID](BrandIdField, operator) } -var BrandCreatedAtField = orm.FieldIdentifier[time.Time]{ +var BrandCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: brandType, } -func BrandCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { - return orm.FieldCondition[models.Brand, time.Time]{ - FieldIdentifier: BrandCreatedAtField, - Operator: operator, - } +func BrandCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Brand] { + return condition.NewFieldCondition[models.Brand, time.Time](BrandCreatedAtField, operator) } -var BrandUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var BrandUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: brandType, } -func BrandUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { - return orm.FieldCondition[models.Brand, time.Time]{ - FieldIdentifier: BrandUpdatedAtField, - Operator: operator, - } +func BrandUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Brand] { + return condition.NewFieldCondition[models.Brand, time.Time](BrandUpdatedAtField, operator) } -var BrandDeletedAtField = orm.FieldIdentifier[time.Time]{ +var BrandDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: brandType, } -func BrandDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Brand] { - return orm.FieldCondition[models.Brand, time.Time]{ - FieldIdentifier: BrandDeletedAtField, - Operator: operator, - } +func BrandDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Brand] { + return condition.NewFieldCondition[models.Brand, time.Time](BrandDeletedAtField, operator) } -var BrandNameField = orm.FieldIdentifier[string]{ +var BrandNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: brandType, } -func BrandName(operator orm.Operator[string]) orm.WhereCondition[models.Brand] { - return orm.FieldCondition[models.Brand, string]{ - FieldIdentifier: BrandNameField, - Operator: operator, - } +func BrandName(operator operator.Operator[string]) condition.WhereCondition[models.Brand] { + return condition.NewFieldCondition[models.Brand, string](BrandNameField, operator) } -var BrandPreloadAttributes = orm.NewPreloadCondition[models.Brand](BrandIdField, BrandCreatedAtField, BrandUpdatedAtField, BrandDeletedAtField, BrandNameField) +var BrandPreloadAttributes = condition.NewPreloadCondition[models.Brand](BrandIdField, BrandCreatedAtField, BrandUpdatedAtField, BrandDeletedAtField, BrandNameField) diff --git a/testintegration/conditions/child_conditions.go b/testintegration/conditions/child_conditions.go index 2dc0a23e..6cb05d54 100644 --- a/testintegration/conditions/child_conditions.go +++ b/testintegration/conditions/child_conditions.go @@ -2,128 +2,95 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var childType = reflect.TypeOf(*new(models.Child)) -var ChildIdField = orm.FieldIdentifier[orm.UUID]{ +var ChildIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: childType, } -func ChildId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Child] { - return orm.FieldCondition[models.Child, orm.UUID]{ - FieldIdentifier: ChildIdField, - Operator: operator, - } +func ChildId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Child] { + return condition.NewFieldCondition[models.Child, model.UUID](ChildIdField, operator) } -var ChildCreatedAtField = orm.FieldIdentifier[time.Time]{ +var ChildCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: childType, } -func ChildCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Child] { - return orm.FieldCondition[models.Child, time.Time]{ - FieldIdentifier: ChildCreatedAtField, - Operator: operator, - } +func ChildCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Child] { + return condition.NewFieldCondition[models.Child, time.Time](ChildCreatedAtField, operator) } -var ChildUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var ChildUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: childType, } -func ChildUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Child] { - return orm.FieldCondition[models.Child, time.Time]{ - FieldIdentifier: ChildUpdatedAtField, - Operator: operator, - } +func ChildUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Child] { + return condition.NewFieldCondition[models.Child, time.Time](ChildUpdatedAtField, operator) } -var ChildDeletedAtField = orm.FieldIdentifier[time.Time]{ +var ChildDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: childType, } -func ChildDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Child] { - return orm.FieldCondition[models.Child, time.Time]{ - FieldIdentifier: ChildDeletedAtField, - Operator: operator, - } +func ChildDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Child] { + return condition.NewFieldCondition[models.Child, time.Time](ChildDeletedAtField, operator) } -var ChildNameField = orm.FieldIdentifier[string]{ +var ChildNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: childType, } -func ChildName(operator orm.Operator[string]) orm.WhereCondition[models.Child] { - return orm.FieldCondition[models.Child, string]{ - FieldIdentifier: ChildNameField, - Operator: operator, - } +func ChildName(operator operator.Operator[string]) condition.WhereCondition[models.Child] { + return condition.NewFieldCondition[models.Child, string](ChildNameField, operator) } -var ChildNumberField = orm.FieldIdentifier[int]{ +var ChildNumberField = query.FieldIdentifier[int]{ Field: "Number", ModelType: childType, } -func ChildNumber(operator orm.Operator[int]) orm.WhereCondition[models.Child] { - return orm.FieldCondition[models.Child, int]{ - FieldIdentifier: ChildNumberField, - Operator: operator, - } +func ChildNumber(operator operator.Operator[int]) condition.WhereCondition[models.Child] { + return condition.NewFieldCondition[models.Child, int](ChildNumberField, operator) } -func ChildParent1(conditions ...orm.Condition[models.Parent1]) orm.IJoinCondition[models.Child] { - return orm.JoinCondition[models.Child, models.Parent1]{ - Conditions: conditions, - RelationField: "Parent1", - T1Field: "Parent1ID", - T1PreloadCondition: ChildPreloadAttributes, - T2Field: "ID", - } +func ChildParent1(conditions ...condition.Condition[models.Parent1]) condition.JoinCondition[models.Child] { + return condition.NewJoinCondition[models.Child, models.Parent1](conditions, "Parent1", "Parent1ID", ChildPreloadAttributes, "ID") } var ChildPreloadParent1 = ChildParent1(Parent1PreloadAttributes) -var ChildParent1IdField = orm.FieldIdentifier[orm.UUID]{ +var ChildParent1IdField = query.FieldIdentifier[model.UUID]{ Field: "Parent1ID", ModelType: childType, } -func ChildParent1Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Child] { - return orm.FieldCondition[models.Child, orm.UUID]{ - FieldIdentifier: ChildParent1IdField, - Operator: operator, - } +func ChildParent1Id(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Child] { + return condition.NewFieldCondition[models.Child, model.UUID](ChildParent1IdField, operator) } -func ChildParent2(conditions ...orm.Condition[models.Parent2]) orm.IJoinCondition[models.Child] { - return orm.JoinCondition[models.Child, models.Parent2]{ - Conditions: conditions, - RelationField: "Parent2", - T1Field: "Parent2ID", - T1PreloadCondition: ChildPreloadAttributes, - T2Field: "ID", - } +func ChildParent2(conditions ...condition.Condition[models.Parent2]) condition.JoinCondition[models.Child] { + return condition.NewJoinCondition[models.Child, models.Parent2](conditions, "Parent2", "Parent2ID", ChildPreloadAttributes, "ID") } var ChildPreloadParent2 = ChildParent2(Parent2PreloadAttributes) -var ChildParent2IdField = orm.FieldIdentifier[orm.UUID]{ +var ChildParent2IdField = query.FieldIdentifier[model.UUID]{ Field: "Parent2ID", ModelType: childType, } -func ChildParent2Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Child] { - return orm.FieldCondition[models.Child, orm.UUID]{ - FieldIdentifier: ChildParent2IdField, - Operator: operator, - } +func ChildParent2Id(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Child] { + return condition.NewFieldCondition[models.Child, model.UUID](ChildParent2IdField, operator) } -var ChildPreloadAttributes = orm.NewPreloadCondition[models.Child](ChildIdField, ChildCreatedAtField, ChildUpdatedAtField, ChildDeletedAtField, ChildNameField, ChildNumberField, ChildParent1IdField, ChildParent2IdField) -var ChildPreloadRelations = []orm.Condition[models.Child]{ChildPreloadParent1, ChildPreloadParent2} +var ChildPreloadAttributes = condition.NewPreloadCondition[models.Child](ChildIdField, ChildCreatedAtField, ChildUpdatedAtField, ChildDeletedAtField, ChildNameField, ChildNumberField, ChildParent1IdField, ChildParent2IdField) +var ChildPreloadRelations = []condition.Condition[models.Child]{ChildPreloadParent1, ChildPreloadParent2} diff --git a/testintegration/conditions/city_conditions.go b/testintegration/conditions/city_conditions.go index caa99bd0..d40ccd1a 100644 --- a/testintegration/conditions/city_conditions.go +++ b/testintegration/conditions/city_conditions.go @@ -2,94 +2,73 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var cityType = reflect.TypeOf(*new(models.City)) -var CityIdField = orm.FieldIdentifier[orm.UUID]{ +var CityIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: cityType, } -func CityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { - return orm.FieldCondition[models.City, orm.UUID]{ - FieldIdentifier: CityIdField, - Operator: operator, - } +func CityId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.City] { + return condition.NewFieldCondition[models.City, model.UUID](CityIdField, operator) } -var CityCreatedAtField = orm.FieldIdentifier[time.Time]{ +var CityCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: cityType, } -func CityCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { - return orm.FieldCondition[models.City, time.Time]{ - FieldIdentifier: CityCreatedAtField, - Operator: operator, - } +func CityCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.City] { + return condition.NewFieldCondition[models.City, time.Time](CityCreatedAtField, operator) } -var CityUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var CityUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: cityType, } -func CityUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { - return orm.FieldCondition[models.City, time.Time]{ - FieldIdentifier: CityUpdatedAtField, - Operator: operator, - } +func CityUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.City] { + return condition.NewFieldCondition[models.City, time.Time](CityUpdatedAtField, operator) } -var CityDeletedAtField = orm.FieldIdentifier[time.Time]{ +var CityDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: cityType, } -func CityDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.City] { - return orm.FieldCondition[models.City, time.Time]{ - FieldIdentifier: CityDeletedAtField, - Operator: operator, - } +func CityDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.City] { + return condition.NewFieldCondition[models.City, time.Time](CityDeletedAtField, operator) } -var CityNameField = orm.FieldIdentifier[string]{ +var CityNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: cityType, } -func CityName(operator orm.Operator[string]) orm.WhereCondition[models.City] { - return orm.FieldCondition[models.City, string]{ - FieldIdentifier: CityNameField, - Operator: operator, - } +func CityName(operator operator.Operator[string]) condition.WhereCondition[models.City] { + return condition.NewFieldCondition[models.City, string](CityNameField, operator) } -func CityCountry(conditions ...orm.Condition[models.Country]) orm.IJoinCondition[models.City] { - return orm.JoinCondition[models.City, models.Country]{ - Conditions: conditions, - RelationField: "Country", - T1Field: "CountryID", - T1PreloadCondition: CityPreloadAttributes, - T2Field: "ID", - } +func CityCountry(conditions ...condition.Condition[models.Country]) condition.JoinCondition[models.City] { + return condition.NewJoinCondition[models.City, models.Country](conditions, "Country", "CountryID", CityPreloadAttributes, "ID") } var CityPreloadCountry = CityCountry(CountryPreloadAttributes) -var CityCountryIdField = orm.FieldIdentifier[orm.UUID]{ +var CityCountryIdField = query.FieldIdentifier[model.UUID]{ Field: "CountryID", ModelType: cityType, } -func CityCountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.City] { - return orm.FieldCondition[models.City, orm.UUID]{ - FieldIdentifier: CityCountryIdField, - Operator: operator, - } +func CityCountryId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.City] { + return condition.NewFieldCondition[models.City, model.UUID](CityCountryIdField, operator) } -var CityPreloadAttributes = orm.NewPreloadCondition[models.City](CityIdField, CityCreatedAtField, CityUpdatedAtField, CityDeletedAtField, CityNameField, CityCountryIdField) -var CityPreloadRelations = []orm.Condition[models.City]{CityPreloadCountry} +var CityPreloadAttributes = condition.NewPreloadCondition[models.City](CityIdField, CityCreatedAtField, CityUpdatedAtField, CityDeletedAtField, CityNameField, CityCountryIdField) +var CityPreloadRelations = []condition.Condition[models.City]{CityPreloadCountry} diff --git a/testintegration/conditions/company_conditions.go b/testintegration/conditions/company_conditions.go index b7a09705..eecf9cd1 100644 --- a/testintegration/conditions/company_conditions.go +++ b/testintegration/conditions/company_conditions.go @@ -2,75 +2,63 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var companyType = reflect.TypeOf(*new(models.Company)) -var CompanyIdField = orm.FieldIdentifier[orm.UUID]{ +var CompanyIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: companyType, } -func CompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Company] { - return orm.FieldCondition[models.Company, orm.UUID]{ - FieldIdentifier: CompanyIdField, - Operator: operator, - } +func CompanyId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Company] { + return condition.NewFieldCondition[models.Company, model.UUID](CompanyIdField, operator) } -var CompanyCreatedAtField = orm.FieldIdentifier[time.Time]{ +var CompanyCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: companyType, } -func CompanyCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { - return orm.FieldCondition[models.Company, time.Time]{ - FieldIdentifier: CompanyCreatedAtField, - Operator: operator, - } +func CompanyCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Company] { + return condition.NewFieldCondition[models.Company, time.Time](CompanyCreatedAtField, operator) } -var CompanyUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var CompanyUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: companyType, } -func CompanyUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { - return orm.FieldCondition[models.Company, time.Time]{ - FieldIdentifier: CompanyUpdatedAtField, - Operator: operator, - } +func CompanyUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Company] { + return condition.NewFieldCondition[models.Company, time.Time](CompanyUpdatedAtField, operator) } -var CompanyDeletedAtField = orm.FieldIdentifier[time.Time]{ +var CompanyDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: companyType, } -func CompanyDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Company] { - return orm.FieldCondition[models.Company, time.Time]{ - FieldIdentifier: CompanyDeletedAtField, - Operator: operator, - } +func CompanyDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Company] { + return condition.NewFieldCondition[models.Company, time.Time](CompanyDeletedAtField, operator) } -var CompanyNameField = orm.FieldIdentifier[string]{ +var CompanyNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: companyType, } -func CompanyName(operator orm.Operator[string]) orm.WhereCondition[models.Company] { - return orm.FieldCondition[models.Company, string]{ - FieldIdentifier: CompanyNameField, - Operator: operator, - } +func CompanyName(operator operator.Operator[string]) condition.WhereCondition[models.Company] { + return condition.NewFieldCondition[models.Company, string](CompanyNameField, operator) } -func CompanyPreloadSellers(nestedPreloads ...orm.IJoinCondition[models.Seller]) orm.Condition[models.Company] { - return orm.NewCollectionPreloadCondition[models.Company, models.Seller]("Sellers", nestedPreloads) +func CompanyPreloadSellers(nestedPreloads ...condition.JoinCondition[models.Seller]) condition.Condition[models.Company] { + return condition.NewCollectionPreloadCondition[models.Company, models.Seller]("Sellers", nestedPreloads) } -var CompanyPreloadAttributes = orm.NewPreloadCondition[models.Company](CompanyIdField, CompanyCreatedAtField, CompanyUpdatedAtField, CompanyDeletedAtField, CompanyNameField) -var CompanyPreloadRelations = []orm.Condition[models.Company]{CompanyPreloadSellers()} +var CompanyPreloadAttributes = condition.NewPreloadCondition[models.Company](CompanyIdField, CompanyCreatedAtField, CompanyUpdatedAtField, CompanyDeletedAtField, CompanyNameField) +var CompanyPreloadRelations = []condition.Condition[models.Company]{CompanyPreloadSellers()} diff --git a/testintegration/conditions/country_conditions.go b/testintegration/conditions/country_conditions.go index 49c46df5..1477734c 100644 --- a/testintegration/conditions/country_conditions.go +++ b/testintegration/conditions/country_conditions.go @@ -2,82 +2,64 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var countryType = reflect.TypeOf(*new(models.Country)) -var CountryIdField = orm.FieldIdentifier[orm.UUID]{ +var CountryIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: countryType, } -func CountryId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Country] { - return orm.FieldCondition[models.Country, orm.UUID]{ - FieldIdentifier: CountryIdField, - Operator: operator, - } +func CountryId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Country] { + return condition.NewFieldCondition[models.Country, model.UUID](CountryIdField, operator) } -var CountryCreatedAtField = orm.FieldIdentifier[time.Time]{ +var CountryCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: countryType, } -func CountryCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { - return orm.FieldCondition[models.Country, time.Time]{ - FieldIdentifier: CountryCreatedAtField, - Operator: operator, - } +func CountryCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Country] { + return condition.NewFieldCondition[models.Country, time.Time](CountryCreatedAtField, operator) } -var CountryUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var CountryUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: countryType, } -func CountryUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { - return orm.FieldCondition[models.Country, time.Time]{ - FieldIdentifier: CountryUpdatedAtField, - Operator: operator, - } +func CountryUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Country] { + return condition.NewFieldCondition[models.Country, time.Time](CountryUpdatedAtField, operator) } -var CountryDeletedAtField = orm.FieldIdentifier[time.Time]{ +var CountryDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: countryType, } -func CountryDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Country] { - return orm.FieldCondition[models.Country, time.Time]{ - FieldIdentifier: CountryDeletedAtField, - Operator: operator, - } +func CountryDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Country] { + return condition.NewFieldCondition[models.Country, time.Time](CountryDeletedAtField, operator) } -var CountryNameField = orm.FieldIdentifier[string]{ +var CountryNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: countryType, } -func CountryName(operator orm.Operator[string]) orm.WhereCondition[models.Country] { - return orm.FieldCondition[models.Country, string]{ - FieldIdentifier: CountryNameField, - Operator: operator, - } +func CountryName(operator operator.Operator[string]) condition.WhereCondition[models.Country] { + return condition.NewFieldCondition[models.Country, string](CountryNameField, operator) } -func CountryCapital(conditions ...orm.Condition[models.City]) orm.IJoinCondition[models.Country] { - return orm.JoinCondition[models.Country, models.City]{ - Conditions: conditions, - RelationField: "Capital", - T1Field: "ID", - T1PreloadCondition: CountryPreloadAttributes, - T2Field: "CountryID", - } +func CountryCapital(conditions ...condition.Condition[models.City]) condition.JoinCondition[models.Country] { + return condition.NewJoinCondition[models.Country, models.City](conditions, "Capital", "ID", CountryPreloadAttributes, "CountryID") } var CountryPreloadCapital = CountryCapital(CityPreloadAttributes) -var CountryPreloadAttributes = orm.NewPreloadCondition[models.Country](CountryIdField, CountryCreatedAtField, CountryUpdatedAtField, CountryDeletedAtField, CountryNameField) -var CountryPreloadRelations = []orm.Condition[models.Country]{CountryPreloadCapital} +var CountryPreloadAttributes = condition.NewPreloadCondition[models.Country](CountryIdField, CountryCreatedAtField, CountryUpdatedAtField, CountryDeletedAtField, CountryNameField) +var CountryPreloadRelations = []condition.Condition[models.Country]{CountryPreloadCapital} diff --git a/testintegration/conditions/employee_conditions.go b/testintegration/conditions/employee_conditions.go index 5e116eeb..4e666b61 100644 --- a/testintegration/conditions/employee_conditions.go +++ b/testintegration/conditions/employee_conditions.go @@ -2,94 +2,73 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var employeeType = reflect.TypeOf(*new(models.Employee)) -var EmployeeIdField = orm.FieldIdentifier[orm.UUID]{ +var EmployeeIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: employeeType, } -func EmployeeId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { - return orm.FieldCondition[models.Employee, orm.UUID]{ - FieldIdentifier: EmployeeIdField, - Operator: operator, - } +func EmployeeId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Employee] { + return condition.NewFieldCondition[models.Employee, model.UUID](EmployeeIdField, operator) } -var EmployeeCreatedAtField = orm.FieldIdentifier[time.Time]{ +var EmployeeCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: employeeType, } -func EmployeeCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { - return orm.FieldCondition[models.Employee, time.Time]{ - FieldIdentifier: EmployeeCreatedAtField, - Operator: operator, - } +func EmployeeCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Employee] { + return condition.NewFieldCondition[models.Employee, time.Time](EmployeeCreatedAtField, operator) } -var EmployeeUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var EmployeeUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: employeeType, } -func EmployeeUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { - return orm.FieldCondition[models.Employee, time.Time]{ - FieldIdentifier: EmployeeUpdatedAtField, - Operator: operator, - } +func EmployeeUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Employee] { + return condition.NewFieldCondition[models.Employee, time.Time](EmployeeUpdatedAtField, operator) } -var EmployeeDeletedAtField = orm.FieldIdentifier[time.Time]{ +var EmployeeDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: employeeType, } -func EmployeeDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Employee] { - return orm.FieldCondition[models.Employee, time.Time]{ - FieldIdentifier: EmployeeDeletedAtField, - Operator: operator, - } +func EmployeeDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Employee] { + return condition.NewFieldCondition[models.Employee, time.Time](EmployeeDeletedAtField, operator) } -var EmployeeNameField = orm.FieldIdentifier[string]{ +var EmployeeNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: employeeType, } -func EmployeeName(operator orm.Operator[string]) orm.WhereCondition[models.Employee] { - return orm.FieldCondition[models.Employee, string]{ - FieldIdentifier: EmployeeNameField, - Operator: operator, - } +func EmployeeName(operator operator.Operator[string]) condition.WhereCondition[models.Employee] { + return condition.NewFieldCondition[models.Employee, string](EmployeeNameField, operator) } -func EmployeeBoss(conditions ...orm.Condition[models.Employee]) orm.IJoinCondition[models.Employee] { - return orm.JoinCondition[models.Employee, models.Employee]{ - Conditions: conditions, - RelationField: "Boss", - T1Field: "BossID", - T1PreloadCondition: EmployeePreloadAttributes, - T2Field: "ID", - } +func EmployeeBoss(conditions ...condition.Condition[models.Employee]) condition.JoinCondition[models.Employee] { + return condition.NewJoinCondition[models.Employee, models.Employee](conditions, "Boss", "BossID", EmployeePreloadAttributes, "ID") } var EmployeePreloadBoss = EmployeeBoss(EmployeePreloadAttributes) -var EmployeeBossIdField = orm.FieldIdentifier[orm.UUID]{ +var EmployeeBossIdField = query.FieldIdentifier[model.UUID]{ Field: "BossID", ModelType: employeeType, } -func EmployeeBossId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Employee] { - return orm.FieldCondition[models.Employee, orm.UUID]{ - FieldIdentifier: EmployeeBossIdField, - Operator: operator, - } +func EmployeeBossId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Employee] { + return condition.NewFieldCondition[models.Employee, model.UUID](EmployeeBossIdField, operator) } -var EmployeePreloadAttributes = orm.NewPreloadCondition[models.Employee](EmployeeIdField, EmployeeCreatedAtField, EmployeeUpdatedAtField, EmployeeDeletedAtField, EmployeeNameField, EmployeeBossIdField) -var EmployeePreloadRelations = []orm.Condition[models.Employee]{EmployeePreloadBoss} +var EmployeePreloadAttributes = condition.NewPreloadCondition[models.Employee](EmployeeIdField, EmployeeCreatedAtField, EmployeeUpdatedAtField, EmployeeDeletedAtField, EmployeeNameField, EmployeeBossIdField) +var EmployeePreloadRelations = []condition.Condition[models.Employee]{EmployeePreloadBoss} diff --git a/testintegration/conditions/parent1_conditions.go b/testintegration/conditions/parent1_conditions.go index 51343e1c..daa66ecf 100644 --- a/testintegration/conditions/parent1_conditions.go +++ b/testintegration/conditions/parent1_conditions.go @@ -2,82 +2,64 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var parent1Type = reflect.TypeOf(*new(models.Parent1)) -var Parent1IdField = orm.FieldIdentifier[orm.UUID]{ +var Parent1IdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: parent1Type, } -func Parent1Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent1] { - return orm.FieldCondition[models.Parent1, orm.UUID]{ - FieldIdentifier: Parent1IdField, - Operator: operator, - } +func Parent1Id(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Parent1] { + return condition.NewFieldCondition[models.Parent1, model.UUID](Parent1IdField, operator) } -var Parent1CreatedAtField = orm.FieldIdentifier[time.Time]{ +var Parent1CreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: parent1Type, } -func Parent1CreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent1] { - return orm.FieldCondition[models.Parent1, time.Time]{ - FieldIdentifier: Parent1CreatedAtField, - Operator: operator, - } +func Parent1CreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Parent1] { + return condition.NewFieldCondition[models.Parent1, time.Time](Parent1CreatedAtField, operator) } -var Parent1UpdatedAtField = orm.FieldIdentifier[time.Time]{ +var Parent1UpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: parent1Type, } -func Parent1UpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent1] { - return orm.FieldCondition[models.Parent1, time.Time]{ - FieldIdentifier: Parent1UpdatedAtField, - Operator: operator, - } +func Parent1UpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Parent1] { + return condition.NewFieldCondition[models.Parent1, time.Time](Parent1UpdatedAtField, operator) } -var Parent1DeletedAtField = orm.FieldIdentifier[time.Time]{ +var Parent1DeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: parent1Type, } -func Parent1DeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent1] { - return orm.FieldCondition[models.Parent1, time.Time]{ - FieldIdentifier: Parent1DeletedAtField, - Operator: operator, - } +func Parent1DeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Parent1] { + return condition.NewFieldCondition[models.Parent1, time.Time](Parent1DeletedAtField, operator) } -func Parent1ParentParent(conditions ...orm.Condition[models.ParentParent]) orm.IJoinCondition[models.Parent1] { - return orm.JoinCondition[models.Parent1, models.ParentParent]{ - Conditions: conditions, - RelationField: "ParentParent", - T1Field: "ParentParentID", - T1PreloadCondition: Parent1PreloadAttributes, - T2Field: "ID", - } +func Parent1ParentParent(conditions ...condition.Condition[models.ParentParent]) condition.JoinCondition[models.Parent1] { + return condition.NewJoinCondition[models.Parent1, models.ParentParent](conditions, "ParentParent", "ParentParentID", Parent1PreloadAttributes, "ID") } var Parent1PreloadParentParent = Parent1ParentParent(ParentParentPreloadAttributes) -var Parent1ParentParentIdField = orm.FieldIdentifier[orm.UUID]{ +var Parent1ParentParentIdField = query.FieldIdentifier[model.UUID]{ Field: "ParentParentID", ModelType: parent1Type, } -func Parent1ParentParentId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent1] { - return orm.FieldCondition[models.Parent1, orm.UUID]{ - FieldIdentifier: Parent1ParentParentIdField, - Operator: operator, - } +func Parent1ParentParentId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Parent1] { + return condition.NewFieldCondition[models.Parent1, model.UUID](Parent1ParentParentIdField, operator) } -var Parent1PreloadAttributes = orm.NewPreloadCondition[models.Parent1](Parent1IdField, Parent1CreatedAtField, Parent1UpdatedAtField, Parent1DeletedAtField, Parent1ParentParentIdField) -var Parent1PreloadRelations = []orm.Condition[models.Parent1]{Parent1PreloadParentParent} +var Parent1PreloadAttributes = condition.NewPreloadCondition[models.Parent1](Parent1IdField, Parent1CreatedAtField, Parent1UpdatedAtField, Parent1DeletedAtField, Parent1ParentParentIdField) +var Parent1PreloadRelations = []condition.Condition[models.Parent1]{Parent1PreloadParentParent} diff --git a/testintegration/conditions/parent2_conditions.go b/testintegration/conditions/parent2_conditions.go index df4df26a..54584d3a 100644 --- a/testintegration/conditions/parent2_conditions.go +++ b/testintegration/conditions/parent2_conditions.go @@ -2,82 +2,64 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var parent2Type = reflect.TypeOf(*new(models.Parent2)) -var Parent2IdField = orm.FieldIdentifier[orm.UUID]{ +var Parent2IdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: parent2Type, } -func Parent2Id(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent2] { - return orm.FieldCondition[models.Parent2, orm.UUID]{ - FieldIdentifier: Parent2IdField, - Operator: operator, - } +func Parent2Id(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Parent2] { + return condition.NewFieldCondition[models.Parent2, model.UUID](Parent2IdField, operator) } -var Parent2CreatedAtField = orm.FieldIdentifier[time.Time]{ +var Parent2CreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: parent2Type, } -func Parent2CreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent2] { - return orm.FieldCondition[models.Parent2, time.Time]{ - FieldIdentifier: Parent2CreatedAtField, - Operator: operator, - } +func Parent2CreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Parent2] { + return condition.NewFieldCondition[models.Parent2, time.Time](Parent2CreatedAtField, operator) } -var Parent2UpdatedAtField = orm.FieldIdentifier[time.Time]{ +var Parent2UpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: parent2Type, } -func Parent2UpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent2] { - return orm.FieldCondition[models.Parent2, time.Time]{ - FieldIdentifier: Parent2UpdatedAtField, - Operator: operator, - } +func Parent2UpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Parent2] { + return condition.NewFieldCondition[models.Parent2, time.Time](Parent2UpdatedAtField, operator) } -var Parent2DeletedAtField = orm.FieldIdentifier[time.Time]{ +var Parent2DeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: parent2Type, } -func Parent2DeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Parent2] { - return orm.FieldCondition[models.Parent2, time.Time]{ - FieldIdentifier: Parent2DeletedAtField, - Operator: operator, - } +func Parent2DeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Parent2] { + return condition.NewFieldCondition[models.Parent2, time.Time](Parent2DeletedAtField, operator) } -func Parent2ParentParent(conditions ...orm.Condition[models.ParentParent]) orm.IJoinCondition[models.Parent2] { - return orm.JoinCondition[models.Parent2, models.ParentParent]{ - Conditions: conditions, - RelationField: "ParentParent", - T1Field: "ParentParentID", - T1PreloadCondition: Parent2PreloadAttributes, - T2Field: "ID", - } +func Parent2ParentParent(conditions ...condition.Condition[models.ParentParent]) condition.JoinCondition[models.Parent2] { + return condition.NewJoinCondition[models.Parent2, models.ParentParent](conditions, "ParentParent", "ParentParentID", Parent2PreloadAttributes, "ID") } var Parent2PreloadParentParent = Parent2ParentParent(ParentParentPreloadAttributes) -var Parent2ParentParentIdField = orm.FieldIdentifier[orm.UUID]{ +var Parent2ParentParentIdField = query.FieldIdentifier[model.UUID]{ Field: "ParentParentID", ModelType: parent2Type, } -func Parent2ParentParentId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Parent2] { - return orm.FieldCondition[models.Parent2, orm.UUID]{ - FieldIdentifier: Parent2ParentParentIdField, - Operator: operator, - } +func Parent2ParentParentId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Parent2] { + return condition.NewFieldCondition[models.Parent2, model.UUID](Parent2ParentParentIdField, operator) } -var Parent2PreloadAttributes = orm.NewPreloadCondition[models.Parent2](Parent2IdField, Parent2CreatedAtField, Parent2UpdatedAtField, Parent2DeletedAtField, Parent2ParentParentIdField) -var Parent2PreloadRelations = []orm.Condition[models.Parent2]{Parent2PreloadParentParent} +var Parent2PreloadAttributes = condition.NewPreloadCondition[models.Parent2](Parent2IdField, Parent2CreatedAtField, Parent2UpdatedAtField, Parent2DeletedAtField, Parent2ParentParentIdField) +var Parent2PreloadRelations = []condition.Condition[models.Parent2]{Parent2PreloadParentParent} diff --git a/testintegration/conditions/parent_parent_conditions.go b/testintegration/conditions/parent_parent_conditions.go index 00d48641..9c24f784 100644 --- a/testintegration/conditions/parent_parent_conditions.go +++ b/testintegration/conditions/parent_parent_conditions.go @@ -2,83 +2,68 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var parentParentType = reflect.TypeOf(*new(models.ParentParent)) -var ParentParentIdField = orm.FieldIdentifier[orm.UUID]{ +var ParentParentIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: parentParentType, } -func ParentParentId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.ParentParent] { - return orm.FieldCondition[models.ParentParent, orm.UUID]{ - FieldIdentifier: ParentParentIdField, - Operator: operator, - } +func ParentParentId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.ParentParent] { + return condition.NewFieldCondition[models.ParentParent, model.UUID](ParentParentIdField, operator) } -var ParentParentCreatedAtField = orm.FieldIdentifier[time.Time]{ +var ParentParentCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: parentParentType, } -func ParentParentCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.ParentParent] { - return orm.FieldCondition[models.ParentParent, time.Time]{ - FieldIdentifier: ParentParentCreatedAtField, - Operator: operator, - } +func ParentParentCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.ParentParent] { + return condition.NewFieldCondition[models.ParentParent, time.Time](ParentParentCreatedAtField, operator) } -var ParentParentUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var ParentParentUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: parentParentType, } -func ParentParentUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.ParentParent] { - return orm.FieldCondition[models.ParentParent, time.Time]{ - FieldIdentifier: ParentParentUpdatedAtField, - Operator: operator, - } +func ParentParentUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.ParentParent] { + return condition.NewFieldCondition[models.ParentParent, time.Time](ParentParentUpdatedAtField, operator) } -var ParentParentDeletedAtField = orm.FieldIdentifier[time.Time]{ +var ParentParentDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: parentParentType, } -func ParentParentDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.ParentParent] { - return orm.FieldCondition[models.ParentParent, time.Time]{ - FieldIdentifier: ParentParentDeletedAtField, - Operator: operator, - } +func ParentParentDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.ParentParent] { + return condition.NewFieldCondition[models.ParentParent, time.Time](ParentParentDeletedAtField, operator) } -var ParentParentNameField = orm.FieldIdentifier[string]{ +var ParentParentNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: parentParentType, } -func ParentParentName(operator orm.Operator[string]) orm.WhereCondition[models.ParentParent] { - return orm.FieldCondition[models.ParentParent, string]{ - FieldIdentifier: ParentParentNameField, - Operator: operator, - } +func ParentParentName(operator operator.Operator[string]) condition.WhereCondition[models.ParentParent] { + return condition.NewFieldCondition[models.ParentParent, string](ParentParentNameField, operator) } -var ParentParentNumberField = orm.FieldIdentifier[int]{ +var ParentParentNumberField = query.FieldIdentifier[int]{ Field: "Number", ModelType: parentParentType, } -func ParentParentNumber(operator orm.Operator[int]) orm.WhereCondition[models.ParentParent] { - return orm.FieldCondition[models.ParentParent, int]{ - FieldIdentifier: ParentParentNumberField, - Operator: operator, - } +func ParentParentNumber(operator operator.Operator[int]) condition.WhereCondition[models.ParentParent] { + return condition.NewFieldCondition[models.ParentParent, int](ParentParentNumberField, operator) } -var ParentParentPreloadAttributes = orm.NewPreloadCondition[models.ParentParent](ParentParentIdField, ParentParentCreatedAtField, ParentParentUpdatedAtField, ParentParentDeletedAtField, ParentParentNameField, ParentParentNumberField) +var ParentParentPreloadAttributes = condition.NewPreloadCondition[models.ParentParent](ParentParentIdField, ParentParentCreatedAtField, ParentParentUpdatedAtField, ParentParentDeletedAtField, ParentParentNameField, ParentParentNumberField) diff --git a/testintegration/conditions/person_conditions.go b/testintegration/conditions/person_conditions.go index d43a16a7..1d23e05e 100644 --- a/testintegration/conditions/person_conditions.go +++ b/testintegration/conditions/person_conditions.go @@ -2,71 +2,59 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var personType = reflect.TypeOf(*new(models.Person)) -var PersonIdField = orm.FieldIdentifier[orm.UUID]{ +var PersonIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: personType, } -func PersonId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Person] { - return orm.FieldCondition[models.Person, orm.UUID]{ - FieldIdentifier: PersonIdField, - Operator: operator, - } +func PersonId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Person] { + return condition.NewFieldCondition[models.Person, model.UUID](PersonIdField, operator) } -var PersonCreatedAtField = orm.FieldIdentifier[time.Time]{ +var PersonCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: personType, } -func PersonCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { - return orm.FieldCondition[models.Person, time.Time]{ - FieldIdentifier: PersonCreatedAtField, - Operator: operator, - } +func PersonCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Person] { + return condition.NewFieldCondition[models.Person, time.Time](PersonCreatedAtField, operator) } -var PersonUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var PersonUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: personType, } -func PersonUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { - return orm.FieldCondition[models.Person, time.Time]{ - FieldIdentifier: PersonUpdatedAtField, - Operator: operator, - } +func PersonUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Person] { + return condition.NewFieldCondition[models.Person, time.Time](PersonUpdatedAtField, operator) } -var PersonDeletedAtField = orm.FieldIdentifier[time.Time]{ +var PersonDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: personType, } -func PersonDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Person] { - return orm.FieldCondition[models.Person, time.Time]{ - FieldIdentifier: PersonDeletedAtField, - Operator: operator, - } +func PersonDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Person] { + return condition.NewFieldCondition[models.Person, time.Time](PersonDeletedAtField, operator) } -var PersonNameField = orm.FieldIdentifier[string]{ +var PersonNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: personType, } -func PersonName(operator orm.Operator[string]) orm.WhereCondition[models.Person] { - return orm.FieldCondition[models.Person, string]{ - FieldIdentifier: PersonNameField, - Operator: operator, - } +func PersonName(operator operator.Operator[string]) condition.WhereCondition[models.Person] { + return condition.NewFieldCondition[models.Person, string](PersonNameField, operator) } -var PersonPreloadAttributes = orm.NewPreloadCondition[models.Person](PersonIdField, PersonCreatedAtField, PersonUpdatedAtField, PersonDeletedAtField, PersonNameField) +var PersonPreloadAttributes = condition.NewPreloadCondition[models.Person](PersonIdField, PersonCreatedAtField, PersonUpdatedAtField, PersonDeletedAtField, PersonNameField) diff --git a/testintegration/conditions/phone_conditions.go b/testintegration/conditions/phone_conditions.go index 976a3c3a..51a86e60 100644 --- a/testintegration/conditions/phone_conditions.go +++ b/testintegration/conditions/phone_conditions.go @@ -2,94 +2,73 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var phoneType = reflect.TypeOf(*new(models.Phone)) -var PhoneIdField = orm.FieldIdentifier[orm.UIntID]{ +var PhoneIdField = query.FieldIdentifier[model.UIntID]{ Field: "ID", ModelType: phoneType, } -func PhoneId(operator orm.Operator[orm.UIntID]) orm.WhereCondition[models.Phone] { - return orm.FieldCondition[models.Phone, orm.UIntID]{ - FieldIdentifier: PhoneIdField, - Operator: operator, - } +func PhoneId(operator operator.Operator[model.UIntID]) condition.WhereCondition[models.Phone] { + return condition.NewFieldCondition[models.Phone, model.UIntID](PhoneIdField, operator) } -var PhoneCreatedAtField = orm.FieldIdentifier[time.Time]{ +var PhoneCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: phoneType, } -func PhoneCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { - return orm.FieldCondition[models.Phone, time.Time]{ - FieldIdentifier: PhoneCreatedAtField, - Operator: operator, - } +func PhoneCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Phone] { + return condition.NewFieldCondition[models.Phone, time.Time](PhoneCreatedAtField, operator) } -var PhoneUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var PhoneUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: phoneType, } -func PhoneUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { - return orm.FieldCondition[models.Phone, time.Time]{ - FieldIdentifier: PhoneUpdatedAtField, - Operator: operator, - } +func PhoneUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Phone] { + return condition.NewFieldCondition[models.Phone, time.Time](PhoneUpdatedAtField, operator) } -var PhoneDeletedAtField = orm.FieldIdentifier[time.Time]{ +var PhoneDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: phoneType, } -func PhoneDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Phone] { - return orm.FieldCondition[models.Phone, time.Time]{ - FieldIdentifier: PhoneDeletedAtField, - Operator: operator, - } +func PhoneDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Phone] { + return condition.NewFieldCondition[models.Phone, time.Time](PhoneDeletedAtField, operator) } -var PhoneNameField = orm.FieldIdentifier[string]{ +var PhoneNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: phoneType, } -func PhoneName(operator orm.Operator[string]) orm.WhereCondition[models.Phone] { - return orm.FieldCondition[models.Phone, string]{ - FieldIdentifier: PhoneNameField, - Operator: operator, - } +func PhoneName(operator operator.Operator[string]) condition.WhereCondition[models.Phone] { + return condition.NewFieldCondition[models.Phone, string](PhoneNameField, operator) } -func PhoneBrand(conditions ...orm.Condition[models.Brand]) orm.IJoinCondition[models.Phone] { - return orm.JoinCondition[models.Phone, models.Brand]{ - Conditions: conditions, - RelationField: "Brand", - T1Field: "BrandID", - T1PreloadCondition: PhonePreloadAttributes, - T2Field: "ID", - } +func PhoneBrand(conditions ...condition.Condition[models.Brand]) condition.JoinCondition[models.Phone] { + return condition.NewJoinCondition[models.Phone, models.Brand](conditions, "Brand", "BrandID", PhonePreloadAttributes, "ID") } var PhonePreloadBrand = PhoneBrand(BrandPreloadAttributes) -var PhoneBrandIdField = orm.FieldIdentifier[uint]{ +var PhoneBrandIdField = query.FieldIdentifier[uint]{ Field: "BrandID", ModelType: phoneType, } -func PhoneBrandId(operator orm.Operator[uint]) orm.WhereCondition[models.Phone] { - return orm.FieldCondition[models.Phone, uint]{ - FieldIdentifier: PhoneBrandIdField, - Operator: operator, - } +func PhoneBrandId(operator operator.Operator[uint]) condition.WhereCondition[models.Phone] { + return condition.NewFieldCondition[models.Phone, uint](PhoneBrandIdField, operator) } -var PhonePreloadAttributes = orm.NewPreloadCondition[models.Phone](PhoneIdField, PhoneCreatedAtField, PhoneUpdatedAtField, PhoneDeletedAtField, PhoneNameField, PhoneBrandIdField) -var PhonePreloadRelations = []orm.Condition[models.Phone]{PhonePreloadBrand} +var PhonePreloadAttributes = condition.NewPreloadCondition[models.Phone](PhoneIdField, PhoneCreatedAtField, PhoneUpdatedAtField, PhoneDeletedAtField, PhoneNameField, PhoneBrandIdField) +var PhonePreloadRelations = []condition.Condition[models.Phone]{PhonePreloadBrand} diff --git a/testintegration/conditions/product_conditions.go b/testintegration/conditions/product_conditions.go index 6ec05899..316f0206 100644 --- a/testintegration/conditions/product_conditions.go +++ b/testintegration/conditions/product_conditions.go @@ -2,193 +2,151 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var productType = reflect.TypeOf(*new(models.Product)) -var ProductIdField = orm.FieldIdentifier[orm.UUID]{ +var ProductIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: productType, } -func ProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, orm.UUID]{ - FieldIdentifier: ProductIdField, - Operator: operator, - } +func ProductId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, model.UUID](ProductIdField, operator) } -var ProductCreatedAtField = orm.FieldIdentifier[time.Time]{ +var ProductCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: productType, } -func ProductCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, time.Time]{ - FieldIdentifier: ProductCreatedAtField, - Operator: operator, - } +func ProductCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, time.Time](ProductCreatedAtField, operator) } -var ProductUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var ProductUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: productType, } -func ProductUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, time.Time]{ - FieldIdentifier: ProductUpdatedAtField, - Operator: operator, - } +func ProductUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, time.Time](ProductUpdatedAtField, operator) } -var ProductDeletedAtField = orm.FieldIdentifier[time.Time]{ +var ProductDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: productType, } -func ProductDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, time.Time]{ - FieldIdentifier: ProductDeletedAtField, - Operator: operator, - } +func ProductDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, time.Time](ProductDeletedAtField, operator) } -var ProductStringField = orm.FieldIdentifier[string]{ +var ProductStringField = query.FieldIdentifier[string]{ Column: "string_something_else", Field: "String", ModelType: productType, } -func ProductString(operator orm.Operator[string]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, string]{ - FieldIdentifier: ProductStringField, - Operator: operator, - } +func ProductString(operator operator.Operator[string]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, string](ProductStringField, operator) } -var ProductIntField = orm.FieldIdentifier[int]{ +var ProductIntField = query.FieldIdentifier[int]{ Field: "Int", ModelType: productType, } -func ProductInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, int]{ - FieldIdentifier: ProductIntField, - Operator: operator, - } +func ProductInt(operator operator.Operator[int]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, int](ProductIntField, operator) } -var ProductIntPointerField = orm.FieldIdentifier[int]{ +var ProductIntPointerField = query.FieldIdentifier[int]{ Field: "IntPointer", ModelType: productType, } -func ProductIntPointer(operator orm.Operator[int]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, int]{ - FieldIdentifier: ProductIntPointerField, - Operator: operator, - } +func ProductIntPointer(operator operator.Operator[int]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, int](ProductIntPointerField, operator) } -var ProductFloatField = orm.FieldIdentifier[float64]{ +var ProductFloatField = query.FieldIdentifier[float64]{ Field: "Float", ModelType: productType, } -func ProductFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, float64]{ - FieldIdentifier: ProductFloatField, - Operator: operator, - } +func ProductFloat(operator operator.Operator[float64]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, float64](ProductFloatField, operator) } -var ProductNullFloatField = orm.FieldIdentifier[float64]{ +var ProductNullFloatField = query.FieldIdentifier[float64]{ Field: "NullFloat", ModelType: productType, } -func ProductNullFloat(operator orm.Operator[float64]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, float64]{ - FieldIdentifier: ProductNullFloatField, - Operator: operator, - } +func ProductNullFloat(operator operator.Operator[float64]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, float64](ProductNullFloatField, operator) } -var ProductBoolField = orm.FieldIdentifier[bool]{ +var ProductBoolField = query.FieldIdentifier[bool]{ Field: "Bool", ModelType: productType, } -func ProductBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, bool]{ - FieldIdentifier: ProductBoolField, - Operator: operator, - } +func ProductBool(operator operator.Operator[bool]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, bool](ProductBoolField, operator) } -var ProductNullBoolField = orm.FieldIdentifier[bool]{ +var ProductNullBoolField = query.FieldIdentifier[bool]{ Field: "NullBool", ModelType: productType, } -func ProductNullBool(operator orm.Operator[bool]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, bool]{ - FieldIdentifier: ProductNullBoolField, - Operator: operator, - } +func ProductNullBool(operator operator.Operator[bool]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, bool](ProductNullBoolField, operator) } -var ProductByteArrayField = orm.FieldIdentifier[[]uint8]{ +var ProductByteArrayField = query.FieldIdentifier[[]uint8]{ Field: "ByteArray", ModelType: productType, } -func ProductByteArray(operator orm.Operator[[]uint8]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, []uint8]{ - FieldIdentifier: ProductByteArrayField, - Operator: operator, - } +func ProductByteArray(operator operator.Operator[[]uint8]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, []uint8](ProductByteArrayField, operator) } -var ProductMultiStringField = orm.FieldIdentifier[models.MultiString]{ +var ProductMultiStringField = query.FieldIdentifier[models.MultiString]{ Field: "MultiString", ModelType: productType, } -func ProductMultiString(operator orm.Operator[models.MultiString]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, models.MultiString]{ - FieldIdentifier: ProductMultiStringField, - Operator: operator, - } +func ProductMultiString(operator operator.Operator[models.MultiString]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, models.MultiString](ProductMultiStringField, operator) } -var ProductToBeEmbeddedEmbeddedIntField = orm.FieldIdentifier[int]{ +var ProductToBeEmbeddedEmbeddedIntField = query.FieldIdentifier[int]{ Field: "EmbeddedInt", ModelType: productType, } -func ProductToBeEmbeddedEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, int]{ - FieldIdentifier: ProductToBeEmbeddedEmbeddedIntField, - Operator: operator, - } +func ProductToBeEmbeddedEmbeddedInt(operator operator.Operator[int]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, int](ProductToBeEmbeddedEmbeddedIntField, operator) } -var ProductGormEmbeddedIntField = orm.FieldIdentifier[int]{ +var ProductGormEmbeddedIntField = query.FieldIdentifier[int]{ ColumnPrefix: "gorm_embedded_", Field: "Int", ModelType: productType, } -func ProductGormEmbeddedInt(operator orm.Operator[int]) orm.WhereCondition[models.Product] { - return orm.FieldCondition[models.Product, int]{ - FieldIdentifier: ProductGormEmbeddedIntField, - Operator: operator, - } +func ProductGormEmbeddedInt(operator operator.Operator[int]) condition.WhereCondition[models.Product] { + return condition.NewFieldCondition[models.Product, int](ProductGormEmbeddedIntField, operator) } -var ProductPreloadAttributes = orm.NewPreloadCondition[models.Product](ProductIdField, ProductCreatedAtField, ProductUpdatedAtField, ProductDeletedAtField, ProductStringField, ProductIntField, ProductIntPointerField, ProductFloatField, ProductNullFloatField, ProductBoolField, ProductNullBoolField, ProductByteArrayField, ProductMultiStringField, ProductToBeEmbeddedEmbeddedIntField, ProductGormEmbeddedIntField) +var ProductPreloadAttributes = condition.NewPreloadCondition[models.Product](ProductIdField, ProductCreatedAtField, ProductUpdatedAtField, ProductDeletedAtField, ProductStringField, ProductIntField, ProductIntPointerField, ProductFloatField, ProductNullFloatField, ProductBoolField, ProductNullBoolField, ProductByteArrayField, ProductMultiStringField, ProductToBeEmbeddedEmbeddedIntField, ProductGormEmbeddedIntField) diff --git a/testintegration/conditions/sale_conditions.go b/testintegration/conditions/sale_conditions.go index 6ab2984c..2204453f 100644 --- a/testintegration/conditions/sale_conditions.go +++ b/testintegration/conditions/sale_conditions.go @@ -2,128 +2,95 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var saleType = reflect.TypeOf(*new(models.Sale)) -var SaleIdField = orm.FieldIdentifier[orm.UUID]{ +var SaleIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: saleType, } -func SaleId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { - return orm.FieldCondition[models.Sale, orm.UUID]{ - FieldIdentifier: SaleIdField, - Operator: operator, - } +func SaleId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Sale] { + return condition.NewFieldCondition[models.Sale, model.UUID](SaleIdField, operator) } -var SaleCreatedAtField = orm.FieldIdentifier[time.Time]{ +var SaleCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: saleType, } -func SaleCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { - return orm.FieldCondition[models.Sale, time.Time]{ - FieldIdentifier: SaleCreatedAtField, - Operator: operator, - } +func SaleCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Sale] { + return condition.NewFieldCondition[models.Sale, time.Time](SaleCreatedAtField, operator) } -var SaleUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var SaleUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: saleType, } -func SaleUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { - return orm.FieldCondition[models.Sale, time.Time]{ - FieldIdentifier: SaleUpdatedAtField, - Operator: operator, - } +func SaleUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Sale] { + return condition.NewFieldCondition[models.Sale, time.Time](SaleUpdatedAtField, operator) } -var SaleDeletedAtField = orm.FieldIdentifier[time.Time]{ +var SaleDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: saleType, } -func SaleDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Sale] { - return orm.FieldCondition[models.Sale, time.Time]{ - FieldIdentifier: SaleDeletedAtField, - Operator: operator, - } +func SaleDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Sale] { + return condition.NewFieldCondition[models.Sale, time.Time](SaleDeletedAtField, operator) } -var SaleCodeField = orm.FieldIdentifier[int]{ +var SaleCodeField = query.FieldIdentifier[int]{ Field: "Code", ModelType: saleType, } -func SaleCode(operator orm.Operator[int]) orm.WhereCondition[models.Sale] { - return orm.FieldCondition[models.Sale, int]{ - FieldIdentifier: SaleCodeField, - Operator: operator, - } +func SaleCode(operator operator.Operator[int]) condition.WhereCondition[models.Sale] { + return condition.NewFieldCondition[models.Sale, int](SaleCodeField, operator) } -var SaleDescriptionField = orm.FieldIdentifier[string]{ +var SaleDescriptionField = query.FieldIdentifier[string]{ Field: "Description", ModelType: saleType, } -func SaleDescription(operator orm.Operator[string]) orm.WhereCondition[models.Sale] { - return orm.FieldCondition[models.Sale, string]{ - FieldIdentifier: SaleDescriptionField, - Operator: operator, - } +func SaleDescription(operator operator.Operator[string]) condition.WhereCondition[models.Sale] { + return condition.NewFieldCondition[models.Sale, string](SaleDescriptionField, operator) } -func SaleProduct(conditions ...orm.Condition[models.Product]) orm.IJoinCondition[models.Sale] { - return orm.JoinCondition[models.Sale, models.Product]{ - Conditions: conditions, - RelationField: "Product", - T1Field: "ProductID", - T1PreloadCondition: SalePreloadAttributes, - T2Field: "ID", - } +func SaleProduct(conditions ...condition.Condition[models.Product]) condition.JoinCondition[models.Sale] { + return condition.NewJoinCondition[models.Sale, models.Product](conditions, "Product", "ProductID", SalePreloadAttributes, "ID") } var SalePreloadProduct = SaleProduct(ProductPreloadAttributes) -var SaleProductIdField = orm.FieldIdentifier[orm.UUID]{ +var SaleProductIdField = query.FieldIdentifier[model.UUID]{ Field: "ProductID", ModelType: saleType, } -func SaleProductId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { - return orm.FieldCondition[models.Sale, orm.UUID]{ - FieldIdentifier: SaleProductIdField, - Operator: operator, - } +func SaleProductId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Sale] { + return condition.NewFieldCondition[models.Sale, model.UUID](SaleProductIdField, operator) } -func SaleSeller(conditions ...orm.Condition[models.Seller]) orm.IJoinCondition[models.Sale] { - return orm.JoinCondition[models.Sale, models.Seller]{ - Conditions: conditions, - RelationField: "Seller", - T1Field: "SellerID", - T1PreloadCondition: SalePreloadAttributes, - T2Field: "ID", - } +func SaleSeller(conditions ...condition.Condition[models.Seller]) condition.JoinCondition[models.Sale] { + return condition.NewJoinCondition[models.Sale, models.Seller](conditions, "Seller", "SellerID", SalePreloadAttributes, "ID") } var SalePreloadSeller = SaleSeller(SellerPreloadAttributes) -var SaleSellerIdField = orm.FieldIdentifier[orm.UUID]{ +var SaleSellerIdField = query.FieldIdentifier[model.UUID]{ Field: "SellerID", ModelType: saleType, } -func SaleSellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Sale] { - return orm.FieldCondition[models.Sale, orm.UUID]{ - FieldIdentifier: SaleSellerIdField, - Operator: operator, - } +func SaleSellerId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Sale] { + return condition.NewFieldCondition[models.Sale, model.UUID](SaleSellerIdField, operator) } -var SalePreloadAttributes = orm.NewPreloadCondition[models.Sale](SaleIdField, SaleCreatedAtField, SaleUpdatedAtField, SaleDeletedAtField, SaleCodeField, SaleDescriptionField, SaleProductIdField, SaleSellerIdField) -var SalePreloadRelations = []orm.Condition[models.Sale]{SalePreloadProduct, SalePreloadSeller} +var SalePreloadAttributes = condition.NewPreloadCondition[models.Sale](SaleIdField, SaleCreatedAtField, SaleUpdatedAtField, SaleDeletedAtField, SaleCodeField, SaleDescriptionField, SaleProductIdField, SaleSellerIdField) +var SalePreloadRelations = []condition.Condition[models.Sale]{SalePreloadProduct, SalePreloadSeller} diff --git a/testintegration/conditions/seller_conditions.go b/testintegration/conditions/seller_conditions.go index 611ca23a..beff0f5e 100644 --- a/testintegration/conditions/seller_conditions.go +++ b/testintegration/conditions/seller_conditions.go @@ -2,116 +2,86 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var sellerType = reflect.TypeOf(*new(models.Seller)) -var SellerIdField = orm.FieldIdentifier[orm.UUID]{ +var SellerIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: sellerType, } -func SellerId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { - return orm.FieldCondition[models.Seller, orm.UUID]{ - FieldIdentifier: SellerIdField, - Operator: operator, - } +func SellerId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Seller] { + return condition.NewFieldCondition[models.Seller, model.UUID](SellerIdField, operator) } -var SellerCreatedAtField = orm.FieldIdentifier[time.Time]{ +var SellerCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: sellerType, } -func SellerCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { - return orm.FieldCondition[models.Seller, time.Time]{ - FieldIdentifier: SellerCreatedAtField, - Operator: operator, - } +func SellerCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Seller] { + return condition.NewFieldCondition[models.Seller, time.Time](SellerCreatedAtField, operator) } -var SellerUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var SellerUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: sellerType, } -func SellerUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { - return orm.FieldCondition[models.Seller, time.Time]{ - FieldIdentifier: SellerUpdatedAtField, - Operator: operator, - } +func SellerUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Seller] { + return condition.NewFieldCondition[models.Seller, time.Time](SellerUpdatedAtField, operator) } -var SellerDeletedAtField = orm.FieldIdentifier[time.Time]{ +var SellerDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: sellerType, } -func SellerDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.Seller] { - return orm.FieldCondition[models.Seller, time.Time]{ - FieldIdentifier: SellerDeletedAtField, - Operator: operator, - } +func SellerDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.Seller] { + return condition.NewFieldCondition[models.Seller, time.Time](SellerDeletedAtField, operator) } -var SellerNameField = orm.FieldIdentifier[string]{ +var SellerNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: sellerType, } -func SellerName(operator orm.Operator[string]) orm.WhereCondition[models.Seller] { - return orm.FieldCondition[models.Seller, string]{ - FieldIdentifier: SellerNameField, - Operator: operator, - } +func SellerName(operator operator.Operator[string]) condition.WhereCondition[models.Seller] { + return condition.NewFieldCondition[models.Seller, string](SellerNameField, operator) } -func SellerCompany(conditions ...orm.Condition[models.Company]) orm.IJoinCondition[models.Seller] { - return orm.JoinCondition[models.Seller, models.Company]{ - Conditions: conditions, - RelationField: "Company", - T1Field: "CompanyID", - T1PreloadCondition: SellerPreloadAttributes, - T2Field: "ID", - } +func SellerCompany(conditions ...condition.Condition[models.Company]) condition.JoinCondition[models.Seller] { + return condition.NewJoinCondition[models.Seller, models.Company](conditions, "Company", "CompanyID", SellerPreloadAttributes, "ID") } var SellerPreloadCompany = SellerCompany(CompanyPreloadAttributes) -var SellerCompanyIdField = orm.FieldIdentifier[orm.UUID]{ +var SellerCompanyIdField = query.FieldIdentifier[model.UUID]{ Field: "CompanyID", ModelType: sellerType, } -func SellerCompanyId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { - return orm.FieldCondition[models.Seller, orm.UUID]{ - FieldIdentifier: SellerCompanyIdField, - Operator: operator, - } +func SellerCompanyId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Seller] { + return condition.NewFieldCondition[models.Seller, model.UUID](SellerCompanyIdField, operator) } -func SellerUniversity(conditions ...orm.Condition[models.University]) orm.IJoinCondition[models.Seller] { - return orm.JoinCondition[models.Seller, models.University]{ - Conditions: conditions, - RelationField: "University", - T1Field: "UniversityID", - T1PreloadCondition: SellerPreloadAttributes, - T2Field: "ID", - } +func SellerUniversity(conditions ...condition.Condition[models.University]) condition.JoinCondition[models.Seller] { + return condition.NewJoinCondition[models.Seller, models.University](conditions, "University", "UniversityID", SellerPreloadAttributes, "ID") } var SellerPreloadUniversity = SellerUniversity(UniversityPreloadAttributes) -var SellerUniversityIdField = orm.FieldIdentifier[orm.UUID]{ +var SellerUniversityIdField = query.FieldIdentifier[model.UUID]{ Field: "UniversityID", ModelType: sellerType, } -func SellerUniversityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.Seller] { - return orm.FieldCondition[models.Seller, orm.UUID]{ - FieldIdentifier: SellerUniversityIdField, - Operator: operator, - } +func SellerUniversityId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.Seller] { + return condition.NewFieldCondition[models.Seller, model.UUID](SellerUniversityIdField, operator) } -var SellerPreloadAttributes = orm.NewPreloadCondition[models.Seller](SellerIdField, SellerCreatedAtField, SellerUpdatedAtField, SellerDeletedAtField, SellerNameField, SellerCompanyIdField, SellerUniversityIdField) -var SellerPreloadRelations = []orm.Condition[models.Seller]{SellerPreloadCompany, SellerPreloadUniversity} +var SellerPreloadAttributes = condition.NewPreloadCondition[models.Seller](SellerIdField, SellerCreatedAtField, SellerUpdatedAtField, SellerDeletedAtField, SellerNameField, SellerCompanyIdField, SellerUniversityIdField) +var SellerPreloadRelations = []condition.Condition[models.Seller]{SellerPreloadCompany, SellerPreloadUniversity} diff --git a/testintegration/conditions/university_conditions.go b/testintegration/conditions/university_conditions.go index fd5255bd..206db16f 100644 --- a/testintegration/conditions/university_conditions.go +++ b/testintegration/conditions/university_conditions.go @@ -2,71 +2,59 @@ package conditions import ( - orm "github.com/ditrit/badaas/orm" + condition "github.com/ditrit/badaas/orm/condition" + model "github.com/ditrit/badaas/orm/model" + operator "github.com/ditrit/badaas/orm/operator" + query "github.com/ditrit/badaas/orm/query" models "github.com/ditrit/badaas/testintegration/models" "reflect" "time" ) var universityType = reflect.TypeOf(*new(models.University)) -var UniversityIdField = orm.FieldIdentifier[orm.UUID]{ +var UniversityIdField = query.FieldIdentifier[model.UUID]{ Field: "ID", ModelType: universityType, } -func UniversityId(operator orm.Operator[orm.UUID]) orm.WhereCondition[models.University] { - return orm.FieldCondition[models.University, orm.UUID]{ - FieldIdentifier: UniversityIdField, - Operator: operator, - } +func UniversityId(operator operator.Operator[model.UUID]) condition.WhereCondition[models.University] { + return condition.NewFieldCondition[models.University, model.UUID](UniversityIdField, operator) } -var UniversityCreatedAtField = orm.FieldIdentifier[time.Time]{ +var UniversityCreatedAtField = query.FieldIdentifier[time.Time]{ Field: "CreatedAt", ModelType: universityType, } -func UniversityCreatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.University] { - return orm.FieldCondition[models.University, time.Time]{ - FieldIdentifier: UniversityCreatedAtField, - Operator: operator, - } +func UniversityCreatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.University] { + return condition.NewFieldCondition[models.University, time.Time](UniversityCreatedAtField, operator) } -var UniversityUpdatedAtField = orm.FieldIdentifier[time.Time]{ +var UniversityUpdatedAtField = query.FieldIdentifier[time.Time]{ Field: "UpdatedAt", ModelType: universityType, } -func UniversityUpdatedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.University] { - return orm.FieldCondition[models.University, time.Time]{ - FieldIdentifier: UniversityUpdatedAtField, - Operator: operator, - } +func UniversityUpdatedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.University] { + return condition.NewFieldCondition[models.University, time.Time](UniversityUpdatedAtField, operator) } -var UniversityDeletedAtField = orm.FieldIdentifier[time.Time]{ +var UniversityDeletedAtField = query.FieldIdentifier[time.Time]{ Field: "DeletedAt", ModelType: universityType, } -func UniversityDeletedAt(operator orm.Operator[time.Time]) orm.WhereCondition[models.University] { - return orm.FieldCondition[models.University, time.Time]{ - FieldIdentifier: UniversityDeletedAtField, - Operator: operator, - } +func UniversityDeletedAt(operator operator.Operator[time.Time]) condition.WhereCondition[models.University] { + return condition.NewFieldCondition[models.University, time.Time](UniversityDeletedAtField, operator) } -var UniversityNameField = orm.FieldIdentifier[string]{ +var UniversityNameField = query.FieldIdentifier[string]{ Field: "Name", ModelType: universityType, } -func UniversityName(operator orm.Operator[string]) orm.WhereCondition[models.University] { - return orm.FieldCondition[models.University, string]{ - FieldIdentifier: UniversityNameField, - Operator: operator, - } +func UniversityName(operator operator.Operator[string]) condition.WhereCondition[models.University] { + return condition.NewFieldCondition[models.University, string](UniversityNameField, operator) } -var UniversityPreloadAttributes = orm.NewPreloadCondition[models.University](UniversityIdField, UniversityCreatedAtField, UniversityUpdatedAtField, UniversityDeletedAtField, UniversityNameField) +var UniversityPreloadAttributes = condition.NewPreloadCondition[models.University](UniversityIdField, UniversityCreatedAtField, UniversityUpdatedAtField, UniversityDeletedAtField, UniversityNameField) diff --git a/testintegration/crudRepository.go b/testintegration/crudRepository.go index f81bed76..0dc4cee8 100644 --- a/testintegration/crudRepository.go +++ b/testintegration/crudRepository.go @@ -6,6 +6,8 @@ import ( "gotest.tools/assert" "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/errors" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" ) @@ -13,12 +15,12 @@ import ( type CRUDRepositoryIntTestSuite struct { suite.Suite db *gorm.DB - crudProductRepository orm.CRUDRepository[models.Product, orm.UUID] + crudProductRepository orm.CRUDRepository[models.Product, model.UUID] } func NewCRUDRepositoryIntTestSuite( db *gorm.DB, - crudProductRepository orm.CRUDRepository[models.Product, orm.UUID], + crudProductRepository orm.CRUDRepository[models.Product, model.UUID], ) *CRUDRepositoryIntTestSuite { return &CRUDRepositoryIntTestSuite{ db: db, @@ -38,7 +40,7 @@ func (ts *CRUDRepositoryIntTestSuite) TearDownSuite() { func (ts *CRUDRepositoryIntTestSuite) TestGetByIDReturnsErrorIfIDDontMatch() { ts.createProduct(0) - _, err := ts.crudProductRepository.GetByID(ts.db, orm.NilUUID) + _, err := ts.crudProductRepository.GetByID(ts.db, model.NilUUID) ts.Error(err, gorm.ErrRecordNotFound) } @@ -80,7 +82,7 @@ func (ts *CRUDRepositoryIntTestSuite) TestQueryOneReturnsErrorIfMoreThanOneMatch ts.db, conditions.ProductInt(orm.Eq(0)), ) - ts.Error(err, orm.ErrMoreThanOneObjectFound) + ts.Error(err, errors.ErrMoreThanOneObjectFound) } // ------------------------- utils ------------------------- diff --git a/testintegration/crudServiceCommon.go b/testintegration/crudServiceCommon.go index 67a506e2..1cfe8458 100644 --- a/testintegration/crudServiceCommon.go +++ b/testintegration/crudServiceCommon.go @@ -4,7 +4,7 @@ import ( "github.com/stretchr/testify/suite" "gorm.io/gorm" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/testintegration/models" ) @@ -48,7 +48,7 @@ func (ts *CRUDServiceCommonIntTestSuite) createSale(code int, product *models.Pr } func (ts *CRUDServiceCommonIntTestSuite) createSeller(name string, company *models.Company) *models.Seller { - var companyID *orm.UUID + var companyID *model.UUID if company != nil { companyID = &company.ID } diff --git a/testintegration/join_conditions_test.go b/testintegration/join_conditions_test.go index a2143b3e..79a63783 100644 --- a/testintegration/join_conditions_test.go +++ b/testintegration/join_conditions_test.go @@ -5,6 +5,8 @@ import ( "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/orm/dynamic" + "github.com/ditrit/badaas/orm/errors" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/orm/unsafe" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" @@ -12,26 +14,26 @@ import ( type JoinConditionsIntTestSuite struct { CRUDServiceCommonIntTestSuite - crudSaleService orm.CRUDService[models.Sale, orm.UUID] - crudSellerService orm.CRUDService[models.Seller, orm.UUID] - crudCountryService orm.CRUDService[models.Country, orm.UUID] - crudCityService orm.CRUDService[models.City, orm.UUID] - crudEmployeeService orm.CRUDService[models.Employee, orm.UUID] - crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID] - crudPhoneService orm.CRUDService[models.Phone, orm.UIntID] - crudChildService orm.CRUDService[models.Child, orm.UUID] + crudSaleService orm.CRUDService[models.Sale, model.UUID] + crudSellerService orm.CRUDService[models.Seller, model.UUID] + crudCountryService orm.CRUDService[models.Country, model.UUID] + crudCityService orm.CRUDService[models.City, model.UUID] + crudEmployeeService orm.CRUDService[models.Employee, model.UUID] + crudBicycleService orm.CRUDService[models.Bicycle, model.UUID] + crudPhoneService orm.CRUDService[models.Phone, model.UIntID] + crudChildService orm.CRUDService[models.Child, model.UUID] } func NewJoinConditionsIntTestSuite( db *gorm.DB, - crudSaleService orm.CRUDService[models.Sale, orm.UUID], - crudSellerService orm.CRUDService[models.Seller, orm.UUID], - crudCountryService orm.CRUDService[models.Country, orm.UUID], - crudCityService orm.CRUDService[models.City, orm.UUID], - crudEmployeeService orm.CRUDService[models.Employee, orm.UUID], - crudBicycleService orm.CRUDService[models.Bicycle, orm.UUID], - crudPhoneService orm.CRUDService[models.Phone, orm.UIntID], - crudChildService orm.CRUDService[models.Child, orm.UUID], + crudSaleService orm.CRUDService[models.Sale, model.UUID], + crudSellerService orm.CRUDService[models.Seller, model.UUID], + crudCountryService orm.CRUDService[models.Country, model.UUID], + crudCityService orm.CRUDService[models.City, model.UUID], + crudEmployeeService orm.CRUDService[models.Employee, model.UUID], + crudBicycleService orm.CRUDService[models.Bicycle, model.UUID], + crudPhoneService orm.CRUDService[models.Phone, model.UIntID], + crudChildService orm.CRUDService[models.Child, model.UUID], ) *JoinConditionsIntTestSuite { return &JoinConditionsIntTestSuite{ CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ @@ -407,7 +409,7 @@ func (ts *JoinConditionsIntTestSuite) TestJoinWithEmptyContainerConditionReturns orm.Not[models.Product](), ), ) - ts.ErrorIs(err, orm.ErrEmptyConditions) + ts.ErrorIs(err, errors.ErrEmptyConditions) ts.ErrorContains(err, "connector: Not; model: models.Product") } @@ -461,7 +463,7 @@ func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorWithNotJoinedModelRetur _, err := ts.crudChildService.Query( conditions.ChildId(dynamic.Eq(conditions.ParentParentIdField)), ) - ts.ErrorIs(err, orm.ErrFieldModelNotConcerned) + ts.ErrorIs(err, errors.ErrFieldModelNotConcerned) ts.ErrorContains(err, "not concerned model: models.ParentParent; operator: Eq; model: models.Child, field: ID") } @@ -475,7 +477,7 @@ func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorJoinMoreThanOnceWithout ), conditions.ChildId(dynamic.Eq(conditions.ParentParentIdField)), ) - ts.ErrorIs(err, orm.ErrJoinMustBeSelected) + ts.ErrorIs(err, errors.ErrJoinMustBeSelected) ts.ErrorContains(err, "joined multiple times model: models.ParentParent; operator: Eq; model: models.Child, field: ID") } @@ -515,6 +517,6 @@ func (ts *JoinConditionsIntTestSuite) TestDynamicOperatorJoinMoreThanOnceWithout dynamic.Between(conditions.ParentParentIdField, conditions.ParentParentIdField), ), ) - ts.ErrorIs(err, orm.ErrJoinMustBeSelected) + ts.ErrorIs(err, errors.ErrJoinMustBeSelected) ts.ErrorContains(err, "joined multiple times model: models.ParentParent; operator: Between; model: models.Child, field: ID") } diff --git a/testintegration/models/badaas-orm.go b/testintegration/models/badaas-orm.go index d71a13c6..4492c23b 100644 --- a/testintegration/models/badaas-orm.go +++ b/testintegration/models/badaas-orm.go @@ -1,47 +1,47 @@ // Code generated by badaas-cli v0.0.0, DO NOT EDIT. package models -import orm "github.com/ditrit/badaas/orm" +import preload "github.com/ditrit/badaas/orm/preload" func (m Bicycle) GetOwner() (*Person, error) { - return orm.VerifyStructLoaded[Person](&m.Owner) + return preload.VerifyStructLoaded[Person](&m.Owner) } func (m Child) GetParent1() (*Parent1, error) { - return orm.VerifyStructLoaded[Parent1](&m.Parent1) + return preload.VerifyStructLoaded[Parent1](&m.Parent1) } func (m Child) GetParent2() (*Parent2, error) { - return orm.VerifyStructLoaded[Parent2](&m.Parent2) + return preload.VerifyStructLoaded[Parent2](&m.Parent2) } func (m City) GetCountry() (*Country, error) { - return orm.VerifyPointerWithIDLoaded[Country](m.CountryID, m.Country) + return preload.VerifyPointerWithIDLoaded[Country](m.CountryID, m.Country) } func (m Company) GetSellers() ([]Seller, error) { - return orm.VerifyCollectionLoaded[Seller](m.Sellers) + return preload.VerifyCollectionLoaded[Seller](m.Sellers) } func (m Country) GetCapital() (*City, error) { - return orm.VerifyStructLoaded[City](&m.Capital) + return preload.VerifyStructLoaded[City](&m.Capital) } func (m Employee) GetBoss() (*Employee, error) { - return orm.VerifyPointerLoaded[Employee](m.BossID, m.Boss) + return preload.VerifyPointerLoaded[Employee](m.BossID, m.Boss) } func (m Parent1) GetParentParent() (*ParentParent, error) { - return orm.VerifyStructLoaded[ParentParent](&m.ParentParent) + return preload.VerifyStructLoaded[ParentParent](&m.ParentParent) } func (m Parent2) GetParentParent() (*ParentParent, error) { - return orm.VerifyStructLoaded[ParentParent](&m.ParentParent) + return preload.VerifyStructLoaded[ParentParent](&m.ParentParent) } func (m Phone) GetBrand() (*Brand, error) { - return orm.VerifyStructLoaded[Brand](&m.Brand) + return preload.VerifyStructLoaded[Brand](&m.Brand) } func (m Sale) GetProduct() (*Product, error) { - return orm.VerifyStructLoaded[Product](&m.Product) + return preload.VerifyStructLoaded[Product](&m.Product) } func (m Sale) GetSeller() (*Seller, error) { - return orm.VerifyPointerLoaded[Seller](m.SellerID, m.Seller) + return preload.VerifyPointerLoaded[Seller](m.SellerID, m.Seller) } func (m Seller) GetCompany() (*Company, error) { - return orm.VerifyPointerLoaded[Company](m.CompanyID, m.Company) + return preload.VerifyPointerLoaded[Company](m.CompanyID, m.Company) } func (m Seller) GetUniversity() (*University, error) { - return orm.VerifyPointerLoaded[University](m.UniversityID, m.University) + return preload.VerifyPointerLoaded[University](m.UniversityID, m.University) } diff --git a/testintegration/models/models.go b/testintegration/models/models.go index 9cd06845..fa5694c8 100644 --- a/testintegration/models/models.go +++ b/testintegration/models/models.go @@ -6,15 +6,15 @@ import ( "fmt" "strings" - "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/model" ) type Employee struct { - orm.UUIDModel + model.UUIDModel Name string Boss *Employee // Self-Referential Has One (Employee 0..* -> 0..1 Employee) - BossID *orm.UUID + BossID *model.UUID } func (m Employee) Equal(other Employee) bool { @@ -22,7 +22,7 @@ func (m Employee) Equal(other Employee) bool { } type Company struct { - orm.UUIDModel + model.UUIDModel Name string Sellers *[]Seller // Company HasMany Sellers (Company 0..1 -> 0..* Seller) @@ -70,7 +70,7 @@ type ToBeGormEmbedded struct { } type Product struct { - orm.UUIDModel + model.UUIDModel String string `gorm:"column:string_something_else"` Int int @@ -90,7 +90,7 @@ func (m Product) Equal(other Product) bool { } type University struct { - orm.UUIDModel + model.UUIDModel Name string } @@ -100,29 +100,29 @@ func (m University) Equal(other University) bool { } type Seller struct { - orm.UUIDModel + model.UUIDModel Name string Company *Company - CompanyID *orm.UUID // Company HasMany Sellers (Company 0..1 -> 0..* Seller) + CompanyID *model.UUID // Company HasMany Sellers (Company 0..1 -> 0..* Seller) University *University - UniversityID *orm.UUID + UniversityID *model.UUID } type Sale struct { - orm.UUIDModel + model.UUIDModel Code int Description string // Sale belongsTo Product (Sale 0..* -> 1 Product) Product Product - ProductID orm.UUID + ProductID model.UUID // Sale belongsTo Seller (Sale 0..* -> 0..1 Seller) Seller *Seller - SellerID *orm.UUID + SellerID *model.UUID } func (m Sale) Equal(other Sale) bool { @@ -134,18 +134,18 @@ func (m Seller) Equal(other Seller) bool { } type Country struct { - orm.UUIDModel + model.UUIDModel Name string Capital City // Country HasOne City (Country 1 -> 1 City) } type City struct { - orm.UUIDModel + model.UUIDModel Name string Country *Country - CountryID orm.UUID // Country HasOne City (Country 1 -> 1 City) + CountryID model.UUID // Country HasOne City (Country 1 -> 1 City) } func (m Country) Equal(other Country) bool { @@ -157,7 +157,7 @@ func (m City) Equal(other City) bool { } type Person struct { - orm.UUIDModel + model.UUIDModel Name string `gorm:"unique;type:VARCHAR(255)"` } @@ -167,7 +167,7 @@ func (m Person) TableName() string { } type Bicycle struct { - orm.UUIDModel + model.UUIDModel Name string // Bicycle BelongsTo Person (Bicycle 0..* -> 1 Person) @@ -180,7 +180,7 @@ func (m Bicycle) Equal(other Bicycle) bool { } type Brand struct { - orm.UIntModel + model.UIntModel Name string } @@ -190,7 +190,7 @@ func (m Brand) Equal(other Brand) bool { } type Phone struct { - orm.UIntModel + model.UIntModel Name string // Phone belongsTo Brand (Phone 0..* -> 1 Brand) @@ -203,7 +203,7 @@ func (m Phone) Equal(other Phone) bool { } type ParentParent struct { - orm.UUIDModel + model.UUIDModel Name string Number int @@ -214,10 +214,10 @@ func (m ParentParent) Equal(other ParentParent) bool { } type Parent1 struct { - orm.UUIDModel + model.UUIDModel ParentParent ParentParent - ParentParentID orm.UUID + ParentParentID model.UUID } func (m Parent1) Equal(other Parent1) bool { @@ -225,10 +225,10 @@ func (m Parent1) Equal(other Parent1) bool { } type Parent2 struct { - orm.UUIDModel + model.UUIDModel ParentParent ParentParent - ParentParentID orm.UUID + ParentParentID model.UUID } func (m Parent2) Equal(other Parent2) bool { @@ -236,16 +236,16 @@ func (m Parent2) Equal(other Parent2) bool { } type Child struct { - orm.UUIDModel + model.UUIDModel Name string Number int Parent1 Parent1 - Parent1ID orm.UUID + Parent1ID model.UUID Parent2 Parent2 - Parent2ID orm.UUID + Parent2ID model.UUID } func (m Child) Equal(other Child) bool { diff --git a/testintegration/operators_test.go b/testintegration/operators_test.go index d8aaff56..9ce80ba1 100644 --- a/testintegration/operators_test.go +++ b/testintegration/operators_test.go @@ -8,6 +8,7 @@ import ( "github.com/ditrit/badaas/orm" "github.com/ditrit/badaas/orm/dynamic" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/orm/unsafe" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" @@ -15,12 +16,12 @@ import ( type OperatorsIntTestSuite struct { CRUDServiceCommonIntTestSuite - crudProductService orm.CRUDService[models.Product, orm.UUID] + crudProductService orm.CRUDService[models.Product, model.UUID] } func NewOperatorsIntTestSuite( db *gorm.DB, - crudProductService orm.CRUDService[models.Product, orm.UUID], + crudProductService orm.CRUDService[models.Product, model.UUID], ) *OperatorsIntTestSuite { return &OperatorsIntTestSuite{ CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ diff --git a/testintegration/preload_conditions_test.go b/testintegration/preload_conditions_test.go index ee294c04..b186854b 100644 --- a/testintegration/preload_conditions_test.go +++ b/testintegration/preload_conditions_test.go @@ -8,6 +8,8 @@ import ( "gotest.tools/assert" "github.com/ditrit/badaas/orm" + badaasORMErrors "github.com/ditrit/badaas/orm/errors" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" "github.com/ditrit/badaas/utils" @@ -15,26 +17,26 @@ import ( type PreloadConditionsIntTestSuite struct { CRUDServiceCommonIntTestSuite - crudSaleService orm.CRUDService[models.Sale, orm.UUID] - crudCompanyService orm.CRUDService[models.Company, orm.UUID] - crudSellerService orm.CRUDService[models.Seller, orm.UUID] - crudCountryService orm.CRUDService[models.Country, orm.UUID] - crudCityService orm.CRUDService[models.City, orm.UUID] - crudEmployeeService orm.CRUDService[models.Employee, orm.UUID] - crudPhoneService orm.CRUDService[models.Phone, orm.UIntID] - crudChildService orm.CRUDService[models.Child, orm.UUID] + crudSaleService orm.CRUDService[models.Sale, model.UUID] + crudCompanyService orm.CRUDService[models.Company, model.UUID] + crudSellerService orm.CRUDService[models.Seller, model.UUID] + crudCountryService orm.CRUDService[models.Country, model.UUID] + crudCityService orm.CRUDService[models.City, model.UUID] + crudEmployeeService orm.CRUDService[models.Employee, model.UUID] + crudPhoneService orm.CRUDService[models.Phone, model.UIntID] + crudChildService orm.CRUDService[models.Child, model.UUID] } func NewPreloadConditionsIntTestSuite( db *gorm.DB, - crudSaleService orm.CRUDService[models.Sale, orm.UUID], - crudCompanyService orm.CRUDService[models.Company, orm.UUID], - crudSellerService orm.CRUDService[models.Seller, orm.UUID], - crudCountryService orm.CRUDService[models.Country, orm.UUID], - crudCityService orm.CRUDService[models.City, orm.UUID], - crudEmployeeService orm.CRUDService[models.Employee, orm.UUID], - crudPhoneService orm.CRUDService[models.Phone, orm.UIntID], - crudChildService orm.CRUDService[models.Child, orm.UUID], + crudSaleService orm.CRUDService[models.Sale, model.UUID], + crudCompanyService orm.CRUDService[models.Company, model.UUID], + crudSellerService orm.CRUDService[models.Seller, model.UUID], + crudCountryService orm.CRUDService[models.Country, model.UUID], + crudCityService orm.CRUDService[models.City, model.UUID], + crudEmployeeService orm.CRUDService[models.Employee, model.UUID], + crudPhoneService orm.CRUDService[models.Phone, model.UIntID], + crudChildService orm.CRUDService[models.Child, model.UUID], ) *PreloadConditionsIntTestSuite { return &PreloadConditionsIntTestSuite{ CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ @@ -65,11 +67,11 @@ func (ts *PreloadConditionsIntTestSuite) TestNoPreloadReturnsErrorOnGetRelation( ts.False(saleLoaded.Product.IsLoaded()) _, err = saleLoaded.GetProduct() - ts.ErrorIs(err, orm.ErrRelationNotLoaded) + ts.ErrorIs(err, badaasORMErrors.ErrRelationNotLoaded) ts.Nil(saleLoaded.Seller) // is nil but we cant determine why directly (not loaded or really null) _, err = saleLoaded.GetSeller() // GetSeller give us that information - ts.ErrorIs(err, orm.ErrRelationNotLoaded) + ts.ErrorIs(err, badaasORMErrors.ErrRelationNotLoaded) } func (ts *PreloadConditionsIntTestSuite) TestNoPreloadWhenItsNullKnowsItsReallyNull() { @@ -85,7 +87,7 @@ func (ts *PreloadConditionsIntTestSuite) TestNoPreloadWhenItsNullKnowsItsReallyN ts.False(saleLoaded.Product.IsLoaded()) _, err = saleLoaded.GetProduct() - ts.ErrorIs(err, orm.ErrRelationNotLoaded) + ts.ErrorIs(err, badaasORMErrors.ErrRelationNotLoaded) ts.Nil(saleLoaded.Seller) // is nil but we cant determine why directly (not loaded or really null) saleSeller, err := saleLoaded.GetSeller() // GetSeller give us that information @@ -224,7 +226,7 @@ func (ts *PreloadConditionsIntTestSuite) TestNoPreloadNullableAtSecondLevel() { // the not null one is not loaded sellerCompany, err := saleSeller.GetCompany() - return errors.Is(err, orm.ErrRelationNotLoaded) && sellerCompany == nil + return errors.Is(err, badaasORMErrors.ErrRelationNotLoaded) && sellerCompany == nil })) ts.True(pie.Any(entities, func(sale *models.Sale) bool { saleSeller, err := sale.GetSeller() @@ -372,7 +374,7 @@ func (ts *PreloadConditionsIntTestSuite) TestNoPreloadOneToOne() { EqualList(&ts.Suite, []*models.City{&capital1}, entities) _, err = entities[0].GetCountry() - ts.ErrorIs(err, orm.ErrRelationNotLoaded) + ts.ErrorIs(err, badaasORMErrors.ErrRelationNotLoaded) } func (ts *PreloadConditionsIntTestSuite) TestPreloadOneToOneReversed() { @@ -754,7 +756,7 @@ func (ts *PreloadConditionsIntTestSuite) TestNoPreloadCollection() { EqualList(&ts.Suite, []*models.Company{company}, entities) _, err = entities[0].GetSellers() - ts.ErrorIs(err, orm.ErrRelationNotLoaded) + ts.ErrorIs(err, badaasORMErrors.ErrRelationNotLoaded) } func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributes() { @@ -870,11 +872,11 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributesWithF conditions.CompanyPreloadSellers( conditions.SellerUniversity( conditions.UniversityPreloadAttributes, - conditions.UniversityId(orm.Eq(orm.NilUUID)), + conditions.UniversityId(orm.Eq(model.NilUUID)), ), ), ) - ts.ErrorIs(err, orm.ErrOnlyPreloadsAllowed) + ts.ErrorIs(err, badaasORMErrors.ErrOnlyPreloadsAllowed) ts.ErrorContains(err, "model: models.Company, field: Sellers") } @@ -884,6 +886,6 @@ func (ts *PreloadConditionsIntTestSuite) TestPreloadListAndNestedAttributesWitho conditions.SellerUniversity(), ), ) - ts.ErrorIs(err, orm.ErrOnlyPreloadsAllowed) + ts.ErrorIs(err, badaasORMErrors.ErrOnlyPreloadsAllowed) ts.ErrorContains(err, "model: models.Company, field: Sellers") } diff --git a/testintegration/where_conditions_test.go b/testintegration/where_conditions_test.go index e3cacfe9..f6f839f3 100644 --- a/testintegration/where_conditions_test.go +++ b/testintegration/where_conditions_test.go @@ -5,6 +5,8 @@ import ( "gotest.tools/assert" "github.com/ditrit/badaas/orm" + "github.com/ditrit/badaas/orm/errors" + "github.com/ditrit/badaas/orm/model" "github.com/ditrit/badaas/orm/unsafe" "github.com/ditrit/badaas/testintegration/conditions" "github.com/ditrit/badaas/testintegration/models" @@ -12,16 +14,16 @@ import ( type WhereConditionsIntTestSuite struct { CRUDServiceCommonIntTestSuite - crudProductService orm.CRUDService[models.Product, orm.UUID] - crudSaleService orm.CRUDService[models.Sale, orm.UUID] - crudBrandService orm.CRUDService[models.Brand, orm.UIntID] + crudProductService orm.CRUDService[models.Product, model.UUID] + crudSaleService orm.CRUDService[models.Sale, model.UUID] + crudBrandService orm.CRUDService[models.Brand, model.UIntID] } func NewWhereConditionsIntTestSuite( db *gorm.DB, - crudProductService orm.CRUDService[models.Product, orm.UUID], - crudSaleService orm.CRUDService[models.Sale, orm.UUID], - crudBrandService orm.CRUDService[models.Brand, orm.UIntID], + crudProductService orm.CRUDService[models.Product, model.UUID], + crudSaleService orm.CRUDService[models.Sale, model.UUID], + crudBrandService orm.CRUDService[models.Brand, model.UIntID], ) *WhereConditionsIntTestSuite { return &WhereConditionsIntTestSuite{ CRUDServiceCommonIntTestSuite: CRUDServiceCommonIntTestSuite{ @@ -36,14 +38,14 @@ func NewWhereConditionsIntTestSuite( // ------------------------- GetByID -------------------------------- func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsErrorIfNotEntityCreated() { - _, err := ts.crudProductService.GetByID(orm.NilUUID) + _, err := ts.crudProductService.GetByID(model.NilUUID) ts.Error(err, gorm.ErrRecordNotFound) } func (ts *WhereConditionsIntTestSuite) TestGetByIDReturnsErrorIfNotEntityMatch() { ts.createProduct("", 0, 0, false, nil) - _, err := ts.crudProductService.GetByID(orm.NewUUID()) + _, err := ts.crudProductService.GetByID(model.NewUUID()) ts.Error(err, gorm.ErrRecordNotFound) } @@ -410,7 +412,7 @@ func (ts *WhereConditionsIntTestSuite) TestConditionOfRelationTypeOptionalByNil( ts.createSale(0, product2, seller2) entities, err := ts.crudSaleService.Query( - conditions.SaleSellerId(orm.IsNull[orm.UUID]()), + conditions.SaleSellerId(orm.IsNull[model.UUID]()), ) ts.Nil(err) @@ -566,5 +568,5 @@ func (ts *WhereConditionsIntTestSuite) TestEmptyContainerConditionReturnsError() _, err := ts.crudProductService.Query( orm.Not[models.Product](), ) - ts.ErrorIs(err, orm.ErrEmptyConditions) + ts.ErrorIs(err, errors.ErrEmptyConditions) } From 4d51366b570d83cc3f271be5737f7183d4af9c9c Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 18:09:45 +0200 Subject: [PATCH 83/90] add readthedocs files --- .dockerignore | 3 +- .gitignore | 5 ++- .readthedocs.yml | 25 ++++++++++++++ docs/Makefile | 29 ++++++++++++++++ docs/README.rst | 59 ++++++++++++++++++++++++++++++++ docs/conf.py | 40 ++++++++++++++++++++++ docs/make.bat | 35 +++++++++++++++++++ docs/requirements.in | 3 ++ docs/requirements.txt | 73 ++++++++++++++++++++++++++++++++++++++++ sonar-project.properties | 2 +- 10 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 .readthedocs.yml create mode 100644 docs/Makefile create mode 100644 docs/README.rst create mode 100644 docs/conf.py create mode 100644 docs/make.bat create mode 100644 docs/requirements.in create mode 100644 docs/requirements.txt diff --git a/.dockerignore b/.dockerignore index 3eb84618..8ed4299e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,4 +21,5 @@ test_e2e/ !test_e2e/go.sum mocks/ docker/ -**/*_test.go \ No newline at end of file +**/*_test.go +docs/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4210c0fd..8b8068e9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,7 @@ node* .vscode # binary output -badaas \ No newline at end of file +badaas + +# Sphinx documentation +docs/_build/ \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..7dcd5ee7 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,25 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# If using Sphinx, optionally build your docs in additional formats such as PDF +formats: + - pdf + - epub + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: docs/requirements.txt \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..6283bf6c --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,29 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build +PYTHON = python3 + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +htmlview: html + @ls + $(PYTHON) -c "import webbrowser; from pathlib import Path; \ + webbrowser.open(Path('_build/html/index.html').resolve().as_uri())" + +watch: htmlview + watchmedo shell-command -p '*.rst' -c 'make html' -R -D + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.rst b/docs/README.rst new file mode 100644 index 00000000..75d490a5 --- /dev/null +++ b/docs/README.rst @@ -0,0 +1,59 @@ +:orphan: + +====================================== +BaDaaS documentation quick start guide +====================================== + +This file provides a quick guide on how to compile the BaDaaS documentation. + + +Setup the environment +--------------------- + +To compile the documentation you need Sphinx Python library. To install it +and all its dependencies run the following command from this dir + +:: + + pip install -r requirements.txt + + +Compile the documentation +------------------------- + +To compile the documentation (to classic HTML output) run the following command +from this dir:: + + make html + +Documentation will be generated (in HTML format) inside the ``_build/html`` dir. + + +View the documentation +---------------------- + +To view the documentation run the following command:: + + make htmlview + +This command will fire up your default browser and open the main page of your +(previously generated) HTML documentation. + + +Start over +---------- + +To clean up all generated documentation files and start from scratch run:: + + make clean + +Keep in mind that this command won't touch any documentation source files. + + +Recreating documentation on the fly +----------------------------------- + +There is a way to recreate the doc automatically when you make changes, you +need to install watchdog (``pip install watchdog``) and then use:: + + make watch diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..86279d51 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,40 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'BaDaaS' +copyright = '2023, DitRit' +author = 'DitRit' +release = '0.0.1' +version = '0.0.1' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "myst_parser", + 'sphinx.ext.duration', + 'sphinx.ext.doctest', + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + "sphinx.ext.autosectionlabel", +] + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# Make sure the target is unique +autosectionlabel_prefix_document = True + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] + +# -- Options for EPUB output +epub_show_urls = 'footnote' diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..32bb2452 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 00000000..4cb3091f --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1,3 @@ +Sphinx +sphinx_rtd_theme +myst-parser \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..4f36d62c --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,73 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile docs/requirements.in +# +alabaster==0.7.13 + # via sphinx +babel==2.12.1 + # via sphinx +certifi==2023.5.7 + # via requests +charset-normalizer==3.2.0 + # via requests +docutils==0.18.1 + # via + # myst-parser + # sphinx + # sphinx-rtd-theme +idna==3.4 + # via requests +imagesize==1.4.1 + # via sphinx +jinja2==3.1.2 + # via + # myst-parser + # sphinx +markdown-it-py==3.0.0 + # via + # mdit-py-plugins + # myst-parser +markupsafe==2.1.3 + # via jinja2 +mdit-py-plugins==0.4.0 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +myst-parser==2.0.0 + # via -r docs/requirements.in +packaging==23.1 + # via sphinx +pygments==2.15.1 + # via sphinx +pyyaml==6.0 + # via myst-parser +requests==2.31.0 + # via sphinx +snowballstemmer==2.2.0 + # via sphinx +sphinx==6.2.1 + # via + # -r docs/requirements.in + # myst-parser + # sphinx-rtd-theme + # sphinxcontrib-jquery +sphinx-rtd-theme==1.2.2 + # via -r docs/requirements.in +sphinxcontrib-applehelp==1.0.4 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==2.0.1 + # via sphinx +sphinxcontrib-jquery==4.1 + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.5 + # via sphinx +urllib3==2.0.3 + # via requests diff --git a/sonar-project.properties b/sonar-project.properties index aac1e397..f6bf3638 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,7 +3,7 @@ sonar.organization=ditrit sonar.projectName=badaas sonar.host.url=https://sonarcloud.io sonar.sources=. -sonar.exclusions=**/*_test.go,mocks/***,vendor/*** +sonar.exclusions=**/*_test.go,mocks/***,vendor/***,docs/*** sonar.tests=. sonar.test.inclusions=**/*_test.go,testintegration/***,test_e2e/*** sonar.go.coverage.reportPaths=*.out From ab7cca758544b008a203afc2f69cbcefa13d3b40 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 18:05:31 +0200 Subject: [PATCH 84/90] add golangci-lint to dependencies --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 9543bfda..b567669b 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ install_dependencies: go install gotest.tools/gotestsum@latest go install github.com/vektra/mockery/v2@v2.20.0 go install github.com/ditrit/badaas-cli@latest + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest lint: golangci-lint run From 19bd5173c42f8178c26b5f66acde81b82b57e714 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 18:06:52 +0200 Subject: [PATCH 85/90] move validators to utils --- services/userservice/userservice.go | 4 ++-- {validators => utils/validators}/email.go | 2 +- {validators => utils/validators}/email_test.go | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) rename {validators => utils/validators}/email.go (92%) rename {validators => utils/validators}/email_test.go (53%) diff --git a/services/userservice/userservice.go b/services/userservice/userservice.go index 0d67bc68..01105f2f 100644 --- a/services/userservice/userservice.go +++ b/services/userservice/userservice.go @@ -12,7 +12,7 @@ import ( "github.com/ditrit/badaas/persistence/models" "github.com/ditrit/badaas/persistence/models/dto" "github.com/ditrit/badaas/services/auth/protocols/basicauth" - validator "github.com/ditrit/badaas/validators" + "github.com/ditrit/badaas/utils/validators" ) // UserService provide functions related to Users @@ -48,7 +48,7 @@ func NewUserService( // Create a new user func (userService *userServiceImpl) NewUser(username, email, password string) (*models.User, error) { - sanitizedEmail, err := validator.ValidEmail(email) + sanitizedEmail, err := validators.ValidEmail(email) if err != nil { return nil, fmt.Errorf("the provided email is not valid") } diff --git a/validators/email.go b/utils/validators/email.go similarity index 92% rename from validators/email.go rename to utils/validators/email.go index b9984c2b..1a10ab72 100644 --- a/validators/email.go +++ b/utils/validators/email.go @@ -1,4 +1,4 @@ -package validator +package validators import "net/mail" diff --git a/validators/email_test.go b/utils/validators/email_test.go similarity index 53% rename from validators/email_test.go rename to utils/validators/email_test.go index ecd84f90..600741ed 100644 --- a/validators/email_test.go +++ b/utils/validators/email_test.go @@ -1,31 +1,31 @@ -package validator_test +package validators_test import ( "testing" "github.com/stretchr/testify/assert" - validator "github.com/ditrit/badaas/validators" + "github.com/ditrit/badaas/utils/validators" ) func TestValidEmail(t *testing.T) { - mail, err := validator.ValidEmail("bob.bobemail.com") + mail, err := validators.ValidEmail("bob.bobemail.com") assert.Error(t, err) assert.Equal(t, "", mail) - mail, err = validator.ValidEmail("bob.bob@") + mail, err = validators.ValidEmail("bob.bob@") assert.Error(t, err) assert.Equal(t, "", mail) - mail, err = validator.ValidEmail("bob.bob@email.com") + mail, err = validators.ValidEmail("bob.bob@email.com") assert.NoError(t, err) assert.Equal(t, "bob.bob@email.com", mail) - mail, err = validator.ValidEmail("Gopher ") + mail, err = validators.ValidEmail("Gopher ") assert.NoError(t, err) assert.Equal(t, "from@example.com", mail) - mail, err = validator.ValidEmail("bob.bob%@email.com") + mail, err = validators.ValidEmail("bob.bob%@email.com") assert.NoError(t, err) assert.Equal(t, "bob.bob%@email.com", mail) } From 2088aa2829673e4ec0a1e545d65ea19421861fb9 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 18:15:20 +0200 Subject: [PATCH 86/90] move badaas docs to readthedocs --- .gitignore | 1 + README.md | 165 +--------------------------- configuration.md | 152 -------------------------- docs/badaas/configuration.rst | 188 ++++++++++++++++++++++++++++++++ docs/badaas/functionalities.rst | 37 +++++++ docs/badaas/quickstart.rst | 102 +++++++++++++++++ docs/index.rst | 40 +++++++ 7 files changed, 374 insertions(+), 311 deletions(-) delete mode 100644 configuration.md create mode 100644 docs/badaas/configuration.rst create mode 100644 docs/badaas/functionalities.rst create mode 100644 docs/badaas/quickstart.rst create mode 100644 docs/index.rst diff --git a/.gitignore b/.gitignore index 8b8068e9..d0e1c4ea 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ node* # binary output badaas +!docs/badaas # Sphinx documentation docs/_build/ \ No newline at end of file diff --git a/README.md b/README.md index 2bdcffe4..4a2d31a0 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,9 @@ # BADAAS: Backend And Distribution As A Service -Badaas enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). +BaDaaS enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). > **Warning** -> BaDaaS is still under development. Each of its components can have a different state of evolution that you can consult in [Features and components](#features-and-components) - -- [Features and components](#features-and-components) -- [Quickstart](#quickstart) - - [Example](#example) - - [Step-by-step instructions](#step-by-step-instructions) -- [Configuration](#configuration) -- [Contributing](#contributing) -- [License](#license) +> BaDaaS is still under development and each of its components can have a different state of evolution ## Features and components @@ -20,159 +12,14 @@ Badaas provides several key features, each provided by a component that can be u - **Authentication**(unstable): Badaas can authenticate users using its internal authentication scheme or externally by using protocols such as OIDC, SAML, Oauth2... - **Authorization**(wip_unstable): On resource access, Badaas will check if the user is authorized using a RBAC model. - **Distribution**(todo): Badaas is built to run in clusters by default. Communications between nodes are TLS encrypted using [shoset](https://github.com/ditrit/shoset). -- **Persistence**(wip_unstable): Applicative objects are persisted as well as user files. Those resources are shared across the clusters to increase resiliency. To achieve this, BaDaaS uses the [BaDaaS ORM](https://github.com/ditrit/badaas/orm) component. +- **Persistence**(wip_unstable): Applicative objects are persisted as well as user files. Those resources are shared across the clusters to increase resiliency. To achieve this, BaDaaS uses the [badaas-orm](https://github.com/ditrit/badaas/orm) component. - **Querying Resources**(unstable): Resources are accessible via a REST API. -- **Posix compliant**(stable): Badaas strives towards being a good unix citizen and respecting commonly accepted norms. (see [Configuration](#configuration)) +- **Posix compliant**(stable): Badaas strives towards being a good unix citizen and respecting commonly accepted norms. - **Advanced logs management**(todo): Badaas provides an interface to interact with the logs produced by the clusters. Logs are formatted in json by default. -## Quickstart - -### Example - -To quickly get badaas up and running, you can head to the [example](https://github.com/ditrit/badaas-example). This example will help you to see how to use badaas and as a template to start your own project - -### Step-by-step instructions - -Once you have started your project with `go init`, you must add the dependency to badaas: - -```bash -go get -u github.com/ditrit/badaas -``` - -Then, you can use the following structure to configure and start your application - -```go -func main() { - badaas.BaDaaS.AddModules( - // add badaas modules - ).Provide( - // provide constructors - ).Invoke( - // invoke functions - ).Start() -} -``` - -#### Config badaas functionalities - -You are free to choose which badaas functionalities you wish to use. To add them, you must add the corresponding module, for example: - -```go -func main() { - badaas.BaDaaS.AddModules( - badaas.InfoModule, - badaas.AuthModule, - ).Provide( - NewAPIVersion, - ).Start() -} - -func NewAPIVersion() *semver.Version { - return semver.MustParse("0.0.0-unreleased") -} -``` - -#### Add your own functionalities - -With the "Provide" and "Invoke" functions you will be able to add your own functionalities to the application. For example, to add a route you must first provide the constructor of the controller and then invoke the function that adds this route: - -```go -func main() { - badaas.BaDaaS.Provide( - NewHelloController, - ).Invoke( - AddExampleRoutes, - ).Start() -} - -type HelloController interface { - SayHello(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) -} - -type helloControllerImpl struct{} - -func NewHelloController() HelloController { - return &helloControllerImpl{} -} - -func (*helloControllerImpl) SayHello(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { - return "hello world", nil -} - -func AddExampleRoutes( - router *mux.Router, - jsonController middlewares.JSONController, - helloController HelloController, -) { - router.HandleFunc( - "/hello", - jsonController.Wrap(helloController.SayHello), - ).Methods("GET") -} -``` - -#### Run it - -Once you have defined the functionalities of your project (the `/hello` route in this case), you can run the application using the steps described in the example README.md - -### Provided functionalities - -#### InfoModule - -`InfoModule` adds the path `/info`, where the api version will be answered. To set the version you want to be responded you must provide a function that returns it: - -```go -func main() { - badaas.BaDaaS.AddModules( - badaas.InfoModule, - ).Provide( - NewAPIVersion, - ).Start() -} - -func NewAPIVersion() *semver.Version { - return semver.MustParse("0.0.0-unreleased") -} -``` - -#### AuthModule - -`AuthModule` adds `/login` and `/logout`, which allow us to add authentication to our application in a simple way: - -```go -func main() { - badaas.BaDaaS.AddModules( - badaas.AuthModule, - ).Start() -} -``` - -### Configuration - -Badaas use [verdeter](https://github.com/ditrit/verdeter) to manage it's configuration, so Badaas is POSIX compliant by default. - -Badgen automatically generates a default configuration in `badaas/config/badaas.yml`, but you are free to modify it if you need to. - -This can be done using environment variables, configuration files or CLI flags. -CLI flags take priority on the environment variables and the environment variables take priority on the content of the configuration file. - -As an example we will define the `database.port` configuration key using the 3 methods: - -- Using a CLI flag: `--database.port=1222` -- Using an environment variable: `export BADAAS_DATABASE_PORT=1222` (*dots are replaced by underscores*) -- Using a config file (in YAML here): - - ```yml - # /etc/badaas/badaas.yml - database: - port: 1222 - ``` - -The config file can be placed at `/etc/badaas/badaas.yml` or `$HOME/.config/badaas/badaas.yml` or in the same folder as the badaas binary `./badaas.yml`. - -If needed, the location can be overridden using the config key `config_path`. +## Documentation -***For a full overview of the configuration keys: please head to the [configuration documentation](./configuration.md).*** + ## Contributing diff --git a/configuration.md b/configuration.md deleted file mode 100644 index 20b7652a..00000000 --- a/configuration.md +++ /dev/null @@ -1,152 +0,0 @@ -# Configuration - -To see a complete example of a working config file: head to [`badaas.example.yml`](./badaas.example.yml). - -As said in the README: - -> Badaas can be configured using environment variables, CLI flags or a configuration file. -> CLI flags take priority on the environment variables and the environment variables take priority on the content of the configuration file. - -In this documentation file, we will mainly focus our attention on config files but we won't forget that we can use environment variables and CLI flags to change Badaas' config. - -The config file can be formatted in any syntax that [`viper`](https://github.com/spf13/viper) supports but we will only use YAML syntax in our docs. - -- [Configuration](#configuration) - - [Database](#database) - - [Logger](#logger) - - [HTTP Server](#http-server) - - [Default values](#default-values) - - [Session management](#session-management) - -## Database - -We use CockroachDB as a database. It is Postgres compatible, so the information we need to provide will not be a surprise to Postgres users. - -```yml -# The settings for the database. -database: - # The host of the database server. - # (mandatory) - host: e2e-db-1 - - # The port of the database server. - # (mandatory) - port: 26257 - - # The name of the database to use. - name: badaas_db - - # The sslmode of the connection to the database server. - # (mandatory) - sslmode: disable - - # The username of the account on the database server. - # (mandatory) - username: root - - # The password of the account on the database server. - # (mandatory) - password: postgres - - # The settings for the initialization of the database server. - init: - # Number of time badaas will try to establish a connection to the database server. - # default (10) - retry: 10 - - # Waiting time between connection, in seconds. - # default (5) - retryTime: 5 -``` - -Please note that the init section `init:` is not mandatory. Badaas is suited with a simple but effective retry mechanism that will retry `database.init.retry` time to establish a connection with the database. Badaas will wait `database.init.retryTime` seconds between each retry. - -## Logger - -Badaas use a structured logger that can output json logs in production and user adapted logs for debug using the `logger.mode` key. - -Badaas offers the possibility to change the log message of the Middleware Logger but provides a sane default. It is formatted using the Jinja syntax. The values available are `method`, `url` and `protocol`. - -```yml -# The settings for the logger. -logger: - # Either `dev` or `prod` - # default (`prod`) - mode: prod - request: - # Change the log emitted when badaas receives a request on a valid endpoint. - template: "Receive {{method}} request on {{url}}" -``` - -## HTTP Server - -You can change the host Badaas will bind to, the port and the timeout in seconds. - -Additionally you can change the number of elements returned by default for a paginated response. - -```yml -# The settings for the http server. -server: - # The address to bind badaas to. - # default ("0.0.0.0") - host: "" - - # The port badaas should use. - # default (8000) - port: 8000 - - # The maximum timeout for the http server in seconds. - # default (15) - timeout: 15 - - # The settings for the pagination. - pagination: - page: - # The maximum number of record per page - # default (100) - max: 100 -``` - -## Default values - -The section allow to change some settings for the first run. - -```yml -# The settings for the first run. -default: - # The admin settings for the first run - admin: - # The admin password for the first run. Won't change is the admin user already exists. - password: admin -``` - -## Session management - -You can change the way the session service handle user sessions. -Session are extended if the user made a request to badaas in the "roll duration". The session duration and the refresh interval of the cache can be changed. They contains some good defaults. - -Please see the diagram below to see what is the roll duration relative to the session duration. - -```txt - | session duration | - |<----------------------------------------->| - ----|-------------------------|-----------------|----> time - | | | - |<--------------->| - roll duration -``` - -```yml -# The settings for session service -# This section contains some good defaults, don't change those value unless you need to. -session: - # The duration of a user session, in seconds - # Default (14400) equal to 4 hours - duration: 14400 - # The refresh interval in seconds. Badaas refresh it's internal session cache periodically. - # Default (30) - pullInterval: 30 - # The duration in which the user can renew it's session by making a request. - # Default (3600) equal to 1 hour - rollDuration: 3600 -``` diff --git a/docs/badaas/configuration.rst b/docs/badaas/configuration.rst new file mode 100644 index 00000000..ccf4d664 --- /dev/null +++ b/docs/badaas/configuration.rst @@ -0,0 +1,188 @@ +============================== +Configuration +============================== + +Methods +------------------------------- + +Badaas use `verdeter `_ to manage it's configuration, +so Badaas is POSIX compliant by default. + +Badaas-cli automatically generates a default configuration in `badaas/config/badaas.yml`, +but you are free to modify it if you need to. + +This can be done using environment variables, configuration files or CLI flags. +CLI flags take priority on the environment variables and the environment variables take +priority on the content of the configuration file. + +As an example we will define the `database.port` configuration key using the 3 methods: + +- Using a CLI flag: :code:`--database.port=1222` +- Using an environment variable: :code:`export BADAAS_DATABASE_PORT=1222` (*dots are replaced by underscores*) +- Using a config file (in YAML here) + +.. code-block:: yaml + + # /etc/badaas/badaas.yml + database: + port: 1222 + +The config file can be placed at `/etc/badaas/badaas.yml` or `$HOME/.config/badaas/badaas.yml` +or in the same folder as the badaas binary `./badaas.yml`. + +If needed, the location can be overridden using the config key `config_path`. + +Config file +---------------------------- + +In this section, we will focus our attention on config files but +we won't forget that we can use environment variables and CLI flags to change Badaas' config. + +The config file can be formatted in any syntax that +`viper `_ supports but we will only use YAML syntax in our docs. + +To see a complete example of a working config file: head to +`badaas.example.yml `_. + +Database +^^^^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: yaml + + # The settings for the database. + database: + # The host of the database server. + # (mandatory) + host: e2e-db-1 + + # The port of the database server. + # (mandatory) + port: 26257 + + # The name of the database to use. + name: badaas_db + + # The sslmode of the connection to the database server. + # (mandatory) + sslmode: disable + + # The username of the account on the database server. + # (mandatory) + username: root + + # The password of the account on the database server. + # (mandatory) + password: postgres + + # The settings for the initialization of the database server. + init: + # Number of time badaas will try to establish a connection to the database server. + # default (10) + retry: 10 + + # Waiting time between connection, in seconds. + # default (5) + retryTime: 5 + +Please note that the init section `init:` is not mandatory. +Badaas is suited with a simple but effective retry mechanism that will retry +`database.init.retry` time to establish a connection with the database. +Badaas will wait `database.init.retryTime` seconds between each retry. + +Logger +^^^^^^^^^^^^^^^^^^^^^^^^ + +Badaas use a structured logger that can output json logs in +production and user adapted logs for debug using the `logger.mode` key. + +Badaas offers the possibility to change the log message of the +Middleware Logger but provides a sane default. It is formatted using the Jinja syntax. +The values available are `method`, `url` and `protocol`. + +.. code-block:: yaml + + # The settings for the logger. + logger: + # Either `dev` or `prod` + # default (`prod`) + mode: prod + request: + # Change the log emitted when badaas receives a request on a valid endpoint. + template: "Receive {{method}} request on {{url}}" + +HTTP Server +^^^^^^^^^^^^^^^^^^^^^^^^ + +You can change the host Badaas will bind to, the port and the timeout in seconds. + +Additionally you can change the number of elements returned by default for a paginated response + +.. code-block:: yaml + + # The settings for the http server. + server: + # The address to bind badaas to. + # default ("0.0.0.0") + host: "" + + # The port badaas should use. + # default (8000) + port: 8000 + + # The maximum timeout for the http server in seconds. + # default (15) + timeout: 15 + + # The settings for the pagination. + pagination: + page: + # The maximum number of record per page + # default (100) + max: 100 + + +Default values +^^^^^^^^^^^^^^^^^^^^^^^^ + +The section allow to change some settings for the first run. + +.. code-block:: yaml + + # The settings for the first run. + default: + # The admin settings for the first run + admin: + # The admin password for the first run. Won't change is the admin user already exists. + password: admin + +Session management +^^^^^^^^^^^^^^^^^^^^^^^^ + +You can change the way the session service handle user sessions. +Session are extended if the user made a request to badaas in the "roll duration". +The session duration and the refresh interval of the cache can be changed. +They contains some good defaults. + +Please see the diagram below to see what is the roll duration relative to the session duration. +:: + + | session duration | + |<----------------------------------------->| + ----|-------------------------|-----------------|----> time + | | | + |<--------------->| + roll duration + +.. code-block:: yaml + + # The settings for session service + # This section contains some good defaults, don't change those value unless you need to. + session: + # The duration of a user session, in seconds + # Default (14400) equal to 4 hours + duration: 14400 + # The refresh interval in seconds. Badaas refresh it's internal session cache periodically. + # Default (30) + pullInterval: 30 + # The duration in which the user can renew it's session by making a request. + # Default (3600) equal to 1 hour + rollDuration: 3600 \ No newline at end of file diff --git a/docs/badaas/functionalities.rst b/docs/badaas/functionalities.rst new file mode 100644 index 00000000..8026b479 --- /dev/null +++ b/docs/badaas/functionalities.rst @@ -0,0 +1,37 @@ +============================== +Functionalities +============================== + +InfoModule +------------------------------- + +`InfoModule` adds the path `/info`, where the api version will be answered. +To set the version you want to be responded you must provide a function that returns it: + +.. code-block:: go + + func main() { + badaas.BaDaaS.AddModules( + badaas.InfoModule, + ).Provide( + NewAPIVersion, + ).Start() + } + + func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") + } + +AuthModule +------------------------------- + +`AuthModule` adds `/login` and `/logout`, which allow us to add authentication to our +application in a simple way: + +.. code-block:: go + + func main() { + badaas.BaDaaS.AddModules( + badaas.AuthModule, + ).Start() + } diff --git a/docs/badaas/quickstart.rst b/docs/badaas/quickstart.rst new file mode 100644 index 00000000..613760d1 --- /dev/null +++ b/docs/badaas/quickstart.rst @@ -0,0 +1,102 @@ +============================== +Quickstart +============================== + +To quickly get badaas up and running, you can head to the +`example `_. +By following its README.md, you will see how to use badaas and it will be util +as a template to start your own project. + +Step-by-step instructions +----------------------------------- + +Once you have started your project with `go init`, you must add the dependency to badaas: + +.. code-block:: bash + + go get -u github.com/ditrit/badaas + +Then, you can use the following structure to configure and start your application + +.. code-block:: go + + func main() { + badaas.BaDaaS.AddModules( + // add badaas modules + ).Provide( + // provide constructors + ).Invoke( + // invoke functions + ).Start() + } + +Config badaas functionalities +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You are free to choose which badaas functionalities you wish to use. +To add them, you must add the corresponding module, for example: + +.. code-block:: go + + func main() { + badaas.BaDaaS.AddModules( + badaas.InfoModule, + badaas.AuthModule, + ).Provide( + NewAPIVersion, + ).Start() + } + + func NewAPIVersion() *semver.Version { + return semver.MustParse("0.0.0-unreleased") + } + +Add your own functionalities +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +With the "Provide" and "Invoke" functions you will be able to add your own functionalities to the application. +For example, to add a route you must first provide the constructor of the controller and +then invoke the function that adds this route: + +.. code-block:: go + + func main() { + badaas.BaDaaS.Provide( + NewHelloController, + ).Invoke( + AddExampleRoutes, + ).Start() + } + + type HelloController interface { + SayHello(http.ResponseWriter, *http.Request) (any, httperrors.HTTPError) + } + + type helloControllerImpl struct{} + + func NewHelloController() HelloController { + return &helloControllerImpl{} + } + + func (*helloControllerImpl) SayHello(response http.ResponseWriter, r *http.Request) (any, httperrors.HTTPError) { + return "hello world", nil + } + + func AddExampleRoutes( + router *mux.Router, + jsonController middlewares.JSONController, + helloController HelloController, + ) { + router.HandleFunc( + "/hello", + jsonController.Wrap(helloController.SayHello), + ).Methods("GET") + } + +For details visit :doc:`functionalities`. + +Run it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Once you have defined the functionalities of your project (the `/hello` route in this case), +you can run the application using the steps described in the example README.md diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..0fb0cb98 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,40 @@ +============================== +Introduction +============================== + +Badaas enables the effortless construction of **distributed, resilient, +highly available and secure applications by design**, while ensuring very simple +deployment and management (NoOps). + +.. warning:: + BaDaaS is still under development and each of its components can have a different state of evolution + +Features and components +================================= + +Badaas provides several key features, +each provided by a component that can be used independently and has a different state of evolution: + +- **Authentication** (unstable): Badaas can authenticate users using its internal + authentication scheme or externally by using protocols such as OIDC, SAML, Oauth2... +- **Authorization** (wip_unstable): On resource access, Badaas will check if the user + is authorized using a RBAC model. +- **Distribution** (todo): Badaas is built to run in clusters by default. + Communications between nodes are TLS encrypted using `shoset `_. +- **Persistence** (wip_unstable): Applicative objects are persisted as well as user files. + Those resources are shared across the clusters to increase resiliency. +- **Querying Resources** (unstable): Resources are accessible via a REST API. +- **Posix compliant** (stable): Badaas strives towards being a good unix citizen and + respecting commonly accepted norms. (see :doc:`badaas/configuration`) +- **Advanced logs management** (todo): Badaas provides an interface to interact with + the logs produced by the clusters. Logs are formatted in json by default. + +Learn how to use BaDaaS following the :doc:`badaas/quickstart`. + +.. toctree:: + :caption: BaDaaS + + self + badaas/quickstart + badaas/functionalities + badaas/configuration From 22053890f6a3b786690a5d224ac1818ffac985d5 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 18:16:16 +0200 Subject: [PATCH 87/90] move badaas-orm docs to readthedocs --- docs/badaas-orm/advanced_query.rst | 164 ++++++++++++++++ docs/badaas-orm/concepts.rst | 188 +++++++++++++++++++ docs/badaas-orm/connecting_to_a_database.rst | 36 ++++ docs/badaas-orm/crud.rst | 58 ++++++ docs/badaas-orm/declaring_models.rst | 173 +++++++++++++++++ docs/badaas-orm/index.rst | 33 ++++ docs/badaas-orm/preloading.rst | 171 +++++++++++++++++ docs/badaas-orm/query.rst | 175 +++++++++++++++++ docs/index.rst | 13 ++ orm/README.md | 50 ++--- 10 files changed, 1024 insertions(+), 37 deletions(-) create mode 100644 docs/badaas-orm/advanced_query.rst create mode 100644 docs/badaas-orm/concepts.rst create mode 100644 docs/badaas-orm/connecting_to_a_database.rst create mode 100644 docs/badaas-orm/crud.rst create mode 100644 docs/badaas-orm/declaring_models.rst create mode 100644 docs/badaas-orm/index.rst create mode 100644 docs/badaas-orm/preloading.rst create mode 100644 docs/badaas-orm/query.rst diff --git a/docs/badaas-orm/advanced_query.rst b/docs/badaas-orm/advanced_query.rst new file mode 100644 index 00000000..8b399dc9 --- /dev/null +++ b/docs/badaas-orm/advanced_query.rst @@ -0,0 +1,164 @@ +============================== +Advanced query +============================== + +Dynamic operators +-------------------------------- + +In :doc:`/badaas-orm/query` we have seen how to use the operators +to make comparisons between the attributes of a model and static values such as a string, +a number, etc. But if we want to make comparisons between two or more attributes of +the same type we need to use the dynamic operators. +These, instead of a dynamic value, receive a FieldIdentifier, that is, +an object that identifies the attribute with which the operation is to be performed. + +These identifiers are also generated during the generation of conditions and +their name of these FieldIdentifiers will be Field where + is the model type and is the attribute name. + +For example we query all YourModels that has the same value in its String attribute that +its related Related's String attribute. + +.. code-block:: go + + type Related struct { + model.UUIDModel + + String string + } + + type YourModel struct { + model.UUIDModel + + String string + + Related Related + RelatedID model.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelRelated( + conditions.RelatedString( + dynamic.Eq(conditions.YourModelStringField), + ), + ), + ) + +**Attention**, when using dynamic operators the verification that the FieldIdentifier +is concerned by the query is performed at run time, returning an error otherwise. +For example: + +.. code-block:: go + + type Related struct { + model.UUIDModel + + String string + } + + type YourModel struct { + model.UUIDModel + + String string + + Related Related + RelatedID model.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelString( + dynamic.Eq(conditions.RelatedStringField), + ), + ) + +will respond orm.ErrFieldModelNotConcerned in err. + +All operators supported by badaas-orm that receive any value are available in their dynamic version at +. + +Select join +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In case the attribute to be used by the dynamic operator is present more +than once in the query, it will be necessary to select the join to be used, +to avoid getting the error orm.ErrJoinMustBeSelected. +To do this, you must use the SelectJoin method, as in the following example: + +.. code-block:: go + + type ParentParent struct { + model.UUIDModel + } + + type Parent1 struct { + model.UUIDModel + + ParentParent ParentParent + ParentParentID model.UUID + } + + type Parent2 struct { + model.UUIDModel + + ParentParent ParentParent + ParentParentID model.UUID + } + + type Child struct { + model.UUIDModel + + Parent1 Parent1 + Parent1ID model.UUID + + Parent2 Parent2 + Parent2ID model.UUID + } + + models, err := ts.crudChildService.Query( + conditions.ChildParent1( + conditions.Parent1ParentParent(), + ), + conditions.ChildParent2( + conditions.Parent2ParentParent(), + ), + conditions.ChildName( + // for the value 0 (conditions.ParentParentNameField), + // choose the first (0) join (made by conditions.ChildParent1()) + dynamic.Eq(conditions.ParentParentNameField).SelectJoin(0, 0), + ), + ) + +Unsafe operators +-------------------------------- + +In case you want to avoid the type validations performed by the operators, unsafe operators should be used. +Although their use is not recommended, this can be useful when the database +used allows operations between different types or when attributes of different +types map at the same time in the database (see ). + +If it is neither of these two cases, the use of an unsafe operator will result in +an error in the execution of the query that depends on the database used. + +All operators supported by badaas-orm that receive any value are available in their unsafe version at +. + +Unsafe conditions (raw SQL) +-------------------------------- + +In case you need to use operators that are not supported by badaas-orm +(please create an issue in our repository if you think we have forgotten any), +you can always run raw SQL with unsafe.NewCondition, as in the following example: + +.. code-block:: go + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelString( + unsafe.NewCondition[models.YourModel]("%s.name = NULL"), + ), + ) + +As you can see in the example, "%s" can be used in the raw SQL to be replaced +by the table name of the model to which the condition belongs. + +Of course, its use is not recommended because it can generate errors in the execution +of the query that will depend on the database used. \ No newline at end of file diff --git a/docs/badaas-orm/concepts.rst b/docs/badaas-orm/concepts.rst new file mode 100644 index 00000000..6962d628 --- /dev/null +++ b/docs/badaas-orm/concepts.rst @@ -0,0 +1,188 @@ +============================== +Concepts +============================== + +Model +------------------------------ + +A model is any object (struct) of go that you want to persist +in the database and on which you can perform queries. +For this, the struct must have an embedded badaas-orm base model. + +For details visit :ref:`badaas-orm/declaring_models:model declaration`. + +Base model +----------------------------- + +It is a struct that when embedded allows your structures to become BaDaaS models, +adding attributes ID, CreatedAt, UpdatedAt and DeletedAt attributes and the possibility to persist, +create conditions and perform queries on these structures. + +For details visit :ref:`badaas-orm/declaring_models:base models`. + +Model ID +----------------------------- + +The id is a unique identifier needed to persist a model in the database. +It can be a model.UIntID or a model.UUID, depending on the base model used. + +For details visit :ref:`badaas-orm/declaring_models:base models`. + +Auto Migration +---------------------------------------------------------- + +To persist the models it is necessary to migrate the database, +so that the structure of the tables corresponds to the definition of the model. +This migration is performed by gorm through the gormDB. + +For details visit :ref:`badaas-orm/connecting_to_a_database:migration`. + +GormDB +----------------------------- + +GormDB is a gorm.DB object that allows communication with the database. + +For details visit :ref:`badaas-orm/connecting_to_a_database:connection`. + +Condition +----------------------------- + +Conditions are the basis of the badaas-orm query system, every query is composed of a set of conditions. +Conditions belong to a particular model and there are 4 different types: +WhereConditions, ConnectionConditions, JoinConditions and PreloadConditions. + +For details visit :doc:`/badaas-orm/query`. + +WhereCondition +----------------------------- + +Type of condition that allows filters to be made on the model to which they belong +and an attribute of this model. These filters are performed through operators. + +For details visit :doc:`/badaas-orm/query`. + +ConnectionCondition +----------------------------- + +Type of condition that allows the use of logical operators +(and, or, or, not) between WhereConditions. + +For details visit :doc:`/badaas-orm/query`. + +JoinCondition +----------------------------- + +Condition type that allows to navigate relationships between models, +which will result in a join in the executed query +(don't worry, if you don't know what a join is, +you don't need to understand the queries that badaas-orm executes). + +For details visit :doc:`/badaas-orm/query`. + +PreloadCondition +----------------------------- + +Type of condition that allows retrieving information from a model as a result of the database (preload). +This information can be all its attributes and/or another model that is related to it. + +For details visit :doc:`/badaas-orm/preloading`. + +Operator +----------------------------- + +Concept similar to database operators, +which allow different operations to be performed on an attribute of a model, +such as comparisons, predicates, pattern matching, etc. + +Operators can be classified as static, dynamic and unsafe. + +For details visit :doc:`/badaas-orm/query`. + +Static operator +----------------------------- + +Static operators are those that perform operations on an attribute and static values, +such as a boolean value, an integer, etc. + +For details visit :doc:`/badaas-orm/query`. + +Dynamic operator +----------------------------- + +Dynamic operators are those that perform operations between an attribute and other attributes, +either from the same model or from a different model, as long as the type of these attributes is the same. + +For details visit :doc:`/badaas-orm/advanced_query`. + +Unsafe operator +----------------------------- + +Unsafe operators are those that can perform operations between an attribute and +any type of value or attribute. + +For details visit :doc:`/badaas-orm/advanced_query`. + +Nullable types +----------------------------- + +Nullable types are the types provided by the sql library +that are a nullable version of the basic types: +sql.NullString, sql.NullTime, sql.NullInt64, sql.NullInt32, +sql.NullBool, sql.NullFloat64, etc.. + +For details visit . + +CRUDService +----------------------------- + +A CrudService is a service that allows us to perform CRUD (create, read, update and delete) +operations on a specific model, executing all the necessary operations within a transaction. +Internally they use the CRUDRepository of that model. + +For details visit :ref:`badaas-orm/crud:CRUDServices and CRUDRepositories`. + +CRUDRepository +----------------------------- + +A CRUDRepository is an object that allows us to perform CRUD operations (create, read, update, delete) +on a model but, unlike services, its internal operations are performed within a transaction received +by parameter. +This is useful to be able to define services that perform multiple CRUD +operations within the same transaction. + +For details visit :ref:`badaas-orm/crud:CRUDServices and CRUDRepositories`. + +Compilable query system +----------------------------- + +The set of conditions that are received by the read operations of the CRUDService +and CRUDRepository form the badaas-orm compilable query system. +It is so named because the conditions will verify at compile time that the query to be executed is correct. + +For details visit :ref:`badaas-orm/query:compilable query system`. + +Conditions generation +---------------------------- + +Conditions are the basis of the compilable query system. +They are generated for each model and attribute and can then be used. +Their generation is done with badaas-cli. + +For details visit :ref:`badaas-orm/query:Conditions generation`. + +Dependency injection +----------------------------------- + +Dependency injection is a programming technique in which an object or function +receives other objects or functions that it depends on. badaas-orm is compatible with +`uber fx `_ to inject the CRUDServices and +CRUDRepositories in your objects and functions. + +Relation getter +----------------------------------- + +Relationships between objects can be loaded from the database using PreloadConditions. +In order to safely navigate the relations in the loaded model badaas-orm provides methods +called "relation getters". + +For details visit :doc:`/badaas-orm/preloading`. \ No newline at end of file diff --git a/docs/badaas-orm/connecting_to_a_database.rst b/docs/badaas-orm/connecting_to_a_database.rst new file mode 100644 index 00000000..2abe2a25 --- /dev/null +++ b/docs/badaas-orm/connecting_to_a_database.rst @@ -0,0 +1,36 @@ +============================== +Connecting to a database +============================== + +Connection +----------------------------- + +badaas-orm supports the PostgreSQL databases using gorm's driver. +Some databases may be compatible with the postgres dialect, +in which case you could just use the dialect for those databases (from which CockroachDB is tested). + +To communicate with the database badaas-orm need a :ref:`GormDB ` object, +that can be created by following `gorm documentation `_. + +badaas-orm also offers the `orm.ConnectToDialector` method that will allow you to connect to a database +using the specified dialector with retrying. +It also configures the `gorm's logger `_ to work with +`zap logger `_. + +When using badaas-orm with `fx` as :ref:`dependency injector ` you +will need to provide (`fx.Provide`) a function that returns a `*gorm.DB`. + +Migration +---------------------------- + +Migration is done by gorm using the `gormDB.AutoMigrate` method. +For details visit `gorm docs `_. + +When using badaas-orm with `fx` as :ref:`dependency injector ` +this method can't be called directly. In that case, badaas-orm will execute the migration by providing +`orm.AutoMigrate` to fx. For this to work, you will need to provide also a method that returns +`orm.GetModelsResult` with the models you want to include in the migration. +Remember that the order in this list is important for gorm to be able to execute the migration. + + + diff --git a/docs/badaas-orm/crud.rst b/docs/badaas-orm/crud.rst new file mode 100644 index 00000000..253d1ae8 --- /dev/null +++ b/docs/badaas-orm/crud.rst @@ -0,0 +1,58 @@ +============================== +CRUD Operations +============================== + +CRUDServices and CRUDRepositories +-------------------------------------- + +CRUD operations are made to your models via the CRUDServices and CRUDRepositories. +The difference between them is that a CRUDService will execute this operations within a transaction +while the CRUDRepository will be executed within a transaction received by parameter, +thus allowing defining services that perform multiple CRUD operations within the same transaction. + +Create, Save and Delete methods are just hooks to the gorm's corresponding methods. +For details visit +, and . +On the other hand, read (query) operations are provided by badaas-orm via its +:ref:`compilable query system ` +(see how in :doc:`/badaas-orm/query`). + +Each pair of CRUDService and CRUDRepository corresponds to a model. To create them you must use +the `orm.GetCRUD[, ](gormDB)` where +`` is the type of your :ref:`model `, +`` is the type of your :ref:`model's id ` +and `gormDB` is the :ref:`GormDB ` object. + +When using badaas-orm with `fx` as :ref:`dependency injector ` you +will need to provide to fx `orm.GetCRUDServiceModule[]()` +where `` is the type of your :ref:`model `. +After that the following can be used by dependency injection: + +- `crudYourModelService orm.CRUDService[, ]` +- `crudYourModelRepository orm.CRUDRepository[, ]` + +For example: + +.. code-block:: go + + + type YourModel struct { + model.UUIDModel + } + + func main() { + fx.New( + // connect to db + fx.Provide(NewGormDBConnection), + // activate badaas-orm + fx.Provide(GetModels), + orm.AutoMigrate, + + orm.GetCRUDServiceModule[YourModel](), + fx.Invoke(QueryCRUDObjects), + ).Run() + } + + func QueryCRUDObjects(crudYourModelService orm.CRUDService[YourModel, model.UUID]) { + // use crudYourModelService + } \ No newline at end of file diff --git a/docs/badaas-orm/declaring_models.rst b/docs/badaas-orm/declaring_models.rst new file mode 100644 index 00000000..94ef757a --- /dev/null +++ b/docs/badaas-orm/declaring_models.rst @@ -0,0 +1,173 @@ +============================== +Declaring models +============================== + +Model declaration +----------------------- + +The badaas-orm :ref:`model ` declaration is based on the GORM model declaration, +so its definition, conventions, tags and associations are compatible with badaas-orm. +For details see `gorm documentation `_. +On the contrary, badaas-orm presents some differences/extras that are explained in this section. + +Base models +----------------------- + +To be considered a model, your structures must have embedded one of the +:ref:`base models ` provided by badaas-orm: + +- `model.UUIDModel`: Model identified by a model.UUID (Random (Version 4) UUID). +- `model.UIntModel`: Model identified by a model.UIntID (auto-incremental uint). + +Both base models provide date created, updated and `deleted `_. + +To use them, simply embed the desired model in any of your structs: + +.. code-block:: go + + type MyModel struct { + model.UUIDModel + + Name string + Email *string + Age uint8 + Birthday *time.Time + MemberNumber sql.NullString + ActivatedAt sql.NullTime + // ... + } + +Type of attributes +----------------------- + +As we can see in the example in the previous section, +the attributes of your models can be of multiple types, +such as basic go types, pointers, and :ref:`nullable types `. + +This difference can generate differences in the information that is stored in the database, +since saving a model created as follows: + +.. code-block:: go + + MyModel{} + +will save a empty string for Name but a null for the Email and the MemberNumber. + +The use of nullable types is strongly recommended and badaas-orm takes into account +their use in each of its functionalities. + +Associations +----------------------- + +All associations provided by GORM are supported. +For more information see , +, and +. +However, in this section we will give some differences in badaas-orm and +details that are not clear in this documentation. + +IDs +^^^^^^^^^^^^^^^^^^^^^ + +Since badaas-orm base models use model.UUID or model.UIntID to identify the models, +the type of id used in a reference to another model is the corresponding one of these two, +for example: + +.. code-block:: go + + type ModelWithUUID struct { + model.UUIDModel + } + + type ModelWithUIntID struct { + model.UIntModel + } + + type ModelWithReferences struct { + model.UUIDModel + + ModelWithUUID *ModelWithUUID + ModelWithUUIDID *model.UUID + + ModelWithUIntID *ModelWithUIntID + ModelWithUIntIDID *model.UIntID + } + +References +^^^^^^^^^^^^^^^^^^^^^ + +References to other models can be made with or without pointers: + +.. code-block:: go + + type ReferencedModel struct { + model.UUIDModel + } + + type ModelWithPointer struct { + model.UUIDModel + + // reference with pointer + PointerReference *ReferencedModel + PointerReferenceID *model.UUID + } + + type ModelWithoutPointer struct { + model.UUIDModel + + // reference without pointer + Reference ReferencedModel + ReferenceID model.UUID + } + +As in the case of attributes, +this can make a difference when persisting, since one created as follows: + +.. code-block:: go + + ModelWithoutPointer{} + +will also create and save an empty ReferencedModel{}, what may be undesired behavior. +For this reason, although both options are still compatible with badaas-orm, +we recommend the use of pointers for references. +In case the relation is not nullable, use the `not null` tag in the id of the reference, for example: + +.. code-block:: go + + type ReferencedModel struct { + model.UUIDModel + } + + type ModelWithPointer struct { + model.UUIDModel + + // reference with pointer not null + PointerReference *ReferencedModel + PointerReferenceID *model.UUID `gorm:"not null"` + } + +Reverse reference +------------------------------------ + +Although no example within the `gorm's documentation `_ shows it, +when defining relations, we can also put a reference in the reverse direction +to add navigability to our model. +In addition, adding this reverse reference will allow the corresponding conditions +to be generated during condition generation. + +For example: + +.. code-block:: go + + type Related struct { + model.UUIDModel + + YourModel *YourModel + } + + type YourModel struct { + model.UUIDModel + + Related *Related + RelatedID *model.UUID + } \ No newline at end of file diff --git a/docs/badaas-orm/index.rst b/docs/badaas-orm/index.rst new file mode 100644 index 00000000..55ce0b06 --- /dev/null +++ b/docs/badaas-orm/index.rst @@ -0,0 +1,33 @@ +============================== +Introduction +============================== + +Badaas-orm is the BaDaaS' component that allows for easy and safe persistence and querying of objects but +it can be used both within a BaDaaS application and independently. + +It's built on top of `gorm `_, +a library that actually provides the functionality of an ORM: mapping objects to tables in the SQL database. +While gorm does this job well with its automatic migration +then performing queries on these objects is somewhat limited, +forcing us to write SQL queries directly when they are complex. +Badaas-orm seeks to address these limitations with a query system that: + +- Is compile-time safe: + its query system is validated at compile time to avoid errors such as + comparing attributes that are of different types, + trying to use attributes or navigate relationships that do not exist, + using information from tables that are not included in the query, etc. +- Is easy to use: + the use of this system does not require knowledge of databases, + SQL languages or complex concepts. + Writing queries only requires programming in go and the result is easy to read. +- Is designed for real applications: + the query system is designed to work well in real-world cases where queries are complex, + require navigating multiple relationships, performing multiple comparisons, etc. +- Is designed so that developers can focus on the business model: + its queries allow easy retrieval of model relationships to apply business logic to the model + and it provides mechanisms to avoid errors in the business logic due to mistakes in loading + information from the database. +- It is designed for high performance: + the query system avoids as much as possible the use of reflection and aims + that all the necessary model data can be retrieved in a single query to the database. \ No newline at end of file diff --git a/docs/badaas-orm/preloading.rst b/docs/badaas-orm/preloading.rst new file mode 100644 index 00000000..3c2f2f41 --- /dev/null +++ b/docs/badaas-orm/preloading.rst @@ -0,0 +1,171 @@ +============================== +Preloading +============================== + +PreloadConditions +--------------------------- + +During the :ref:`conditions generation ` the following +PreloadConditions are also generated which are useful for preloading: + +- One PreloadCondition for each of your models, that will allow to preload this model when doing a query. + The name of these conditions will be PreloadAttributes where + is the model type. +- One PreloadCondition for each of the relations of your model, + to preload that the related object when doing a query. + This is really just a facility that translates to using the JoinCondition of + that relation and then the PreloadAttributes of the related model. + The name of these conditions will be Preload where + is the model type and is the name of the attribute that creates the relation. +- One PreloadCondition to preload all the related models of your model. + The name of these conditions will be PreloadRelations where + is the model type. + +Examples +---------------------------------- + +**Preload a related model** + +In this example we query all YourModels and preload whose related Related. + +.. code-block:: go + + type Related struct { + model.UUIDModel + } + + type YourModel struct { + model.UUIDModel + + Related Related + RelatedID model.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelRelated( + conditions.RelatedPreloadAttributes, + ), + ) + +Or using the PreloadCondition to avoid the JoinCondition +(only useful when you don't want to add other conditions to that Join): + +.. code-block:: go + + type Related struct { + model.UUIDModel + } + + type YourModel struct { + model.UUIDModel + + Related Related + RelatedID model.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelPreloadRelated, + ) + +**Preload a list of models** + +.. code-block:: go + + type Related struct { + model.UUIDModel + + YourModel *YourModel + YourModelID *model.UUID + } + + type YourModel struct { + model.UUIDModel + + Related *[]Related + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelPreloadRelated, + ) + +**Nested preloads** + +.. code-block:: go + + type Parent struct { + model.UUIDModel + } + + type Related struct { + model.UUIDModel + + Parent Parent + ParentID model.UUID + } + + type YourModel struct { + model.UUIDModel + + Related Related + RelatedID model.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelRelated( + conditions.RelatedPreloadParent, + ), + ) + +As we can see, it is not necessary to add the preload to all joins, +it is enough to do it in the deepest one, +to recover, in this example, both Related and Parent. + +Relation getters +-------------------------------------- + +At the moment, with the PreloadConditions, we can choose whether or not to preload a relation. +The problem is that once we get the result of the query, we cannot determine if a null value +corresponds to the fact that the relation is really null or that the preload was not performed, +which means a big risk of making decisions in our business logic on incomplete information. + +For this reason, badaas-orm provides the Relation getters. +These are methods that will be added to your models to safely navigate a relation, +responding `errors.ErrRelationNotLoaded` in case you try to navigate a relation +that was not loaded from the database. +They are created in a file called badaas-orm.go in your model package when +:ref:`generating conditions `. + +Here is an example of its use: + +.. code-block:: go + + type Related struct { + model.UUIDModel + } + + type YourModel struct { + model.UUIDModel + + Related Related + RelatedID model.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelPreloadRelated, + ) + + if err == nil && len(yourModels) > 1 { + firstRelated, err := yourModels[0].GetRelated() + if err == nil { + // you can safely apply your business logic + } else { + // err is errors.ErrRelationNotLoaded + } + } + +Unfortunately, these relation getters cannot be created in all cases but only in those in which: + +- The relation is made with an object directly instead of a pointer + (which is not recommended as described :ref:`here `). +- The relation is made with pointers and the foreign key (typically the ID) is in the same model. +- The relation is made with a pointer to a list. \ No newline at end of file diff --git a/docs/badaas-orm/query.rst b/docs/badaas-orm/query.rst new file mode 100644 index 00000000..7ae9ed45 --- /dev/null +++ b/docs/badaas-orm/query.rst @@ -0,0 +1,175 @@ +============================== +Query +============================== + +Query methods +------------------------ + +In CRUDRepository you will find different methods that will +allow you to perform queries on the model to which that repository belongs: + +- GetByID: will allow you to obtain a model by its id. +- QueryOne: will allow you to obtain the model that meets the conditions received by parameter. +- Query: will allow you to obtain the models that meet the conditions received by parameter. + +Compilable query system +------------------------ + +The set of conditions that are received by the read operations of the CRUDService +and CRUDRepository form the badaas-orm compilable query system. +It is so named because the conditions will verify at compile time that the query to be executed is correct. + +These conditions are objects of type Condition that contain the +necessary information to perform the queries in a safe way. +They are generated from the definition of your models using badaas-cli. + +Conditions generation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The generation of conditions is done with badaas-cli. For this, we need to install badaas-cli: + +.. code-block:: bash + + go install github.com/ditrit/badaas-cli + +Then, inside our project we will have to create a package called conditions +(or another name if you wish) and inside it a file with the following content: + +.. code-block:: go + + package conditions + + //go:generate badaas-cli gen conditions ../models_path_1 ../models_path_2 + +where ../models_path_1 ../models_path_2 are the relative paths between the package conditions +and the packages containing the definition of your models (can be only one). + +Now, from the root of your project you can execute: + +.. code-block:: bash + + go generate ./... + +and the conditions for each of your models will be created in the conditions package. + +Use of the conditions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +After generating the conditions you will have the following conditions: + +- One condition for each attribute of each of your models. + The name of these conditions will be where + is the model type and is the attribute name. + These conditions are of type WhereCondition. +- One condition for each relationship with another model that each of your models has. + The name of these conditions will be where + is the model type and is the name of the attribute that creates the relation. + These conditions are of type JoinCondition because using them will + mean performing a join within the executed query. + +Then, combining these conditions, the Connection Conditions (orm.And, orm.Or, orm.Not) +and the Operators (orm.Eq, orm.Lt, etc.) you will be able to make all +the queries you need in a safe way. + +Examples +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Filter by an attribute** + +In this example we query all YourModel that has "a_string" in the Attribute attribute. + +.. code-block:: go + + type YourModel struct { + model.UUIDModel + + Attribute string + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelAttribute(orm.Eq("a_string")), + ) + +**Filter by an attribute of a related model** + +In this example we query all YourModels whose related Related has "a_string" in its Attribute attribute. + +.. code-block:: go + + type Related struct { + model.UUIDModel + + Attribute string + } + + type YourModel struct { + model.UUIDModel + + Related Related + RelatedID model.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelRelated( + conditions.RelatedAttribute(orm.Eq("a_string")), + ), + ) + +**Multiple conditions** + +In this example we query all YourModels that has a 4 in the IntAttribute attribute and +whose related Related has "a_string" in its Attribute attribute. + +.. code-block:: go + + type Related struct { + model.UUIDModel + + Attribute string + } + + type YourModel struct { + model.UUIDModel + + IntAttribute int + + Related Related + RelatedID model.UUID + } + + yourModels, err := ts.crudYourModelService.Query( + conditions.YourModelIntAttribute(orm.Eq(4)), + conditions.YourModelRelated( + conditions.RelatedAttribute(orm.Eq("a_string")), + ), + ) + +Operators +------------------------ + +Below you will find the complete list of available operators: + +- orm.Eq(value): EqualTo +- orm.EqOrIsNull(value): if value is not NULL returns a Eq operator but if value is NULL returns a IsNull operator +- orm.NotEq(value): NotEqualTo +- orm.NotEqOrIsNotNull(value): if value is not NULL returns a NotEq operator but if value is NULL returns a IsNotNull operator +- orm.Lt(value): LessThan +- orm.LtOrEq(value): LessThanOrEqualTo +- orm.Gt(value): GreaterThan +- orm.GtOrEq(value): GreaterThanOrEqualTo +- orm.IsNull() +- orm.IsNotNull() +- orm.Between(v1, v2): Equivalent to v1 < attribute < v2 +- orm.NotBetween(v1, v2): Equivalent to NOT (v1 < attribute < v2) +- orm.IsTrue() +- orm.IsNotTrue() +- orm.IsFalse() +- orm.IsNotFalse() +- orm.IsUnknown() +- orm.IsNotUnknown() +- orm.IsDistinct(value) +- orm.IsNotDistinct(value) +- orm.Like(pattern) +- orm.Like(pattern).Escape(escape) +- orm.ArrayIn(values) +- orm.ArrayNotIn(values) \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 0fb0cb98..6663a3df 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ each provided by a component that can be used independently and has a different Communications between nodes are TLS encrypted using `shoset `_. - **Persistence** (wip_unstable): Applicative objects are persisted as well as user files. Those resources are shared across the clusters to increase resiliency. + To achieve this, BaDaaS uses the :doc:`badaas-orm ` component. - **Querying Resources** (unstable): Resources are accessible via a REST API. - **Posix compliant** (stable): Badaas strives towards being a good unix citizen and respecting commonly accepted norms. (see :doc:`badaas/configuration`) @@ -38,3 +39,15 @@ Learn how to use BaDaaS following the :doc:`badaas/quickstart`. badaas/quickstart badaas/functionalities badaas/configuration + +.. toctree:: + :caption: Badaas-orm + + badaas-orm/index + badaas-orm/concepts + badaas-orm/declaring_models + badaas-orm/connecting_to_a_database + badaas-orm/crud + badaas-orm/query + badaas-orm/advanced_query + badaas-orm/preloading \ No newline at end of file diff --git a/orm/README.md b/orm/README.md index 240a0cff..5af8dec2 100644 --- a/orm/README.md +++ b/orm/README.md @@ -1,47 +1,23 @@ # BaDaaS ORM: Backend and Distribution ORM (Object Relational Mapping) -BaDaaS ORM is the BaDaaS component that allows for easy persistence and querying of objects. It is built on top of gorm and adds for each entity a service and a repository that allows complex queries without any extra effort. +Badaas-orm is the BaDaaS' component that allows for easy and safe persistence and querying of objects but it can be used both within a BaDaaS application and independently. -BaDaaS ORM can be used both within a BaDaaS application and as a stand-alone application. +It's built on top of `gorm `_, a library that actually provides the functionality of an ORM: mapping objects to tables in the SQL database. While gorm does this job well with its automatic migration then performing queries on these objects is somewhat limited, forcing us to write SQL queries directly when they are complex. Badaas-orm seeks to address these limitations with a query system that: -- [Installation](#installation) -- [Provided functionalities](#provided-functionalities) - - [Base models](#base-models) - - [CRUDServiceModule](#crudservicemodule) +- Is compile-time safe: its query system is validated at compile time to avoid errors such as comparing attributes that are of different types, trying to use attributes or navigate relationships that do not exist, using information from tables that are not included in the query, etc. +- Is easy to use: the use of this system does not require knowledge of databases, SQL languages or complex concepts. Writing queries only requires programming in go and the result is easy to read. +- Is designed for real applications: the query system is designed to work well in real-world cases where queries are complex, require navigating multiple relationships, performing multiple comparisons, etc. +- Is designed so that developers can focus on the business model: its queries allow easy retrieval of model relationships to apply business logic to the model and it provides mechanisms to avoid errors in the business logic due to mistakes in loading information from the database. +- It is designed for high performance: the query system avoids as much as possible the use of reflection and aims that all the necessary model data can be retrieved in a single query to the database. -## Installation +## Documentation -Once you have started your project with `go init`, you must add the dependency to BaDaaS: + -```bash -go get -u github.com/ditrit/badaas -``` +## Contributing -## Provided functionalities +See [this section](../docs/contributing/contributing.md) to view the badaas contribution guidelines. -### Base models +## License -badaas-orm gives you two types of base models for your classes: `orm.UUIDModel` and `orm.UIntModel`. - -To use them, simply embed the desired model in any of your classes: - -```go -type MyClass struct { - orm.UUIDModel - - // your code here -} -``` - -Once done your class will be considered a **BaDaaS Model**. - -The difference between them is the type they will use as primary key: a random uuid and an auto incremental uint respectively. Both provide date created, edited and deleted (). - -### CRUDServiceModule - -`CRUDServiceModule` provides you a CRUDService and a CRUDRepository for your badaas Model. After calling it as, for example, `orm.GetCRUDServiceModule[models.Company](),` the following can be used by dependency injection: - -- `crudCompanyService orm.CRUDService[models.Company, orm.UUID]` -- `crudCompanyRepository orm.CRUDRepository[models.Company, orm.UUID]` - -These classes will allow you to perform queries using the compilable query system generated with badaas-cli. For details on how to do this visit [badaas-cli docs](github.com/ditrit/badaas-cli/README.md). +Badaas is Licensed under the [Mozilla Public License Version 2.0](../LICENSE). From b5eb587dcbf215ac3a2d307fc48c5534c22c2530 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 18:28:59 +0200 Subject: [PATCH 88/90] add code of conduct --- CODE_OF_CONDUCT.md | 132 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 ++ 2 files changed, 136 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..41fe403e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/README.md b/README.md index 4a2d31a0..b54292aa 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,10 @@ Badaas provides several key features, each provided by a component that can be u See [this section](./CONTRIBUTING.md). +## Code of Conduct + +This project has adopted the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md) + ## License Badaas is Licensed under the [Mozilla Public License Version 2.0](./LICENSE). From 1de698b379db2a95cf70daea3248328b93632239 Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Mon, 7 Aug 2023 18:29:27 +0200 Subject: [PATCH 89/90] add readme badges --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index b54292aa..376ea1d2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,15 @@ # BADAAS: Backend And Distribution As A Service +[![Build Status](https://github.com/ditrit/badaas/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/ditrit/badaas/actions) +[![Go Report Card](https://goreportcard.com/badge/github.com/ditrit/badaas)](https://goreportcard.com/report/github.com/ditrit/badaas) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ditrit_badaas&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=ditrit_badaas) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ditrit_badaas&metric=coverage)](https://sonarcloud.io/summary/new_code?id=ditrit_badaas) +[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7624/badge)](https://bestpractices.coreinfrastructure.org/projects/7624) + +[![Go.Dev reference](https://img.shields.io/badge/go.dev-reference-blue?logo=go&logoColor=white)](https://pkg.go.dev/github.com/ditrit/badaas) + +[![Discord DitRit](https://dcbadge.vercel.app/api/server/zkKfj9gj2C?style=flat&theme=default-inverted)](https://discord.gg/zkKfj9gj2C) + BaDaaS enables the effortless construction of ***distributed, resilient, highly available and secure applications by design***, while ensuring very simple deployment and management (NoOps). > **Warning** From afb964c0e5b5eab376417ada5f035f8f6d3c608c Mon Sep 17 00:00:00 2001 From: Franco Liberali Date: Tue, 8 Aug 2023 10:04:24 +0200 Subject: [PATCH 90/90] move contributing docs to readthedocs --- .github/ISSUE_TEMPLATE/bug_report.md | 4 +- .github/ISSUE_TEMPLATE/discussion.md | 7 ++ .github/ISSUE_TEMPLATE/user_story.md | 2 +- CONTRIBUTING.md | 127 +-------------------------- README.md | 2 +- docs/contributing/contributing.md | 66 ++++++++++++++ docs/contributing/developing.md | 94 ++++++++++++++++++++ docs/contributing/maintaining.md | 17 ++++ docs/index.rst | 10 ++- 9 files changed, 200 insertions(+), 129 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/discussion.md create mode 100644 docs/contributing/contributing.md create mode 100644 docs/contributing/developing.md create mode 100644 docs/contributing/maintaining.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9cc652d2..608172f0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: '' -labels: Bug +labels: bug --- ## Describe the bug @@ -31,6 +31,8 @@ If applicable, add screenshots to help explain your problem. Please complete the following information: - badaas version [X.X.X] or commit hash +- go version +- database vendor and version (in case of bugs related with badaas-orm) ## Additional context diff --git a/.github/ISSUE_TEMPLATE/discussion.md b/.github/ISSUE_TEMPLATE/discussion.md new file mode 100644 index 00000000..9835b3a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/discussion.md @@ -0,0 +1,7 @@ +--- +name: Discussion +about: Start a discussion for BaDaaS +title: '' +labels: question +--- + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/user_story.md b/.github/ISSUE_TEMPLATE/user_story.md index 1112fab2..f9a6617a 100644 --- a/.github/ISSUE_TEMPLATE/user_story.md +++ b/.github/ISSUE_TEMPLATE/user_story.md @@ -2,7 +2,7 @@ name: Feature request about: Suggest an idea for this project title: '' -labels: User Story, To be verify +labels: enhancement --- ## Description diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00a4d76a..3621cc97 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,126 +1,3 @@ -# Contribute to the development of badaas +# Contributing -- [Tests](#tests) - - [Dependencies](#dependencies) - - [Unit tests](#unit-tests) - - [Integration tests](#integration-tests) - - [Feature tests (of end to end tests)](#feature-tests-or-end-to-end-tests) -- [Logger](#logger) -- [Directory structure](#directory-structure) -- [Git](#git) - - [Branch naming policy](#branch-naming-policy) - - [Default branch](#default-branch) - - [How to release](#how-to-release) - -## Tests - -### Dependencies - -Running tests have some dependencies as: `mockery`, `gotestsum`, etc.. Install them with `make install dependencies`. - -### Unit tests - -We use the standard test suite in combination with [github.com/stretchr/testify](https://github.com/stretchr/testify) to do our unit testing. Mocks are generated using [mockery](https://github.com/vektra/mockery) a mock generator using this command `make test_generate_mocks`. - -To run them, please run: - -```sh -make test_unit -``` - -### Integration tests - -Integration tests have a database and the dependency injection system. - -```sh -make test_integration -``` - -### Feature tests (or end to end tests) - -We use docker to run a Badaas instance in combination with one node of CockroachDB. - -Run: - -```sh -make test_e2e -``` - -The feature files can be found in the `test_e2e/features` folder. - -## Logger - -We use uber's [zap](https://pkg.go.dev/go.uber.org/zap) to log stuff, please take `zap.Logger` as an argument for your services constructors. [fx](https://github.com/uber-go/fx) will provide your service with an instance. - -## Directory structure - -This is the directory structure we use for the project: - -- `configuration/` *(Go code)*: Contains all the configuration keys and holders. Please only use the interfaces, they are all mocked for easy testing. -- `controllers/` *(Go code)*: Contains all the http controllers, they handle http requests and consume services. -- `docker/` : Contains the docker, docker-compose file and configuration files for different environments. - - `test_db/` : Contains the Dockerfile to build a development/test version of CockroachDB. - - `test_api/` : Contains files to build a development/test version of the api. -- `test_e2e/`: Contains all the feature and steps for e2e tests. -- `testintegration/`: Contains all the integration tests. -- `logger/` *(Go code)*: Contains the logger creation logic. Please don't call it from your own services and code, use the dependency injection system. -- `orm/` *(Go code)*: Contains the code of the orm used by badaas. -- `persistance/` *(Go code)*: - - `gormdatabase/` *(Go code)*: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. - - `models/` *(Go code)*: Contains the models (for a structure to me considered a valid model, it has to embed `badaas/orm.UUIDModel` or `badaas/orm.UIntModel`). - - `dto/` *(Go code)*: Contains the Data Transfer Objects. They are used mainly to decode json payloads. - - `pagination/` *(Go code)*: Contains the pagination logic. - - `repository/` *(Go code)*: Contains the repository interfaces and implementations to manage queries to the database. -- `router/` *(Go code)*: Contains http router of badaas. - - `middlewares/` *(Go code)*: Contains the various http middlewares that we use. -- `services/` *(Go code)*: Contains services. - - `auth/protocols/`: Contains the implementations of authentication clients for different protocols. - - `basicauth/` *(Go code)*: Handle the authentication using email/password. - - `sessionservice/` *(Go code)*: Handle sessions and their lifecycle. - - `userservice/` *(Go code)*: Handle users. -- `utils/` *(Go code)*: Contains utility functions that can be used all around the project. -- `validators/` *(Go code)*: Contains validators such as an email validator. - -At the root of the project, you will find: - -- The README. -- The changelog. -- The LICENSE file. - -## Git - -### Branch naming policy - -`[BRANCH_TYPE]/[BRANCH_NAME]` - -- `BRANCH_TYPE` is a prefix to describe the purpose of the branch. - Accepted prefixes are: - - `feature`, used for feature development - - `bugfix`, used for bug fix - - `improvement`, used for refactor - - `library`, used for updating library - - `prerelease`, used for preparing the branch for the release - - `release`, used for releasing project - - `hotfix`, used for applying a hotfix on main - - `poc`, used for proof of concept -- `BRANCH_NAME` is managed by this regex: `[a-z0-9._-]` (`_` is used as space character). - -### Default branch - -The default branch is `main`. Direct commit on it is forbidden. The only way to update the application is through pull request. - -Release tag are only done on the `main` branch. - -### How to release - -We use [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as guideline for the version management. - -Steps to release: - -- Create a new branch labeled `release/vX.Y.Z` from the latest `main`. -- Improve the version number in `changelog.md`. -- Verify the content of the `changelog.md`. -- Commit the modifications with the label `Release version X.Y.Z`. -- Create a pull request on github for this branch into `main`. -- Once the pull request validated and merged, tag the `main` branch with `vX.Y.Z`. -- After the tag is pushed, make the release on the tag in GitHub. +See [this section](./docs/contributing/contributing.md). diff --git a/README.md b/README.md index 376ea1d2..af6ace97 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Badaas provides several key features, each provided by a component that can be u ## Contributing -See [this section](./CONTRIBUTING.md). +See [this section](./docs/contributing/contributing.md). ## Code of Conduct diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md new file mode 100644 index 00000000..8dcf2b77 --- /dev/null +++ b/docs/contributing/contributing.md @@ -0,0 +1,66 @@ +# Contributing + +Thank you for your interest in BaDaaS! This document provides the guidelines for how to contribute to the project through issues and pull-requests. Contributions can also come in additional ways such as joining the [DitRit discord server](https://discord.gg/zkKfj9gj2C), commenting on issues or pull requests and more. + +## Issues + +### Issue types + +There are 3 types of issues: + +- Bug report: You've found a bug with the code, and want to report it, or create an issue to track the bug. +- Discussion: You have something on your mind, which requires input form others in a discussion, before it eventually manifests as a proposal. +- Feature request: Used for items that propose a new idea or functionality. This allows feedback from others before code is written. + +To ask questions and troubleshoot, please join the [DitRit discord server](https://discord.gg/zkKfj9gj2C) (use the BADAAS channel). + +### Before submitting + +Before you submit an issue, make sure you’ve checked the following: + +1. Check for existing issues + - Before you create a new issue, please do a search in [open issues](https://github.com/ditrit/badaas/issues) to see if the issue or feature request has already been filed. + - If you find your issue already exists, make relevant comments and add your reaction. +2. For bugs + - It’s not an environment issue. + - You have as much data as possible. This usually comes in the form of logs and/or stacktrace. +3. You are assigned to the issue, a branch is created from the issue and the `wip` tag is added if you are also planning to develop the solution. + +## Pull Requests + +All contributions come through pull requests. To submit a proposed change, follow this workflow: + +1. Make sure there's an issue (bug report or feature request) opened, which sets the expectations for the contribution you are about to make +2. Assign yourself to the issue and add the `wip` tag +3. Fork the [repo](https://github.com/ditrit/badaas) and create a new [branch](#branch-naming-policy) from the issue +4. Install the necessary [development environment](developing.md#environment) +5. Create your change and the corresponding [tests](developing.md#tests) +6. Update relevant documentation for the change in `docs/` +7. If changes are necessary in [BaDaaS example](https://github.com/ditrit/badaas-example) and [badaas-orm example](https://github.com/ditrit/badaas-orm-example), follow the same workflow there +8. Open a PR (and add links to the example repos' PR if they exist) +9. Wait for the CI process to finish and make sure all checks are green +10. A maintainer of the project will be assigned + +### Use work-in-progress PRs for early feedback + +A good way to communicate before investing too much time is to create a "Work-in-progress" PR and share it with your reviewers. The standard way of doing this is to add a "[WIP]" prefix in your PR’s title and assign the do-not-merge label. This will let people looking at your PR know that it is not well baked yet. + +### Branch naming policy + +`[BRANCH_TYPE]/[BRANCH_NAME]` + +- `BRANCH_TYPE` is a prefix to describe the purpose of the branch. + Accepted prefixes are: + - `feature`, used for feature development + - `bugfix`, used for bug fix + - `improvement`, used for refactor + - `library`, used for updating library + - `prerelease`, used for preparing the branch for the release + - `release`, used for releasing project + - `hotfix`, used for applying a hotfix on main + - `poc`, used for proof of concept +- `BRANCH_NAME` is managed by this regex: `[a-z0-9._-]` (`_` is used as space character). + +## Code of Conduct + +This project has adopted the [Contributor Covenant Code of Conduct](https://github.com/ditrit/badaas/blob/main/CODE_OF_CONDUCT.md) diff --git a/docs/contributing/developing.md b/docs/contributing/developing.md new file mode 100644 index 00000000..6d07b726 --- /dev/null +++ b/docs/contributing/developing.md @@ -0,0 +1,94 @@ +# Developing + +This document provides the information you need to know before developing code for a pull request. + +## Environment + +- Install [go](https://go.dev/doc/install) >= v1.20 +- Install project dependencies: `go get` +- Install [docker](https://docs.docker.com/engine/install/) and [compose plugin](https://docs.docker.com/compose/install/) + +## Directory structure + +This is the directory structure we use for the project: + +- `configuration/`: Contains all the configuration holders. Please only use the interfaces, they are all mocked for easy testing. +- `controllers/`: Contains all the http controllers, they handle http requests and consume services. +- `docker/` : Contains the docker, docker-compose and configuration files for different environments. +- `docs/`: Contains the documentation showed for readthedocs.io. +- `httperrors/`: Contains the http errors that can be responded by the http api. Should be moved to `controller/` when services stop using them. +- `logger/`: Contains the logger creation logic. Please don't call it from your own services and code, use the dependency injection system. +- `mocks/`: Contains the mocks generated with `mockery`. +- `orm/` *(Go code)*: Contains the code of the orm used by badaas. +- `persistance/`: + - `gormdatabase/`: Contains the logic to create a database. Also contains a go package named `gormzap`: it is a compatibility layer between *gorm.io/gorm* and *github.com/uber-go/zap*. + - `models/`: Contains the models. + - `dto/`: Contains the Data Transfer Objects. They are used mainly to decode json payloads. + - `repository/`: Contains the repository interfaces and implementations to manage queries to the database. +- `router/`: Contains http router of badaas and the routes that can be added by the user. + - `middlewares/`: Contains the various http middlewares that we use. +- `services/`: Contains services. + - `auth/protocols/`: Contains the implementations of authentication clients for different protocols. + - `basicauth/`: Handle the authentication using email/password. + - `oidc/`: Handle the authentication via Open-ID Connect. +- `test_e2e/`: Contains all the feature and steps for e2e tests. +- `testintegration/`: Contains all the integration tests. +- `utils/`: Contains functions that can be util all around the project, as managing data structures, time, etc. + +At the root of the project, you will find: + +- The README. +- The changelog. +- The LICENSE file. + +## Tests + +### Dependencies + +Running tests have some dependencies as: `mockery`, `gotestsum`, etc.. Install them with `make install_dependencies`. + +### Linting + +We use `golangci-lint` for linting our code. You can test it with `make lint`. The configuration file is in the default path (`.golangci.yml`). The file `.vscode.settings.json.template` is a template for your `.vscode/settings.json` that formats the code according to our configuration. + +### Unit tests + +We use the standard test suite in combination with [github.com/stretchr/testify](https://github.com/stretchr/testify) to do our unit testing. Mocks are generated using [mockery](https://github.com/vektra/mockery) using the command `make test_generate_mocks`. + +To run them, use: + +```sh +make -k test_unit +``` + +### Integration tests + +Integration tests have a database and the dependency injection system. + +```sh +make test_integration +``` + +### Feature tests (end to end tests) + +These are black box tests that test BaDaaS using its http api. We use docker to run a Badaas instance in combination with one node of CockroachDB. + +Run: + +```sh +make test_e2e +``` + +The feature files can be found in the `test_e2e/features` folder. + +## Requirements + +To be acceptable, contributions must: + +- Have a good quality of code, based on . +- Have at least 80 percent new code coverage (although a higher percentage may be required depending on the importance of the feature). The tests that contribute to coverage are unit tests and integration tests. +- The features defined in the PR base issue must be explicitly tested by an e2e test or by integration tests in case it is not possible (for badaas-orm features for example). + +## Use of Third-party code + +Third-party code must include licenses. diff --git a/docs/contributing/maintaining.md b/docs/contributing/maintaining.md new file mode 100644 index 00000000..44ce9c41 --- /dev/null +++ b/docs/contributing/maintaining.md @@ -0,0 +1,17 @@ +# Maintaining + +This document is intended for BaDaaS maintainers only. + +## How to release + +Release tag are only done on the `main` branch. We use [Semantic Versioning](https://semver.org/spec/v2.0.0.html) as guideline for the version management. + +Steps to release: + +- Create a new branch labeled `release/vX.Y.Z` from the latest `main`. +- Improve the version number in `changelog.md` and `docs/conf.py`. +- Verify the content of the `changelog.md`. +- Commit the modifications with the label `Release version X.Y.Z`. +- Create a pull request on github for this branch into `main`. +- Once the pull request validated and merged, tag the `main` branch with `vX.Y.Z`. +- After the tag is pushed, make the release on the tag in GitHub. diff --git a/docs/index.rst b/docs/index.rst index 6663a3df..65e7f472 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,4 +50,12 @@ Learn how to use BaDaaS following the :doc:`badaas/quickstart`. badaas-orm/crud badaas-orm/query badaas-orm/advanced_query - badaas-orm/preloading \ No newline at end of file + badaas-orm/preloading + +.. toctree:: + :caption: Contributing + + contributing/contributing + contributing/developing + contributing/maintaining + Github