diff --git a/application/applicationservice/applicationservice.go b/application/applicationservice/applicationservice.go index 5cf4b7c..d165b07 100644 --- a/application/applicationservice/applicationservice.go +++ b/application/applicationservice/applicationservice.go @@ -17,15 +17,16 @@ var ( // Service holds the domain service repositories type Service struct { - Users *userService - Rides *rideService - Visits *visitService - Provider *providerService - Notification *notificationService - Profile *profileService - Organization *organizationService - Zipcodes *zipcodeService - Plan *planService + Users *userService + Rides *rideService + Visits *visitService + Provider *providerService + Notification *notificationService + Profile *profileService + Organization *organizationService + Zipcodes *zipcodeService + Plan *planService + PasswordReset *passwordResetService } // New returns a new domain Service instance @@ -34,15 +35,16 @@ func New(svc *service.Service, mapper *entitymapping.Mapper, notification *notif bcbsi := bcbsi.New(cfg) instance = &Service{ - Users: newUserService(svc, mapper, bcbsi, cfg), - Rides: newRideService(svc, mapper), - Visits: newVisitService(svc, mapper), - Provider: newProviderService(svc, mapper), - Notification: newNotificationService(svc, mapper, notification, cfg), - Profile: newProfileService(svc, mapper), - Organization: newOrganizationService(svc, mapper), - Zipcodes: newZipcodeService(svc, mapper), - Plan: newPlanService(svc, mapper), + Users: newUserService(svc, mapper, bcbsi, cfg), + Rides: newRideService(svc, mapper), + Visits: newVisitService(svc, mapper), + Provider: newProviderService(svc, mapper), + Notification: newNotificationService(svc, mapper, notification, cfg), + Profile: newProfileService(svc, mapper), + Organization: newOrganizationService(svc, mapper), + Zipcodes: newZipcodeService(svc, mapper), + Plan: newPlanService(svc, mapper), + PasswordReset: newPasswordResetService(svc, mapper), } }) diff --git a/application/applicationservice/user.go b/application/applicationservice/user.go index a0abcf4..024b1af 100644 --- a/application/applicationservice/user.go +++ b/application/applicationservice/user.go @@ -77,6 +77,16 @@ func (s *userService) GetByMemberID(memberID string) (retVal viewmodel.User, err return s.mapEntity.User.ToUserModel(user), nil } +// GetByEmail returns a specific user by its email +func (s *userService) GetByEmail(email string) (retVal viewmodel.User, err error) { + user, err := s.svc.Users.GetByEmail(email) + if err != nil { + return retVal, errors.Wrap(err) + } + + return s.mapEntity.User.ToUserModel(user), nil +} + // Login returns a specific user by email and pass func (s *userService) FullLogin(loginType string, key string, pass string, profile string) (retVal viewmodel.User, err error) { user, err := s.svc.Users.FullLogin(loginType, key, pass, profile) @@ -250,6 +260,10 @@ func (s *userService) CheckAndCreateMember(user viewmodel.User, provider viewmod } } + if entityUser.UUID == "" { + entityUser = s.mapEntity.User.ToUserEntity(user) + } + var eligibility viewmodel.Eligibility loc, _ := time.LoadLocation("America/Chicago") eligibility.TrackingID = s.rangeIn(1000000, 9999999) @@ -261,7 +275,7 @@ func (s *userService) CheckAndCreateMember(user viewmodel.User, provider viewmod if err != nil { return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding provider by UUID: %s", err.Error())} } - } else { + } else if entityProvider.InternalID != "" { entityProvider, err = s.svc.Provider.GetByNPI(entityProvider.InternalID, entityAuthorUser) if err != nil { return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding provider by NPI: %s", err.Error())} @@ -269,7 +283,8 @@ func (s *userService) CheckAndCreateMember(user viewmodel.User, provider viewmod } if entityProvider.InternalID == "" { - return viewmodel.User{}, &viewmodel.ValidationError{Message: "Provider not found"} + eligibility.Provider.ProviderNPI = "1699849786" + eligibility.Provider.ProviderName = "LITHOLINK CORPORATION" } else { eligibility.Provider.ProviderNPI = entityProvider.InternalID eligibility.Provider.ProviderName = entityProvider.OrganizatioName diff --git a/application/entitymapping/entitymapping.go b/application/entitymapping/entitymapping.go index 1e39153..9213463 100644 --- a/application/entitymapping/entitymapping.go +++ b/application/entitymapping/entitymapping.go @@ -13,16 +13,17 @@ var ( // Mapper has mapping methods to map entities to view models type Mapper struct { - User *userMapping - Ride *rideMapping - Visit *visitMapping - Address *addressMapping - Provider *providerMapping - Notification *notificationMapping - Profile *profileMapping - Organization *organizationMapping - Zipcode *zipcodeMapping - Plan *planMapping + User *userMapping + Ride *rideMapping + Visit *visitMapping + Address *addressMapping + Provider *providerMapping + Notification *notificationMapping + Profile *profileMapping + Organization *organizationMapping + Zipcode *zipcodeMapping + Plan *planMapping + PasswordReset *passwordResetMapping } // New returns an EntityMapper for fluent mapping @@ -40,6 +41,7 @@ func New() *Mapper { instance.Organization = &organizationMapping{instance} instance.Zipcode = &zipcodeMapping{instance} instance.Plan = &planMapping{instance} + instance.PasswordReset = &passwordResetMapping{instance} }) return instance diff --git a/data/datamysql/datamysql.go b/data/datamysql/datamysql.go index d57838d..1a2e056 100644 --- a/data/datamysql/datamysql.go +++ b/data/datamysql/datamysql.go @@ -20,16 +20,17 @@ var ( // Conn is the MySQL connection manager type Conn struct { - db *sql.DB - users *userRepo - rides *rideRepo - visit *visitRepo - provider *providerRepo - notification *notificationRepo - profile *profileRepo - organization *organizationRepo - zipcodes *zipcodeRepo - plan *planRepo + db *sql.DB + users *userRepo + rides *rideRepo + visit *visitRepo + provider *providerRepo + notification *notificationRepo + profile *profileRepo + organization *organizationRepo + zipcodes *zipcodeRepo + plan *planRepo + passwordReset *passwordResetRepo } // Begin starts a transaction @@ -90,6 +91,10 @@ func (c *Conn) Plans() contract.PlanRepo { return c.plan } +func (c *Conn) PasswordReset() contract.PasswordResetRepo { + return c.passwordReset +} + // Instance returns an instance of a DataManager func Instance(cfg *config.Config) (contract.DataManager, error) { once.Do(func() { @@ -123,6 +128,7 @@ func Instance(cfg *config.Config) (contract.DataManager, error) { instance.organization = newOrganizationRepo(db) instance.zipcodes = newZipcodeRepo(db) instance.plan = newPlanRepo(db) + instance.passwordReset = newPasswordResetRepo(db) }) return instance, connErr diff --git a/data/datamysql/transaction.go b/data/datamysql/transaction.go index 6bc7fd0..5da061e 100644 --- a/data/datamysql/transaction.go +++ b/data/datamysql/transaction.go @@ -8,16 +8,17 @@ import ( ) type transaction struct { - tx *sql.Tx - users *userRepo - rides *rideRepo - visits *visitRepo - provider *providerRepo - notification *notificationRepo - profile *profileRepo - organization *organizationRepo - zipcodes *zipcodeRepo - plan *planRepo + tx *sql.Tx + users *userRepo + rides *rideRepo + visits *visitRepo + provider *providerRepo + notification *notificationRepo + profile *profileRepo + organization *organizationRepo + zipcodes *zipcodeRepo + plan *planRepo + passwordReset *passwordResetRepo } func newTransaction(tx *sql.Tx) *transaction { @@ -34,6 +35,7 @@ func newTransaction(tx *sql.Tx) *transaction { t.organization = newOrganizationRepo(tx) t.zipcodes = newZipcodeRepo(tx) t.plan = newPlanRepo(tx) + t.passwordReset = newPasswordResetRepo(tx) return t } @@ -81,6 +83,10 @@ func (t transaction) Plans() contract.PlanRepo { return t.plan } +func (t transaction) PasswordReset() contract.PasswordResetRepo { + return t.passwordReset +} + func (t *transaction) Commit() error { err := t.tx.Commit() diff --git a/data/datamysql/user.go b/data/datamysql/user.go index 4e39bc7..5c681bc 100644 --- a/data/datamysql/user.go +++ b/data/datamysql/user.go @@ -48,6 +48,32 @@ func (c *userRepo) GetByMemberID(memberID string) (entity.User, error) { } } +func (c *userRepo) GetByEmail(email string) (entity.User, error) { + finalQuery := c.getQuery() + " AND b.email = ?" + + user, err := c.parseSet(c.conn.Query(finalQuery, email)) + if err != nil { + return entity.User{}, err + } + + if len(user) > 0 { + retVal := user[0] + retVal.Contacts, err = c.GetContacts(retVal.ID) + if err != nil { + return entity.User{}, err + } + + retVal.Addresses = nil + retVal.Addresses, err = c.getAddressByUserID(retVal.ID) + if err != nil { + return entity.User{}, err + } + return retVal, nil + } else { + return entity.User{}, nil + } +} + func (c *userRepo) GetByUUID(uuid string, profile string) (entity.User, error) { params := make([]interface{}, 0) params = append(params, uuid) diff --git a/domain/contract/repo.go b/domain/contract/repo.go index f875f7d..6d07202 100644 --- a/domain/contract/repo.go +++ b/domain/contract/repo.go @@ -14,6 +14,7 @@ type repoManager interface { Organization() OrganizationRepo Zipcodes() ZipcodeRepo Plans() PlanRepo + PasswordReset() PasswordResetRepo } // UserRepo defines the data set for users @@ -22,6 +23,7 @@ type UserRepo interface { GetByID(userID int64) (retVal entity.User, err error) GetByUUID(uuid string, profile string) (entity.User, error) GetByMemberID(memberID string) (entity.User, error) + GetByEmail(email string) (entity.User, error) Login(email string, pass string) (entity.User, error) FullLogin(loginType string, key string, pass string, profile string) (entity.User, error) Create(user entity.User) (entity.User, error) @@ -124,3 +126,12 @@ type ZipcodeRepo interface { GetAll() ([]entity.Zipcode, error) GetByParticipatingZipcode(zipcode string) (entity.Zipcode, error) } + +type PasswordResetRepo interface { + GetAll() ([]entity.PasswordReset, error) + CreatePasswordResetEntry(passwordResetEntry entity.PasswordReset) (entity.PasswordReset, error) + GetByID(ID int64) (entity.PasswordReset, error) + GetByToken(token string) (entity.PasswordReset, error) + SetTokenOpened(token string) error + SetTokenUsed(token string) error +} diff --git a/domain/service/service.go b/domain/service/service.go index 1bfb2b6..419a798 100644 --- a/domain/service/service.go +++ b/domain/service/service.go @@ -15,18 +15,19 @@ var ( // Service holds the domain service repositories type Service struct { - db contract.DataManager - cache contract.CacheManager - tnc contract.TNCManager - Users *userService - Rides *rideService - Visits *visitService - Provider *providerService - Notification *notificationService - Profile *profileService - Organization *organizationService - Zipcodes *zipcodeService - Plans *planService + db contract.DataManager + cache contract.CacheManager + tnc contract.TNCManager + Users *userService + Rides *rideService + Visits *visitService + Provider *providerService + Notification *notificationService + Profile *profileService + Organization *organizationService + Zipcodes *zipcodeService + Plans *planService + PasswordReset *passwordResetService } // New returns a new domain Service instance @@ -43,6 +44,7 @@ func New(db contract.DataManager, cache contract.CacheManager, cfg *config.Confi instance.Organization = newOrganizationService(instance) instance.Zipcodes = newZipcodeService(instance) instance.Plans = newPlanService(instance) + instance.PasswordReset = newPasswordResetService(instance) }) return instance, nil diff --git a/domain/service/user.go b/domain/service/user.go index ea00135..ca53aab 100644 --- a/domain/service/user.go +++ b/domain/service/user.go @@ -37,6 +37,10 @@ func (s *userService) GetByMemberID(memberID string) (entity.User, error) { return s.svc.db.Users().GetByMemberID(memberID) } +func (s *userService) GetByEmail(email string) (entity.User, error) { + return s.svc.db.Users().GetByEmail(email) +} + // Login returns a specific user by email and pass func (s *userService) Login(email string, pass string) (entity.User, error) { return s.svc.db.Users().Login(email, pass) @@ -72,7 +76,7 @@ func (s *userService) CreateBulk(users []entity.User) ([]entity.User, error) { return users, nil } -func (s *userService) UpdateLogin(user entity.User) error { +func (s *userService) UpdateLogin(user entity.User) error { return s.svc.db.Users().UpdateLogin(user) } diff --git a/server/router/router.go b/server/router/router.go index de0f3b3..63a0ef0 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -13,6 +13,7 @@ import ( "bitbucket.org/nemt/nemt-portal-api/server/router/lyfthookroute" "bitbucket.org/nemt/nemt-portal-api/server/router/notificationroute" "bitbucket.org/nemt/nemt-portal-api/server/router/organizationroute" + "bitbucket.org/nemt/nemt-portal-api/server/router/passwordresetroute" "bitbucket.org/nemt/nemt-portal-api/server/router/placesroute" "bitbucket.org/nemt/nemt-portal-api/server/router/profileroute" "bitbucket.org/nemt/nemt-portal-api/server/router/providerroute" @@ -39,6 +40,7 @@ func Register(e *echo.Echo, cfg *config.Config, svc *applicationservice.Service, externalroute.Register(prefixGroup.Group("/ext"), cfg, svc, tnc, notification) authenticateroute.Register(prefixGroup.Group("/authenticate"), cfg, svc) selfregisterroute.Register(prefixGroup.Group("/selfregister"), cfg, svc) + passwordresetroute.Register(prefixGroup.Group("/passwordreset"), cfg, svc) appGroup := prefixGroup.Group("/" + cfg.App.Name) usersroute.Register(appGroup.Group("/users"), cfg, svc) diff --git a/server/router/routeutils/response.go b/server/router/routeutils/response.go index 2aab6da..d14fe53 100644 --- a/server/router/routeutils/response.go +++ b/server/router/routeutils/response.go @@ -102,6 +102,11 @@ func ResponseAPINotEligibleWithMessageError(c echo.Context, message string) erro return ResponseAPIError(c, http.StatusForbidden, message, false) } +//ResponseAPIPasswordResetFailed returns a standard API error when password reset fails +func ResponseAPIPasswordResetFailed(c echo.Context, message string) error { + return ResponseAPIError(c, http.StatusForbidden, message, false) +} + func ignoreDefaultWrappedErrors(c echo.Context, errorToHandle *errors.WrappedError, handler func(echo.Context, error) error) error { err := errorToHandle.GetOriginalError() diff --git a/server/router/usersroute/controller.go b/server/router/usersroute/controller.go index c34501f..0d3c86c 100644 --- a/server/router/usersroute/controller.go +++ b/server/router/usersroute/controller.go @@ -348,11 +348,7 @@ func (c *controller) handleMember(ctx echo.Context) error { return routeutils.ResponseAPIAuthError(ctx, "phonenumber or email is required", false) } - provider, err := c.svc.Provider.GetByNPI("1699849786", authUser) - if err != nil { - return routeutils.ResponseAPIAuthError(ctx, "Provider not found", false) - } - + provider := viewmodel.ProviderResp{} user, err = c.svc.Users.CheckAndCreateMember(user, provider, authUser) if err != nil { if validationError, ok := err.(*viewmodel.ValidationError); ok { diff --git a/server/serverconfig/util.go b/server/serverconfig/util.go index ff5d5e0..740bc6c 100644 --- a/server/serverconfig/util.go +++ b/server/serverconfig/util.go @@ -19,7 +19,8 @@ func authSkipper(ctx echo.Context) bool { strings.Contains(path, "/v1/notification/ws") || strings.HasPrefix(path, "/v1/lyfthook") || strings.HasPrefix(path, "/v1/docs") || - strings.HasPrefix(path, "/v1/selfregister")) + strings.HasPrefix(path, "/v1/selfregister") || + strings.HasPrefix(path, "/v1/passwordreset")) } // appSkipper is the default skipper for the application routes diff --git a/server/validation/selfregister.go b/server/validation/selfregister.go index b2cf5fa..ae0f431 100644 --- a/server/validation/selfregister.go +++ b/server/validation/selfregister.go @@ -53,7 +53,7 @@ func ValidateSelfregistration(user *viewmodel.User) []errors.ValidationError { //Provider NPI validation if len(user.Provider.InternalID) != 10 || !isNumeric(user.Provider.InternalID) { - result = append(result, errors.ValidationError{Field: "provider.internal_id", Message: "Provider NPI must be 10 digit number"}) + result = append(result, errors.ValidationError{Field: "provider.internal_id", Message: "Provider NPI must be a 10 digit number"}) } //First name validation