From d277b0ec254797b66d23e0a291457ac60fa24367 Mon Sep 17 00:00:00 2001 From: huyng Date: Sat, 17 Aug 2019 11:03:48 +0700 Subject: [PATCH] Use interface in the handlers of web-api/web-app --- cmd/web-api/handlers/account.go | 30 ++++++-- cmd/web-api/handlers/example.go | 5 +- cmd/web-api/handlers/project.go | 28 +++++--- cmd/web-api/handlers/routes.go | 35 ++++------ cmd/web-api/handlers/signup.go | 8 ++- cmd/web-api/handlers/user.go | 70 +++++++++++++------ cmd/web-api/handlers/user_account.go | 25 ++++++- cmd/web-api/main.go | 3 +- cmd/web-api/tests/account_test.go | 1 + cmd/web-app/handlers/account.go | 22 +++--- cmd/web-app/handlers/api_geo.go | 25 +++++-- cmd/web-app/handlers/projects.go | 4 +- cmd/web-app/handlers/routes.go | 50 +++++++------ cmd/web-app/handlers/signup.go | 9 ++- cmd/web-app/handlers/user.go | 40 ++++++----- cmd/web-app/handlers/users.go | 19 ++--- cmd/web-app/main.go | 10 ++- docker-compose.yaml | 4 +- .../account_preference/account_preference.go | 1 + internal/geonames/countries.go | 8 +-- internal/geonames/country_timezones.go | 12 ++-- internal/geonames/geonames.go | 23 +++--- internal/geonames/models.go | 12 ++++ internal/schema/migrations.go | 4 +- internal/user_account/invite/invite.go | 5 +- internal/user_auth/auth.go | 4 +- 26 files changed, 294 insertions(+), 163 deletions(-) diff --git a/cmd/web-api/handlers/account.go b/cmd/web-api/handlers/account.go index 592d962..f81bba0 100644 --- a/cmd/web-api/handlers/account.go +++ b/cmd/web-api/handlers/account.go @@ -4,23 +4,45 @@ import ( "context" "net/http" "strconv" + "time" "geeks-accelerator/oss/saas-starter-kit/internal/account" + accountref "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" + "github.com/pkg/errors" "gopkg.in/go-playground/validator.v9" ) // Account represents the Account API method handler set. -type Account struct { - *account.Repository +type Accounts struct { + Repository AccountRepository // ADD OTHER STATE LIKE THE LOGGER AND CONFIG HERE. } +type AccountRepository interface { + //CanReadAccount(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, accountID string) error + Find(ctx context.Context, claims auth.Claims, req account.AccountFindRequest) (account.Accounts, error) + Create(ctx context.Context, claims auth.Claims, req account.AccountCreateRequest, now time.Time) (*account.Account, error) + ReadByID(ctx context.Context, claims auth.Claims, id string) (*account.Account, error) + Read(ctx context.Context, claims auth.Claims, req account.AccountReadRequest) (*account.Account, error) + Update(ctx context.Context, claims auth.Claims, req account.AccountUpdateRequest, now time.Time) error + Archive(ctx context.Context, claims auth.Claims, req account.AccountArchiveRequest, now time.Time) error + Delete(ctx context.Context, claims auth.Claims, req account.AccountDeleteRequest) error +} +type AccountPrefRepository interface { + Find(ctx context.Context, claims auth.Claims, req accountref.AccountPreferenceFindRequest) ([]*accountref.AccountPreference, error) + FindByAccountID(ctx context.Context, claims auth.Claims, req accountref.AccountPreferenceFindByAccountIDRequest) ([]*accountref.AccountPreference, error) + Read(ctx context.Context, claims auth.Claims, req accountref.AccountPreferenceReadRequest) (*accountref.AccountPreference, error) + Set(ctx context.Context, claims auth.Claims, req accountref.AccountPreferenceSetRequest, now time.Time) error + Archive(ctx context.Context, claims auth.Claims, req accountref.AccountPreferenceArchiveRequest, now time.Time) error + Delete(ctx context.Context, claims auth.Claims, req accountref.AccountPreferenceDeleteRequest) error +} + // Read godoc // @Summary Get account by ID // @Description Read returns the specified account from the system. @@ -34,7 +56,7 @@ type Account struct { // @Failure 404 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /accounts/{id} [get] -func (h *Account) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Accounts) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, ok := ctx.Value(auth.Key).(auth.Claims) if !ok { return errors.New("claims missing from context") @@ -81,7 +103,7 @@ func (h *Account) Read(ctx context.Context, w http.ResponseWriter, r *http.Reque // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /accounts [patch] -func (h *Account) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Accounts) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { diff --git a/cmd/web-api/handlers/example.go b/cmd/web-api/handlers/example.go index fe15ddd..b866eff 100644 --- a/cmd/web-api/handlers/example.go +++ b/cmd/web-api/handlers/example.go @@ -7,13 +7,14 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" "geeks-accelerator/oss/saas-starter-kit/internal/project" - "github.com/pkg/errors" "net/http" + + "github.com/pkg/errors" ) // Example represents the Example API method handler set. type Example struct { - Project *project.Repository + Project ProjectRepository // ADD OTHER STATE LIKE THE LOGGER AND CONFIG HERE. } diff --git a/cmd/web-api/handlers/project.go b/cmd/web-api/handlers/project.go index 82c835f..b0cd946 100644 --- a/cmd/web-api/handlers/project.go +++ b/cmd/web-api/handlers/project.go @@ -5,23 +5,35 @@ import ( "net/http" "strconv" "strings" + "time" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" "geeks-accelerator/oss/saas-starter-kit/internal/project" + "github.com/pkg/errors" "gopkg.in/go-playground/validator.v9" ) // Project represents the Project API method handler set. -type Project struct { - *project.Repository +type Projects struct { + Repository ProjectRepository // ADD OTHER STATE LIKE THE LOGGER IF NEEDED. } +type ProjectRepository interface { + ReadByID(ctx context.Context, claims auth.Claims, id string) (*project.Project, error) + Find(ctx context.Context, claims auth.Claims, req project.ProjectFindRequest) (project.Projects, error) + Read(ctx context.Context, claims auth.Claims, req project.ProjectReadRequest) (*project.Project, error) + Create(ctx context.Context, claims auth.Claims, req project.ProjectCreateRequest, now time.Time) (*project.Project, error) + Update(ctx context.Context, claims auth.Claims, req project.ProjectUpdateRequest, now time.Time) error + Archive(ctx context.Context, claims auth.Claims, req project.ProjectArchiveRequest, now time.Time) error + Delete(ctx context.Context, claims auth.Claims, req project.ProjectDeleteRequest) error +} + // Find godoc // TODO: Need to implement unittests on projects/find endpoint. There are none. // @Summary List projects @@ -40,7 +52,7 @@ type Project struct { // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /projects [get] -func (h *Project) Find(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Projects) Find(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, ok := ctx.Value(auth.Key).(auth.Claims) if !ok { return errors.New("claims missing from context") @@ -133,7 +145,7 @@ func (h *Project) Find(ctx context.Context, w http.ResponseWriter, r *http.Reque // @Failure 404 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /projects/{id} [get] -func (h *Project) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Projects) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, ok := ctx.Value(auth.Key).(auth.Claims) if !ok { return errors.New("claims missing from context") @@ -181,7 +193,7 @@ func (h *Project) Read(ctx context.Context, w http.ResponseWriter, r *http.Reque // @Failure 404 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /projects [post] -func (h *Project) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -231,7 +243,7 @@ func (h *Project) Create(ctx context.Context, w http.ResponseWriter, r *http.Req // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /projects [patch] -func (h *Project) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -282,7 +294,7 @@ func (h *Project) Update(ctx context.Context, w http.ResponseWriter, r *http.Req // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /projects/archive [patch] -func (h *Project) Archive(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Projects) Archive(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -333,7 +345,7 @@ func (h *Project) Archive(ctx context.Context, w http.ResponseWriter, r *http.Re // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /projects/{id} [delete] -func (h *Project) Delete(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Projects) Delete(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, err := auth.ClaimsFromContext(ctx) if err != nil { return err diff --git a/cmd/web-api/handlers/routes.go b/cmd/web-api/handlers/routes.go index 00f4704..cfec550 100644 --- a/cmd/web-api/handlers/routes.go +++ b/cmd/web-api/handlers/routes.go @@ -5,21 +5,14 @@ import ( "net/http" "os" - "geeks-accelerator/oss/saas-starter-kit/internal/account" - "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" "geeks-accelerator/oss/saas-starter-kit/internal/mid" saasSwagger "geeks-accelerator/oss/saas-starter-kit/internal/mid/saas-swagger" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" _ "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" - "geeks-accelerator/oss/saas-starter-kit/internal/signup" _ "geeks-accelerator/oss/saas-starter-kit/internal/signup" - "geeks-accelerator/oss/saas-starter-kit/internal/user" - "geeks-accelerator/oss/saas-starter-kit/internal/user_account" - "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" - "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "github.com/jmoiron/sqlx" "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis" ) @@ -29,14 +22,14 @@ type AppContext struct { Env webcontext.Env MasterDB *sqlx.DB Redis *redis.Client - UserRepo *user.Repository - UserAccountRepo *user_account.Repository - AccountRepo *account.Repository - AccountPrefRepo *account_preference.Repository - AuthRepo *user_auth.Repository - SignupRepo *signup.Repository - InviteRepo *invite.Repository - ProjectRepo *project.Repository + UserRepo UserRepository + UserAccountRepo UserAccountRepository + AccountRepo AccountRepository + AccountPrefRepo AccountPrefRepository + AuthRepo UserAuthRepository + SignupRepo SignupRepository + InviteRepo UserInviteRepository + ProjectRepo ProjectRepository Authenticator *auth.Authenticator PreAppMiddleware []web.Middleware PostAppMiddleware []web.Middleware @@ -79,9 +72,9 @@ func API(shutdown chan os.Signal, appCtx *AppContext) http.Handler { app.Handle("GET", "/v1/examples/error-response", ex.ErrorResponse) // Register user management and authentication endpoints. - u := User{ - Repository: appCtx.UserRepo, - Auth: appCtx.AuthRepo, + u := Users{ + UserRepo: appCtx.UserRepo, + AuthRepo: appCtx.AuthRepo, } app.Handle("GET", "/v1/users", u.Find, mid.AuthenticateHeader(appCtx.Authenticator)) app.Handle("POST", "/v1/users", u.Create, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) @@ -107,7 +100,7 @@ func API(shutdown chan os.Signal, appCtx *AppContext) http.Handler { app.Handle("DELETE", "/v1/user_accounts", ua.Delete, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) // Register account endpoints. - a := Account{ + a := Accounts{ Repository: appCtx.AccountRepo, } app.Handle("GET", "/v1/accounts/:id", a.Read, mid.AuthenticateHeader(appCtx.Authenticator)) @@ -120,7 +113,7 @@ func API(shutdown chan os.Signal, appCtx *AppContext) http.Handler { app.Handle("POST", "/v1/signup", s.Signup) // Register project. - p := Project{ + p := Projects{ Repository: appCtx.ProjectRepo, } app.Handle("GET", "/v1/projects", p.Find, mid.AuthenticateHeader(appCtx.Authenticator)) diff --git a/cmd/web-api/handlers/signup.go b/cmd/web-api/handlers/signup.go index e2472a3..a29afe2 100644 --- a/cmd/web-api/handlers/signup.go +++ b/cmd/web-api/handlers/signup.go @@ -3,6 +3,7 @@ package handlers import ( "context" "net/http" + "time" "geeks-accelerator/oss/saas-starter-kit/internal/account" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" @@ -10,17 +11,22 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" "geeks-accelerator/oss/saas-starter-kit/internal/signup" + "github.com/pkg/errors" "gopkg.in/go-playground/validator.v9" ) // Signup represents the Signup API method handler set. type Signup struct { - *signup.Repository + Repository SignupRepository // ADD OTHER STATE LIKE THE LOGGER AND CONFIG HERE. } +type SignupRepository interface { + Signup(ctx context.Context, claims auth.Claims, req signup.SignupRequest, now time.Time) (*signup.SignupResult, error) +} + // Signup godoc // @Summary Signup handles new account creation. // @Description Signup creates a new account and user in the system. diff --git a/cmd/web-api/handlers/user.go b/cmd/web-api/handlers/user.go index ddbd934..93551fe 100644 --- a/cmd/web-api/handlers/user.go +++ b/cmd/web-api/handlers/user.go @@ -13,6 +13,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" "geeks-accelerator/oss/saas-starter-kit/internal/user" "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "github.com/gorilla/schema" "github.com/pkg/errors" "gopkg.in/go-playground/validator.v9" @@ -22,13 +23,36 @@ import ( var sessionTtl = time.Hour * 24 // User represents the User API method handler set. -type User struct { - *user.Repository - Auth *user_auth.Repository - +type Users struct { + AuthRepo UserAuthRepository + UserRepo UserRepository // ADD OTHER STATE LIKE THE LOGGER AND CONFIG HERE. } +type UserAuthRepository interface { + SwitchAccount(ctx context.Context, claims auth.Claims, req user_auth.SwitchAccountRequest, expires time.Duration, + now time.Time, scopes ...string) (user_auth.Token, error) + Authenticate(ctx context.Context, req user_auth.AuthenticateRequest, expires time.Duration, now time.Time, scopes ...string) (user_auth.Token, error) + VirtualLogin(ctx context.Context, claims auth.Claims, req user_auth.VirtualLoginRequest, + expires time.Duration, now time.Time, scopes ...string) (user_auth.Token, error) + VirtualLogout(ctx context.Context, claims auth.Claims, expires time.Duration, now time.Time, scopes ...string) (user_auth.Token, error) +} + +type UserRepository interface { + Find(ctx context.Context, claims auth.Claims, req user.UserFindRequest) (user.Users, error) + //FindByAccount(ctx context.Context, claims auth.Claims, req user.UserFindByAccountRequest) (user.Users, error) + Read(ctx context.Context, claims auth.Claims, req user.UserReadRequest) (*user.User, error) + ReadByID(ctx context.Context, claims auth.Claims, id string) (*user.User, error) + Create(ctx context.Context, claims auth.Claims, req user.UserCreateRequest, now time.Time) (*user.User, error) + Update(ctx context.Context, claims auth.Claims, req user.UserUpdateRequest, now time.Time) error + UpdatePassword(ctx context.Context, claims auth.Claims, req user.UserUpdatePasswordRequest, now time.Time) error + Archive(ctx context.Context, claims auth.Claims, req user.UserArchiveRequest, now time.Time) error + Restore(ctx context.Context, claims auth.Claims, req user.UserRestoreRequest, now time.Time) error + Delete(ctx context.Context, claims auth.Claims, req user.UserDeleteRequest) error + ResetPassword(ctx context.Context, req user.UserResetPasswordRequest, now time.Time) (string, error) + ResetConfirm(ctx context.Context, req user.UserResetConfirmRequest, now time.Time) (*user.User, error) +} + // Find godoc // TODO: Need to implement unittests on users/find endpoint. There are none. // @Summary List users @@ -46,7 +70,7 @@ type User struct { // @Failure 400 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /users [get] -func (h *User) Find(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Users) Find(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, ok := ctx.Value(auth.Key).(auth.Claims) if !ok { return errors.New("claims missing from context") @@ -113,7 +137,7 @@ func (h *User) Find(ctx context.Context, w http.ResponseWriter, r *http.Request, // return web.RespondJsonError(ctx, w, err) //} - res, err := h.Repository.Find(ctx, claims, req) + res, err := h.UserRepo.Find(ctx, claims, req) if err != nil { return err } @@ -139,7 +163,7 @@ func (h *User) Find(ctx context.Context, w http.ResponseWriter, r *http.Request, // @Failure 404 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /users/{id} [get] -func (h *User) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Users) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, ok := ctx.Value(auth.Key).(auth.Claims) if !ok { return errors.New("claims missing from context") @@ -156,7 +180,7 @@ func (h *User) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, includeArchived = b } - res, err := h.Repository.Read(ctx, claims, user.UserReadRequest{ + res, err := h.UserRepo.Read(ctx, claims, user.UserReadRequest{ ID: params["id"], IncludeArchived: includeArchived, }) @@ -186,7 +210,7 @@ func (h *User) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /users [post] -func (h *User) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Users) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -205,7 +229,7 @@ func (h *User) Create(ctx context.Context, w http.ResponseWriter, r *http.Reques return web.RespondJsonError(ctx, w, err) } - res, err := h.Repository.Create(ctx, claims, req, v.Now) + usr, err := h.UserRepo.Create(ctx, claims, req, v.Now) if err != nil { cause := errors.Cause(err) switch cause { @@ -221,7 +245,7 @@ func (h *User) Create(ctx context.Context, w http.ResponseWriter, r *http.Reques } } - return web.RespondJson(ctx, w, res.Response(ctx), http.StatusCreated) + return web.RespondJson(ctx, w, usr.Response(ctx), http.StatusCreated) } // Read godoc @@ -237,7 +261,7 @@ func (h *User) Create(ctx context.Context, w http.ResponseWriter, r *http.Reques // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /users [patch] -func (h *User) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -256,7 +280,7 @@ func (h *User) Update(ctx context.Context, w http.ResponseWriter, r *http.Reques return web.RespondJsonError(ctx, w, err) } - err = h.Repository.Update(ctx, claims, req, v.Now) + err = h.UserRepo.Update(ctx, claims, req, v.Now) if err != nil { cause := errors.Cause(err) switch cause { @@ -288,7 +312,7 @@ func (h *User) Update(ctx context.Context, w http.ResponseWriter, r *http.Reques // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /users/password [patch] -func (h *User) UpdatePassword(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Users) UpdatePassword(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -307,7 +331,7 @@ func (h *User) UpdatePassword(ctx context.Context, w http.ResponseWriter, r *htt return web.RespondJsonError(ctx, w, err) } - err = h.Repository.UpdatePassword(ctx, claims, req, v.Now) + err = h.UserRepo.UpdatePassword(ctx, claims, req, v.Now) if err != nil { cause := errors.Cause(err) switch cause { @@ -341,7 +365,7 @@ func (h *User) UpdatePassword(ctx context.Context, w http.ResponseWriter, r *htt // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /users/archive [patch] -func (h *User) Archive(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Users) Archive(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -360,7 +384,7 @@ func (h *User) Archive(ctx context.Context, w http.ResponseWriter, r *http.Reque return web.RespondJsonError(ctx, w, err) } - err = h.Repository.Archive(ctx, claims, req, v.Now) + err = h.UserRepo.Archive(ctx, claims, req, v.Now) if err != nil { cause := errors.Cause(err) switch cause { @@ -392,13 +416,13 @@ func (h *User) Archive(ctx context.Context, w http.ResponseWriter, r *http.Reque // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /users/{id} [delete] -func (h *User) Delete(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Users) Delete(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, err := auth.ClaimsFromContext(ctx) if err != nil { return err } - err = h.Repository.Delete(ctx, claims, + err = h.UserRepo.Delete(ctx, claims, user.UserDeleteRequest{ID: params["id"]}) if err != nil { cause := errors.Cause(err) @@ -431,7 +455,7 @@ func (h *User) Delete(ctx context.Context, w http.ResponseWriter, r *http.Reques // @Failure 401 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /users/switch-account/{account_id} [patch] -func (h *User) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Users) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -442,7 +466,7 @@ func (h *User) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http return err } - tkn, err := h.Auth.SwitchAccount(ctx, claims, user_auth.SwitchAccountRequest{ + tkn, err := h.AuthRepo.SwitchAccount(ctx, claims, user_auth.SwitchAccountRequest{ AccountID: params["account_id"], }, sessionTtl, v.Now) if err != nil { @@ -478,7 +502,7 @@ func (h *User) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http // @Failure 401 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse // @Router /oauth/token [post] -func (h *User) Token(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *Users) Token(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -533,7 +557,7 @@ func (h *User) Token(ctx context.Context, w http.ResponseWriter, r *http.Request scopes = strings.Split(qv, ",") } - tkn, err := h.Auth.Authenticate(ctx, authReq, sessionTtl, v.Now, scopes...) + tkn, err := h.AuthRepo.Authenticate(ctx, authReq, sessionTtl, v.Now, scopes...) if err != nil { cause := errors.Cause(err) switch cause { diff --git a/cmd/web-api/handlers/user_account.go b/cmd/web-api/handlers/user_account.go index aec3075..57c0890 100644 --- a/cmd/web-api/handlers/user_account.go +++ b/cmd/web-api/handlers/user_account.go @@ -5,23 +5,44 @@ import ( "net/http" "strconv" "strings" + "time" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" + "geeks-accelerator/oss/saas-starter-kit/internal/user_account" + "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" + "github.com/pkg/errors" "gopkg.in/go-playground/validator.v9" ) // UserAccount represents the UserAccount API method handler set. type UserAccount struct { - *user_account.Repository - + UserInvite UserInviteRepository + Repository UserAccountRepository // ADD OTHER STATE LIKE THE LOGGER AND CONFIG HERE. } +type UserAccountRepository interface { + Find(ctx context.Context, claims auth.Claims, req user_account.UserAccountFindRequest) (user_account.UserAccounts, error) + FindByUserID(ctx context.Context, claims auth.Claims, userID string, includedArchived bool) (user_account.UserAccounts, error) + UserFindByAccount(ctx context.Context, claims auth.Claims, req user_account.UserFindByAccountRequest) (user_account.Users, error) + Create(ctx context.Context, claims auth.Claims, req user_account.UserAccountCreateRequest, now time.Time) (*user_account.UserAccount, error) + Read(ctx context.Context, claims auth.Claims, req user_account.UserAccountReadRequest) (*user_account.UserAccount, error) + Update(ctx context.Context, claims auth.Claims, req user_account.UserAccountUpdateRequest, now time.Time) error + Archive(ctx context.Context, claims auth.Claims, req user_account.UserAccountArchiveRequest, now time.Time) error + Delete(ctx context.Context, claims auth.Claims, req user_account.UserAccountDeleteRequest) error +} + +type UserInviteRepository interface { + SendUserInvites(ctx context.Context, claims auth.Claims, req invite.SendUserInvitesRequest, now time.Time) ([]string, error) + AcceptInvite(ctx context.Context, req invite.AcceptInviteRequest, now time.Time) (*user_account.UserAccount, error) + AcceptInviteUser(ctx context.Context, req invite.AcceptInviteUserRequest, now time.Time) (*user_account.UserAccount, error) +} + // Find godoc // TODO: Need to implement unittests on user_accounts/find endpoint. There are none. // @Summary List user accounts diff --git a/cmd/web-api/main.go b/cmd/web-api/main.go index 9a48ec4..ede15da 100644 --- a/cmd/web-api/main.go +++ b/cmd/web-api/main.go @@ -35,6 +35,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/user_account" "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/ec2metadata" @@ -435,7 +436,7 @@ func main() { projectRoute, err := project_route.New(cfg.Service.BaseUrl, cfg.Project.WebAppBaseUrl) if err != nil { - log.Fatalf("main : project routes : %+v", cfg.Service.BaseUrl, err) + log.Fatalf("main : project routes : %s: %+v", cfg.Service.BaseUrl, err) } usrRepo := user.NewRepository(masterDb, projectRoute.UserResetPassword, notifyEmail, cfg.Project.SharedSecretKey) diff --git a/cmd/web-api/tests/account_test.go b/cmd/web-api/tests/account_test.go index 918aa6c..f7fecfd 100644 --- a/cmd/web-api/tests/account_test.go +++ b/cmd/web-api/tests/account_test.go @@ -13,6 +13,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/tests" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" + "github.com/pborman/uuid" ) diff --git a/cmd/web-app/handlers/account.go b/cmd/web-app/handlers/account.go index 3ba3e0f..0656fb0 100644 --- a/cmd/web-app/handlers/account.go +++ b/cmd/web-app/handlers/account.go @@ -2,9 +2,7 @@ package handlers import ( "context" - "net/http" - "time" - + "geeks-accelerator/oss/saas-starter-kit/cmd/web-api/handlers" "geeks-accelerator/oss/saas-starter-kit/internal/account" "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" "geeks-accelerator/oss/saas-starter-kit/internal/geonames" @@ -12,19 +10,21 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + + "net/http" + "time" + "github.com/gorilla/schema" - "github.com/jmoiron/sqlx" "github.com/pkg/errors" ) // Account represents the Account API method handler set. type Account struct { - AccountRepo *account.Repository - AccountPrefRepo *account_preference.Repository - AuthRepo *user_auth.Repository + AccountRepo handlers.AccountRepository + AccountPrefRepo handlers.AccountPrefRepository + AuthRepo handlers.UserAuthRepository + GeoRepo GeoRepository Authenticator *auth.Authenticator - MasterDB *sqlx.DB Renderer web.Renderer } @@ -248,14 +248,14 @@ func (h *Account) Update(ctx context.Context, w http.ResponseWriter, r *http.Req data["account"] = acc.Response(ctx) - data["timezones"], err = geonames.ListTimezones(ctx, h.MasterDB) + data["timezones"], err = h.GeoRepo.ListTimezones(ctx) if err != nil { return false, err } data["geonameCountries"] = geonames.ValidGeonameCountries(ctx) - data["countries"], err = geonames.FindCountries(ctx, h.MasterDB, "name", "") + data["countries"], err = h.GeoRepo.FindCountries(ctx, "name", "") if err != nil { return false, err } diff --git a/cmd/web-app/handlers/api_geo.go b/cmd/web-app/handlers/api_geo.go index 3e4c9af..dfc3ad6 100644 --- a/cmd/web-app/handlers/api_geo.go +++ b/cmd/web-app/handlers/api_geo.go @@ -8,14 +8,25 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/geonames" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" - "github.com/jmoiron/sqlx" + + //"github.com/jmoiron/sqlx" "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis" ) // Check provides support for orchestration geo endpoints. type Geo struct { - MasterDB *sqlx.DB - Redis *redis.Client + Redis *redis.Client + GeoRepo GeoRepository +} + +type GeoRepository interface { + FindGeonames(ctx context.Context, orderBy, where string, args ...interface{}) ([]*geonames.Geoname, error) + FindGeonamePostalCodes(ctx context.Context, where string, args ...interface{}) ([]string, error) + FindGeonameRegions(ctx context.Context, orderBy, where string, args ...interface{}) ([]*geonames.Region, error) + FindCountries(ctx context.Context, orderBy, where string, args ...interface{}) ([]*geonames.Country, error) + FindCountryTimezones(ctx context.Context, orderBy, where string, args ...interface{}) ([]*geonames.CountryTimezone, error) + ListTimezones(ctx context.Context) ([]string, error) + LoadGeonames(ctx context.Context, rr chan<- interface{}, countries ...string) } // GeonameByPostalCode... @@ -39,7 +50,7 @@ func (h *Geo) GeonameByPostalCode(ctx context.Context, w http.ResponseWriter, r where := strings.Join(filters, " AND ") - res, err := geonames.FindGeonames(ctx, h.MasterDB, "postal_code", where, args...) + res, err := h.GeoRepo.FindGeonames(ctx, "postal_code", where, args...) if err != nil { fmt.Printf("%+v", err) return web.RespondJsonError(ctx, w, err) @@ -74,7 +85,7 @@ func (h *Geo) PostalCodesAutocomplete(ctx context.Context, w http.ResponseWriter where := strings.Join(filters, " AND ") - res, err := geonames.FindGeonamePostalCodes(ctx, h.MasterDB, where, args...) + res, err := h.GeoRepo.FindGeonamePostalCodes(ctx, where, args...) if err != nil { return web.RespondJsonError(ctx, w, err) } @@ -101,7 +112,7 @@ func (h *Geo) RegionsAutocomplete(ctx context.Context, w http.ResponseWriter, r where := strings.Join(filters, " AND ") - res, err := geonames.FindGeonameRegions(ctx, h.MasterDB, "state_name", where, args...) + res, err := h.GeoRepo.FindGeonameRegions(ctx, "state_name", where, args...) if err != nil { fmt.Printf("%+v", err) return web.RespondJsonError(ctx, w, err) @@ -144,7 +155,7 @@ func (h *Geo) CountryTimezones(ctx context.Context, w http.ResponseWriter, r *ht where := strings.Join(filters, " AND ") - res, err := geonames.FindCountryTimezones(ctx, h.MasterDB, "timezone_id", where, args...) + res, err := h.GeoRepo.FindCountryTimezones(ctx, "timezone_id", where, args...) if err != nil { return web.RespondJsonError(ctx, w, err) } diff --git a/cmd/web-app/handlers/projects.go b/cmd/web-app/handlers/projects.go index 8a375fe..9c54fa1 100644 --- a/cmd/web-app/handlers/projects.go +++ b/cmd/web-app/handlers/projects.go @@ -3,6 +3,7 @@ package handlers import ( "context" "fmt" + "geeks-accelerator/oss/saas-starter-kit/cmd/web-api/handlers" "net/http" "strings" @@ -12,6 +13,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" "geeks-accelerator/oss/saas-starter-kit/internal/project" + "github.com/gorilla/schema" "github.com/pkg/errors" "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis" @@ -19,7 +21,7 @@ import ( // Projects represents the Projects API method handler set. type Projects struct { - ProjectRepo *project.Repository + ProjectRepo handlers.ProjectRepository Redis *redis.Client Renderer web.Renderer } diff --git a/cmd/web-app/handlers/routes.go b/cmd/web-app/handlers/routes.go index 01bd4b2..0aee9d7 100644 --- a/cmd/web-app/handlers/routes.go +++ b/cmd/web-app/handlers/routes.go @@ -9,20 +9,23 @@ import ( "path/filepath" "time" - "geeks-accelerator/oss/saas-starter-kit/internal/account" - "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" + "geeks-accelerator/oss/saas-starter-kit/cmd/web-api/handlers" + //"geeks-accelerator/oss/saas-starter-kit/internal/account" + //"geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" "geeks-accelerator/oss/saas-starter-kit/internal/mid" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" + + //"geeks-accelerator/oss/saas-starter-kit/internal/project" "geeks-accelerator/oss/saas-starter-kit/internal/project_route" - "geeks-accelerator/oss/saas-starter-kit/internal/signup" - "geeks-accelerator/oss/saas-starter-kit/internal/user" - "geeks-accelerator/oss/saas-starter-kit/internal/user_account" - "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" - "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + // "geeks-accelerator/oss/saas-starter-kit/internal/signup" + // "geeks-accelerator/oss/saas-starter-kit/internal/user" + // "geeks-accelerator/oss/saas-starter-kit/internal/user_account" + // "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" + // "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "github.com/ikeikeikeike/go-sitemap-generator/v2/stm" "github.com/jmoiron/sqlx" "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis" @@ -39,14 +42,15 @@ type AppContext struct { Env webcontext.Env MasterDB *sqlx.DB Redis *redis.Client - UserRepo *user.Repository - UserAccountRepo *user_account.Repository - AccountRepo *account.Repository - AccountPrefRepo *account_preference.Repository - AuthRepo *user_auth.Repository - SignupRepo *signup.Repository - InviteRepo *invite.Repository - ProjectRepo *project.Repository + UserRepo handlers.UserRepository + UserAccountRepo handlers.UserAccountRepository + AccountRepo handlers.AccountRepository + AccountPrefRepo handlers.AccountPrefRepository + AuthRepo handlers.UserAuthRepository + SignupRepo handlers.SignupRepository + InviteRepo handlers.UserInviteRepository + ProjectRepo handlers.ProjectRepository + GeoRepo GeoRepository Authenticator *auth.Authenticator StaticDir string TemplateDir string @@ -117,7 +121,7 @@ func APP(shutdown chan os.Signal, appCtx *AppContext) http.Handler { UserAccountRepo: appCtx.UserAccountRepo, AuthRepo: appCtx.AuthRepo, InviteRepo: appCtx.InviteRepo, - MasterDB: appCtx.MasterDB, + GeoRepo: appCtx.GeoRepo, Redis: appCtx.Redis, Renderer: appCtx.Renderer, } @@ -134,12 +138,12 @@ func APP(shutdown chan os.Signal, appCtx *AppContext) http.Handler { app.Handle("GET", "/users", us.Index, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth()) // Register user management and authentication endpoints. - u := User{ + u := UserRepos{ UserRepo: appCtx.UserRepo, UserAccountRepo: appCtx.UserAccountRepo, AccountRepo: appCtx.AccountRepo, AuthRepo: appCtx.AuthRepo, - MasterDB: appCtx.MasterDB, + GeoRepo: appCtx.GeoRepo, Renderer: appCtx.Renderer, } app.Handle("POST", "/user/login", u.Login) @@ -168,7 +172,7 @@ func APP(shutdown chan os.Signal, appCtx *AppContext) http.Handler { AccountPrefRepo: appCtx.AccountPrefRepo, AuthRepo: appCtx.AuthRepo, Authenticator: appCtx.Authenticator, - MasterDB: appCtx.MasterDB, + GeoRepo: appCtx.GeoRepo, Renderer: appCtx.Renderer, } app.Handle("POST", "/account/update", acc.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) @@ -180,7 +184,7 @@ func APP(shutdown chan os.Signal, appCtx *AppContext) http.Handler { s := Signup{ SignupRepo: appCtx.SignupRepo, AuthRepo: appCtx.AuthRepo, - MasterDB: appCtx.MasterDB, + GeoRepo: appCtx.GeoRepo, Renderer: appCtx.Renderer, } // This route is not authenticated @@ -197,8 +201,8 @@ func APP(shutdown chan os.Signal, appCtx *AppContext) http.Handler { // Register geo g := Geo{ - MasterDB: appCtx.MasterDB, - Redis: appCtx.Redis, + GeoRepo: appCtx.GeoRepo, + Redis: appCtx.Redis, } app.Handle("GET", "/geo/regions/autocomplete", g.RegionsAutocomplete) app.Handle("GET", "/geo/postal_codes/autocomplete", g.PostalCodesAutocomplete) diff --git a/cmd/web-app/handlers/signup.go b/cmd/web-app/handlers/signup.go index cb23c48..964d90c 100644 --- a/cmd/web-app/handlers/signup.go +++ b/cmd/web-app/handlers/signup.go @@ -2,6 +2,7 @@ package handlers import ( "context" + "geeks-accelerator/oss/saas-starter-kit/cmd/web-api/handlers" "net/http" "time" @@ -13,6 +14,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" "geeks-accelerator/oss/saas-starter-kit/internal/signup" "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "github.com/gorilla/schema" "github.com/jmoiron/sqlx" "github.com/pkg/errors" @@ -20,8 +22,9 @@ import ( // Signup represents the Signup API method handler set. type Signup struct { - SignupRepo *signup.Repository - AuthRepo *user_auth.Repository + SignupRepo handlers.SignupRepository + AuthRepo handlers.UserAuthRepository + GeoRepo GeoRepository MasterDB *sqlx.DB Renderer web.Renderer } @@ -108,7 +111,7 @@ func (h *Signup) Step1(ctx context.Context, w http.ResponseWriter, r *http.Reque data["geonameCountries"] = geonames.ValidGeonameCountries(ctx) - data["countries"], err = geonames.FindCountries(ctx, h.MasterDB, "name", "") + data["countries"], err = h.GeoRepo.FindCountries(ctx, "name", "") if err != nil { return err } diff --git a/cmd/web-app/handlers/user.go b/cmd/web-app/handlers/user.go index b47b4c9..57b92f4 100644 --- a/cmd/web-app/handlers/user.go +++ b/cmd/web-app/handlers/user.go @@ -8,8 +8,9 @@ import ( "strings" "time" + "geeks-accelerator/oss/saas-starter-kit/cmd/web-api/handlers" "geeks-accelerator/oss/saas-starter-kit/internal/account" - "geeks-accelerator/oss/saas-starter-kit/internal/geonames" + "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" @@ -17,6 +18,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/user" "geeks-accelerator/oss/saas-starter-kit/internal/user_account" "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "github.com/gorilla/schema" "github.com/gorilla/sessions" "github.com/jmoiron/sqlx" @@ -24,13 +26,15 @@ import ( ) // User represents the User API method handler set. -type User struct { - UserRepo *user.Repository - AuthRepo *user_auth.Repository - UserAccountRepo *user_account.Repository - AccountRepo *account.Repository +type UserRepos struct { + UserRepo handlers.UserRepository + AuthRepo handlers.UserAuthRepository + UserAccountRepo handlers.UserAccountRepository + AccountRepo handlers.AccountRepository + GeoRepo GeoRepository MasterDB *sqlx.DB Renderer web.Renderer + SecretKey string } func urlUserVirtualLogin(userID string) string { @@ -44,7 +48,7 @@ type UserLoginRequest struct { } // Login handles authenticating a user into the system. -func (h *User) Login(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h UserRepos) Login(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { ctxValues, err := webcontext.ContextValues(ctx) if err != nil { @@ -132,7 +136,7 @@ func (h *User) Login(ctx context.Context, w http.ResponseWriter, r *http.Request } // Logout handles removing authentication for the user. -func (h *User) Logout(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *UserRepos) Logout(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { sess := webcontext.ContextSession(ctx) @@ -148,7 +152,7 @@ func (h *User) Logout(ctx context.Context, w http.ResponseWriter, r *http.Reques } // ResetPassword allows a user to perform forgot password. -func (h *User) ResetPassword(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *UserRepos) ResetPassword(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { ctxValues, err := webcontext.ContextValues(ctx) if err != nil { @@ -208,7 +212,7 @@ func (h *User) ResetPassword(ctx context.Context, w http.ResponseWriter, r *http } // ResetConfirm handles changing a users password after they have clicked on the link emailed. -func (h *User) ResetConfirm(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *UserRepos) ResetConfirm(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { resetHash := params["hash"] @@ -278,7 +282,7 @@ func (h *User) ResetConfirm(ctx context.Context, w http.ResponseWriter, r *http. return true, web.Redirect(ctx, w, r, "/", http.StatusFound) } - _, err = h.UserRepo.ParseResetHash(ctx, resetHash, ctxValues.Now) + _, err = user.ParseResetHash(ctx, h.SecretKey, resetHash, ctxValues.Now) if err != nil { switch errors.Cause(err) { case user.ErrResetExpired: @@ -316,7 +320,7 @@ func (h *User) ResetConfirm(ctx context.Context, w http.ResponseWriter, r *http. } // View handles displaying the current user profile. -func (h *User) View(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *UserRepos) View(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { data := make(map[string]interface{}) f := func() error { @@ -356,7 +360,7 @@ func (h *User) View(ctx context.Context, w http.ResponseWriter, r *http.Request, } // Update handles allowing the current user to update their profile. -func (h *User) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *UserRepos) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { ctxValues, err := webcontext.ContextValues(ctx) if err != nil { @@ -453,7 +457,7 @@ func (h *User) Update(ctx context.Context, w http.ResponseWriter, r *http.Reques data["user"] = usr.Response(ctx) - data["timezones"], err = geonames.ListTimezones(ctx, h.MasterDB) + data["timezones"], err = h.GeoRepo.ListTimezones(ctx) if err != nil { return err } @@ -472,7 +476,7 @@ func (h *User) Update(ctx context.Context, w http.ResponseWriter, r *http.Reques } // Account handles displaying the Account for the current user. -func (h *User) Account(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *UserRepos) Account(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { data := make(map[string]interface{}) f := func() error { @@ -499,7 +503,7 @@ func (h *User) Account(ctx context.Context, w http.ResponseWriter, r *http.Reque } // VirtualLogin handles switching the scope of the context to another user. -func (h *User) VirtualLogin(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *UserRepos) VirtualLogin(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { ctxValues, err := webcontext.ContextValues(ctx) if err != nil { @@ -634,7 +638,7 @@ func (h *User) VirtualLogin(ctx context.Context, w http.ResponseWriter, r *http. } // VirtualLogout handles switching the scope back to the user who initiated the virtual login. -func (h *User) VirtualLogout(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *UserRepos) VirtualLogout(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { ctxValues, err := webcontext.ContextValues(ctx) if err != nil { @@ -708,7 +712,7 @@ func (h *User) VirtualLogout(ctx context.Context, w http.ResponseWriter, r *http } // VirtualLogin handles switching the scope of the context to another user. -func (h *User) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +func (h *UserRepos) SwitchAccount(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { ctxValues, err := webcontext.ContextValues(ctx) if err != nil { diff --git a/cmd/web-app/handlers/users.go b/cmd/web-app/handlers/users.go index 86e5f8b..8b322e7 100644 --- a/cmd/web-app/handlers/users.go +++ b/cmd/web-app/handlers/users.go @@ -3,11 +3,11 @@ package handlers import ( "context" "fmt" + "geeks-accelerator/oss/saas-starter-kit/cmd/web-api/handlers" "net/http" "strings" "time" - "geeks-accelerator/oss/saas-starter-kit/internal/geonames" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/datatable" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" @@ -17,6 +17,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/user_account" "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "github.com/dustin/go-humanize/english" "github.com/gorilla/schema" "github.com/jmoiron/sqlx" @@ -26,10 +27,12 @@ import ( // Users represents the Users API method handler set. type Users struct { - UserRepo *user.Repository - UserAccountRepo *user_account.Repository - AuthRepo *user_auth.Repository - InviteRepo *invite.Repository + UserRepo handlers.UserRepository + AccountRepo handlers.AccountRepository + UserAccountRepo handlers.UserAccountRepository + AuthRepo handlers.UserAuthRepository + InviteRepo handlers.UserInviteRepository + GeoRepo GeoRepository MasterDB *sqlx.DB Redis *redis.Client Renderer web.Renderer @@ -281,7 +284,7 @@ func (h *Users) Create(ctx context.Context, w http.ResponseWriter, r *http.Reque return nil } - data["timezones"], err = geonames.ListTimezones(ctx, h.MasterDB) + data["timezones"], err = h.GeoRepo.ListTimezones(ctx) if err != nil { return err } @@ -519,7 +522,7 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque data["user"] = usr.Response(ctx) - data["timezones"], err = geonames.ListTimezones(ctx, h.MasterDB) + data["timezones"], err = h.GeoRepo.ListTimezones(ctx) if err != nil { return err } @@ -798,7 +801,7 @@ func (h *Users) InviteAccept(ctx context.Context, w http.ResponseWriter, r *http return nil } - data["timezones"], err = geonames.ListTimezones(ctx, h.MasterDB) + data["timezones"], err = h.GeoRepo.ListTimezones(ctx) if err != nil { return err } diff --git a/cmd/web-app/main.go b/cmd/web-app/main.go index 659d576..8fe14f4 100644 --- a/cmd/web-app/main.go +++ b/cmd/web-app/main.go @@ -7,6 +7,7 @@ import ( "expvar" "fmt" "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" + "geeks-accelerator/oss/saas-starter-kit/internal/geonames" "geeks-accelerator/oss/saas-starter-kit/internal/project" "geeks-accelerator/oss/saas-starter-kit/internal/signup" "geeks-accelerator/oss/saas-starter-kit/internal/user_account" @@ -40,6 +41,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" "geeks-accelerator/oss/saas-starter-kit/internal/project_route" "geeks-accelerator/oss/saas-starter-kit/internal/user" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/ec2metadata" @@ -443,6 +445,7 @@ func main() { usrRepo := user.NewRepository(masterDb, projectRoute.UserResetPassword, notifyEmail, cfg.Project.SharedSecretKey) usrAccRepo := user_account.NewRepository(masterDb) accRepo := account.NewRepository(masterDb) + geoRepo := geonames.NewRepository(masterDb) accPrefRepo := account_preference.NewRepository(masterDb) authRepo := user_auth.NewRepository(masterDb, authenticator, usrRepo, usrAccRepo, accPrefRepo) signupRepo := signup.NewRepository(masterDb, usrRepo, usrAccRepo, accRepo) @@ -450,9 +453,9 @@ func main() { prjRepo := project.NewRepository(masterDb) appCtx := &handlers.AppContext{ - Log: log, - Env: cfg.Env, - MasterDB: masterDb, + Log: log, + Env: cfg.Env, + //MasterDB: masterDb, Redis: redisClient, TemplateDir: cfg.Service.TemplateDir, StaticDir: cfg.Service.StaticFiles.Dir, @@ -462,6 +465,7 @@ func main() { AccountRepo: accRepo, AccountPrefRepo: accPrefRepo, AuthRepo: authRepo, + GeoRepo: geoRepo, SignupRepo: signupRepo, InviteRepo: inviteRepo, ProjectRepo: prjRepo, diff --git a/docker-compose.yaml b/docker-compose.yaml index 7c538bb..128c059 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -26,9 +26,9 @@ services: redis: image: redis:latest expose: - - "6379" + - "6378" ports: - - "6379:6379" + - "6378:6379" networks: main: aliases: diff --git a/internal/account/account_preference/account_preference.go b/internal/account/account_preference/account_preference.go index 3dd0e41..6fb3bad 100644 --- a/internal/account/account_preference/account_preference.go +++ b/internal/account/account_preference/account_preference.go @@ -7,6 +7,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/account" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" + "github.com/huandu/go-sqlbuilder" "github.com/jmoiron/sqlx" "github.com/pborman/uuid" diff --git a/internal/geonames/countries.go b/internal/geonames/countries.go index 4eaaf65..342ef5e 100644 --- a/internal/geonames/countries.go +++ b/internal/geonames/countries.go @@ -2,8 +2,8 @@ package geonames import ( "context" + "github.com/huandu/go-sqlbuilder" - "github.com/jmoiron/sqlx" "github.com/pkg/errors" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) @@ -14,7 +14,7 @@ const ( ) // FindCountries .... -func FindCountries(ctx context.Context, dbConn *sqlx.DB, orderBy, where string, args ...interface{}) ([]*Country, error) { +func (repo *Repository) FindCountries(ctx context.Context, orderBy, where string, args ...interface{}) ([]*Country, error) { span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindCountries") defer span.Finish() @@ -32,11 +32,11 @@ func FindCountries(ctx context.Context, dbConn *sqlx.DB, orderBy, where string, } queryStr, queryArgs := query.Build() - queryStr = dbConn.Rebind(queryStr) + queryStr = repo.DbConn.Rebind(queryStr) args = append(args, queryArgs...) // fetch all places from the db - rows, err := dbConn.QueryContext(ctx, queryStr, args...) + rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) err = errors.WithMessage(err, "find countries failed") diff --git a/internal/geonames/country_timezones.go b/internal/geonames/country_timezones.go index d16d92d..1e4546a 100644 --- a/internal/geonames/country_timezones.go +++ b/internal/geonames/country_timezones.go @@ -2,8 +2,8 @@ package geonames import ( "context" + "github.com/huandu/go-sqlbuilder" - "github.com/jmoiron/sqlx" "github.com/pkg/errors" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) @@ -14,7 +14,7 @@ const ( ) // FindCountryTimezones .... -func FindCountryTimezones(ctx context.Context, dbConn *sqlx.DB, orderBy, where string, args ...interface{}) ([]*CountryTimezone, error) { +func (repo *Repository) FindCountryTimezones(ctx context.Context, orderBy, where string, args ...interface{}) ([]*CountryTimezone, error) { span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindCountryTimezones") defer span.Finish() @@ -32,11 +32,11 @@ func FindCountryTimezones(ctx context.Context, dbConn *sqlx.DB, orderBy, where s } queryStr, queryArgs := query.Build() - queryStr = dbConn.Rebind(queryStr) + queryStr = repo.DbConn.Rebind(queryStr) args = append(args, queryArgs...) // Fetch all country timezones from the db. - rows, err := dbConn.QueryContext(ctx, queryStr, args...) + rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) err = errors.WithMessage(err, "find country timezones failed") @@ -64,8 +64,8 @@ func FindCountryTimezones(ctx context.Context, dbConn *sqlx.DB, orderBy, where s return resp, nil } -func ListTimezones(ctx context.Context, dbConn *sqlx.DB) ([]string, error) { - res, err := FindCountryTimezones(ctx, dbConn, "timezone_id", "") +func (repo *Repository) ListTimezones(ctx context.Context) ([]string, error) { + res, err := repo.FindCountryTimezones(ctx, "timezone_id", "") if err != nil { return nil, err } diff --git a/internal/geonames/geonames.go b/internal/geonames/geonames.go index 47a4e48..9fba47c 100644 --- a/internal/geonames/geonames.go +++ b/internal/geonames/geonames.go @@ -12,8 +12,9 @@ import ( "strings" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" + "github.com/huandu/go-sqlbuilder" - "github.com/jmoiron/sqlx" + // "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/sethgrid/pester" "github.com/shopspring/decimal" @@ -43,7 +44,7 @@ func ValidGeonameCountries(ctx context.Context) []string { } // FindGeonames .... -func FindGeonames(ctx context.Context, dbConn *sqlx.DB, orderBy, where string, args ...interface{}) ([]*Geoname, error) { +func (repo *Repository) FindGeonames(ctx context.Context, orderBy, where string, args ...interface{}) ([]*Geoname, error) { span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindGeonames") defer span.Finish() @@ -61,11 +62,11 @@ func FindGeonames(ctx context.Context, dbConn *sqlx.DB, orderBy, where string, a } queryStr, queryArgs := query.Build() - queryStr = dbConn.Rebind(queryStr) + queryStr = repo.DbConn.Rebind(queryStr) args = append(args, queryArgs...) // fetch all places from the db - rows, err := dbConn.QueryContext(ctx, queryStr, args...) + rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) err = errors.WithMessage(err, "find regions failed") @@ -93,7 +94,7 @@ func FindGeonames(ctx context.Context, dbConn *sqlx.DB, orderBy, where string, a } // FindGeonamePostalCodes .... -func FindGeonamePostalCodes(ctx context.Context, dbConn *sqlx.DB, where string, args ...interface{}) ([]string, error) { +func (repo *Repository) FindGeonamePostalCodes(ctx context.Context, where string, args ...interface{}) ([]string, error) { span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindGeonamePostalCodes") defer span.Finish() @@ -106,11 +107,11 @@ func FindGeonamePostalCodes(ctx context.Context, dbConn *sqlx.DB, where string, } queryStr, queryArgs := query.Build() - queryStr = dbConn.Rebind(queryStr) + queryStr = repo.DbConn.Rebind(queryStr) args = append(args, queryArgs...) // fetch all places from the db - rows, err := dbConn.QueryContext(ctx, queryStr, args...) + rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) err = errors.WithMessage(err, "find regions failed") @@ -138,7 +139,7 @@ func FindGeonamePostalCodes(ctx context.Context, dbConn *sqlx.DB, where string, } // FindGeonameRegions .... -func FindGeonameRegions(ctx context.Context, dbConn *sqlx.DB, orderBy, where string, args ...interface{}) ([]*Region, error) { +func (repo *Repository) FindGeonameRegions(ctx context.Context, orderBy, where string, args ...interface{}) ([]*Region, error) { span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindGeonameRegions") defer span.Finish() @@ -156,11 +157,11 @@ func FindGeonameRegions(ctx context.Context, dbConn *sqlx.DB, orderBy, where str } queryStr, queryArgs := query.Build() - queryStr = dbConn.Rebind(queryStr) + queryStr = repo.DbConn.Rebind(queryStr) args = append(args, queryArgs...) // fetch all places from the db - rows, err := dbConn.QueryContext(ctx, queryStr, args...) + rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) err = errors.WithMessage(err, "find regions failed") @@ -194,7 +195,7 @@ func FindGeonameRegions(ctx context.Context, dbConn *sqlx.DB, orderBy, where str // Possible types sent to the channel are limited to: // - error // - GeoName -func LoadGeonames(ctx context.Context, rr chan<- interface{}, countries ...string) { +func (repo *Repository) LoadGeonames(ctx context.Context, rr chan<- interface{}, countries ...string) { defer close(rr) if len(countries) == 0 { diff --git a/internal/geonames/models.go b/internal/geonames/models.go index a70274d..68204e8 100644 --- a/internal/geonames/models.go +++ b/internal/geonames/models.go @@ -1,6 +1,18 @@ package geonames import "github.com/shopspring/decimal" +import "github.com/jmoiron/sqlx" + +type Repository struct { + DbConn *sqlx.DB +} + +// NewRepository creates a new Repository that defines dependencies for Project. +func NewRepository(db *sqlx.DB) *Repository { + return &Repository{ + DbConn: db, + } +} type Geoname struct { CountryCode string // US diff --git a/internal/schema/migrations.go b/internal/schema/migrations.go index fe6a3c9..14485c6 100644 --- a/internal/schema/migrations.go +++ b/internal/schema/migrations.go @@ -9,6 +9,7 @@ import ( "strings" "geeks-accelerator/oss/saas-starter-kit/internal/geonames" + "github.com/geeks-accelerator/sqlxmigrate" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" @@ -19,6 +20,7 @@ import ( // migrationList returns a list of migrations to be executed. If the id of the // migration already exists in the migrations table it will be skipped. func migrationList(ctx context.Context, db *sqlx.DB, log *log.Logger, isUnittest bool) []*sqlxmigrate.Migration { + geoRepo := geonames.NewRepository(db) return []*sqlxmigrate.Migration{ // Create table users. { @@ -253,7 +255,7 @@ func migrationList(ctx context.Context, db *sqlx.DB, log *log.Logger, isUnittest } else { resChan := make(chan interface{}) - go geonames.LoadGeonames(ctx, resChan) + go geoRepo.LoadGeonames(ctx, resChan) for r := range resChan { switch v := r.(type) { diff --git a/internal/user_account/invite/invite.go b/internal/user_account/invite/invite.go index 0ee8262..274b441 100644 --- a/internal/user_account/invite/invite.go +++ b/internal/user_account/invite/invite.go @@ -6,11 +6,12 @@ import ( "strings" "time" - "geeks-accelerator/oss/saas-starter-kit/internal/account" + //"geeks-accelerator/oss/saas-starter-kit/internal/account" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/user" "geeks-accelerator/oss/saas-starter-kit/internal/user_account" + "github.com/pkg/errors" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) @@ -40,7 +41,7 @@ func (repo *Repository) SendUserInvites(ctx context.Context, claims auth.Claims, } // Ensure the claims can modify the account specified in the request. - err = account.CanModifyAccount(ctx, claims, repo.DbConn, req.AccountID) + err = repo.Account.CanModifyAccount(ctx, claims, req.AccountID) if err != nil { return nil, err } diff --git a/internal/user_auth/auth.go b/internal/user_auth/auth.go index 4e90b10..dcb9dce 100644 --- a/internal/user_auth/auth.go +++ b/internal/user_auth/auth.go @@ -11,6 +11,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/user" "geeks-accelerator/oss/saas-starter-kit/internal/user_account" + "github.com/huandu/go-sqlbuilder" "github.com/lib/pq" "github.com/pkg/errors" @@ -100,7 +101,8 @@ func (repo *Repository) SwitchAccount(ctx context.Context, claims auth.Claims, r } // VirtualLogin allows users to mock being logged in as other users. -func (repo *Repository) VirtualLogin(ctx context.Context, claims auth.Claims, req VirtualLoginRequest, expires time.Duration, now time.Time, scopes ...string) (Token, error) { +func (repo *Repository) VirtualLogin(ctx context.Context, claims auth.Claims, req VirtualLoginRequest, + expires time.Duration, now time.Time, scopes ...string) (Token, error) { span, ctx := tracer.StartSpanFromContext(ctx, "internal.user_auth.VirtualLogin") defer span.Finish()