diff --git a/application/applicationservice/applicationservice.go b/application/applicationservice/applicationservice.go index 8e32be2..11a729d 100644 --- a/application/applicationservice/applicationservice.go +++ b/application/applicationservice/applicationservice.go @@ -23,6 +23,7 @@ type Service struct { Notification *notificationService Profile *profileService Organization *organizationService + Zipcodes *zipcodeService } // New returns a new domain Service instance @@ -36,6 +37,7 @@ func New(svc *service.Service, mapper *entitymapping.Mapper, notification *notif Notification: newNotificationService(svc, mapper, notification, cfg), Profile: newProfileService(svc, mapper), Organization: newOrganizationService(svc, mapper), + Zipcodes: newZipcodeService(svc, mapper), } }) diff --git a/application/applicationservice/zipcode.go b/application/applicationservice/zipcode.go new file mode 100644 index 0000000..a37c1d1 --- /dev/null +++ b/application/applicationservice/zipcode.go @@ -0,0 +1,37 @@ +package applicationservice + +import ( + "bitbucket.org/nemt/nemt-portal-api/application/entitymapping" + "bitbucket.org/nemt/nemt-portal-api/application/viewmodel" + "bitbucket.org/nemt/nemt-portal-api/domain/service" +) + +// zipcodeService holds methods to participating zipcode application service +type zipcodeService struct { + svc *service.Service + mapEntity *entitymapping.Mapper +} + +// newZipcodeService returns a zipcodeService instance +func newZipcodeService(svc *service.Service, mapper *entitymapping.Mapper) *zipcodeService { + return &zipcodeService{ + svc: svc, + mapEntity: mapper, + } +} + +func (s *zipcodeService) GetAll() ([]viewmodel.Zipcode, error) { + result, err := s.svc.Zipcodes.GetAll() + if err != nil { + return nil, err + } + return s.mapEntity.Zipcode.ToZipcodeModelSlice(result), nil +} + +func (s *zipcodeService) GetByParticipatingZipcode(zipcode string) (viewmodel.Zipcode, error) { + result, err := s.svc.Zipcodes.GetByParticipatingZipcode(zipcode) + if err != nil { + return viewmodel.Zipcode{}, err + } + return s.mapEntity.Zipcode.ToZipcodeModel(result), nil +} \ No newline at end of file diff --git a/application/entitymapping/entitymapping.go b/application/entitymapping/entitymapping.go index 2c34565..ca4c71a 100644 --- a/application/entitymapping/entitymapping.go +++ b/application/entitymapping/entitymapping.go @@ -21,6 +21,7 @@ type Mapper struct { Notification *notificationMapping Profile *profileMapping Organization *organizationMapping + Zipcode *zipcodeMapping } // New returns an EntityMapper for fluent mapping @@ -36,6 +37,7 @@ func New() *Mapper { instance.Notification = ¬ificationMapping{instance} instance.Profile = &profileMapping{instance} instance.Organization = &organizationMapping{instance} + instance.Zipcode = &zipcodeMapping{instance} }) return instance diff --git a/application/entitymapping/zipcode.go b/application/entitymapping/zipcode.go new file mode 100644 index 0000000..cef85bf --- /dev/null +++ b/application/entitymapping/zipcode.go @@ -0,0 +1,51 @@ +package entitymapping + +import ( + "bitbucket.org/nemt/nemt-portal-api/application/viewmodel" + "bitbucket.org/nemt/nemt-portal-api/domain/entity" +) + +// zipcodeMapping has method to map zipcode entities to view models +type zipcodeMapping struct { + mapper *Mapper +} + +// ToUserEntitySlice maps a User entity slice to User view model slice +func (mapping *zipcodeMapping) ToZipcodeEntitySlice(list []viewmodel.Zipcode) (retVal []entity.Zipcode) { + retVal = make([]entity.Zipcode, 0) + + for _, item := range list { + retVal = append(retVal, mapping.ToZipcodeEntity(item)) + } + + return retVal +} + +func (mapping *zipcodeMapping) ToZipcodeEntity(model viewmodel.Zipcode) entity.Zipcode { + return entity.Zipcode{ + ID: model.ID, + UUID: model.UUID, + Zipcode: model.Zipcode, + Participating: model.Participating, + } +} + +// ToUserEntitySlice maps a User entity slice to User view model slice +func (mapping *zipcodeMapping) ToZipcodeModelSlice(list []entity.Zipcode) (retVal []viewmodel.Zipcode) { + retVal = make([]viewmodel.Zipcode, 0) + + for _, item := range list { + retVal = append(retVal, mapping.ToZipcodeModel(item)) + } + + return retVal +} + +func (mapping *zipcodeMapping) ToZipcodeModel(model entity.Zipcode) viewmodel.Zipcode { + return viewmodel.Zipcode{ + ID: model.ID, + UUID: model.UUID, + Zipcode: model.Zipcode, + Participating: model.Participating, + } +} diff --git a/application/viewmodel/zipcode.go b/application/viewmodel/zipcode.go new file mode 100644 index 0000000..6a73deb --- /dev/null +++ b/application/viewmodel/zipcode.go @@ -0,0 +1,8 @@ +package viewmodel + +type Zipcode struct { + ID int64 `json:"-"` + UUID string `json:"uuid"` + Zipcode string `json:"zipcode"` + Participating bool `json:"participating"` +} \ No newline at end of file diff --git a/data/datamysql/datamysql.go b/data/datamysql/datamysql.go index 7ac8d6f..a9c8d98 100644 --- a/data/datamysql/datamysql.go +++ b/data/datamysql/datamysql.go @@ -28,6 +28,7 @@ type Conn struct { notification *notificationRepo profile *profileRepo organization *organizationRepo + zipcodes *zipcodeRepo } // Begin starts a transaction @@ -80,6 +81,10 @@ func (c *Conn) Organization() contract.OrganizationRepo { return c.organization } +func (c *Conn) Zipcodes() contract.ZipcodeRepo{ + return c.zipcodes +} + // Instance returns an instance of a DataManager func Instance(cfg *config.Config) (contract.DataManager, error) { once.Do(func() { @@ -111,6 +116,7 @@ func Instance(cfg *config.Config) (contract.DataManager, error) { instance.notification = newNotificationRepo(db) instance.profile = newProfileRepo(db) instance.organization = newOrganizationRepo(db) + instance.zipcodes = newZipcodeRepo(db) }) return instance, connErr diff --git a/data/datamysql/transaction.go b/data/datamysql/transaction.go index 7daae84..696ff6e 100644 --- a/data/datamysql/transaction.go +++ b/data/datamysql/transaction.go @@ -16,6 +16,7 @@ type transaction struct { notification *notificationRepo profile *profileRepo organization *organizationRepo + zipcodes *zipcodeRepo } func newTransaction(tx *sql.Tx) *transaction { @@ -66,6 +67,10 @@ func (t transaction) Organization() contract.OrganizationRepo { return t.organization } +func (t transaction) Zipcodes() contract.ZipcodeRepo{ + return t.zipcodes +} + func (t *transaction) Commit() error { err := t.tx.Commit() diff --git a/data/datamysql/zipcode.go b/data/datamysql/zipcode.go new file mode 100644 index 0000000..a228dad --- /dev/null +++ b/data/datamysql/zipcode.go @@ -0,0 +1,65 @@ +package datamysql + +import ( + "database/sql" + + "bitbucket.org/nemt/nemt-portal-api/domain/entity" + "bitbucket.org/nemt/nemt-portal-api/infra/errors" +) + +// rideRepo maps methods to database +type zipcodeRepo struct { + conn executor +} + +func newZipcodeRepo(conn executor) *zipcodeRepo { + return &zipcodeRepo{ + conn: conn, + } +} + +func (c *zipcodeRepo) getQuery() string { + const ( + query = `SELECT + a.participating_zip_code_id, + a.participating_zip_code_uuid, + a.zipcode, + a.participating + FROM + tab_participating_zip_code a ` + ) + + return query +} + +// parseSet parses a result set result to an entity array +func (c *zipcodeRepo) parseSet(rows *sql.Rows, err error) ([]entity.Zipcode, error) { + if err != nil { + return nil, errors.Wrap(err) + } + result := make([]entity.Zipcode, 0) + for rows.Next() { + entity, err := c.parseEntity(rows) + if err != nil { + return nil, errors.Wrap(err) + } + result = append(result, entity) + } + return result, nil +} + +// parseEntity parses a result to an entity +func (c *zipcodeRepo) parseEntity(row scanner) (retVal entity.Zipcode, err error) { + err = row.Scan( + &retVal.ID, &retVal.UUID, &retVal.Zipcode, &retVal.Participating) + + return retVal, errors.Wrap(err) +} + +func (c *zipcodeRepo) GetAll() ([]entity.Zipcode, error) { + return c.parseSet(c.conn.Query(c.getQuery())) +} + +func (c *zipcodeRepo) GetByParticipatingZipcode(zipcode string) (entity.Zipcode, error) { + return c.parseEntity(c.conn.QueryRow(c.getQuery()+"WHERE a.participating = 1 AND a.zipcode = ?", zipcode)) +} \ No newline at end of file diff --git a/domain/contract/repo.go b/domain/contract/repo.go index 77d1581..06dca1d 100644 --- a/domain/contract/repo.go +++ b/domain/contract/repo.go @@ -12,6 +12,7 @@ type repoManager interface { Notification() NotificationRepo Profile() ProfileRepo Organization() OrganizationRepo + Zipcodes() ZipcodeRepo } // UserRepo defines the data set for users @@ -105,3 +106,8 @@ type ProfileRepo interface { GetVisibles(visible bool) ([]entity.Profile, error) GetByOrganizationType(organizationTypeID int64) ([]entity.Profile, error) } + +type ZipcodeRepo interface { + GetAll() ([]entity.Zipcode, error) + GetByParticipatingZipcode(zipcode string) (entity.Zipcode, error) +} diff --git a/domain/entity/zipcode.go b/domain/entity/zipcode.go new file mode 100644 index 0000000..5266b0c --- /dev/null +++ b/domain/entity/zipcode.go @@ -0,0 +1,8 @@ +package entity + +type Zipcode struct { + ID int64 `db:"participating_zip_code_id" json:"-"` + UUID string `db:"participating_zip_code_uuid" json:"uuid"` + Zipcode string `db:"zipcode" json:"zipcode"` + Participating bool `db:"participating" json:"participating"` +} \ No newline at end of file diff --git a/domain/service/service.go b/domain/service/service.go index 6030b05..d4c6a4b 100644 --- a/domain/service/service.go +++ b/domain/service/service.go @@ -25,6 +25,7 @@ type Service struct { Notification *notificationService Profile *profileService Organization *organizationService + Zipcodes *zipcodeService } // New returns a new domain Service instance @@ -39,6 +40,7 @@ func New(db contract.DataManager, cache contract.CacheManager, cfg *config.Confi instance.Notification = newNotificationService(instance) instance.Profile = newProfileService(instance) instance.Organization = newOrganizationService(instance) + instance.Zipcodes = newZipcodeService(instance) }) return instance, nil diff --git a/domain/service/zipcode.go b/domain/service/zipcode.go new file mode 100644 index 0000000..40775d3 --- /dev/null +++ b/domain/service/zipcode.go @@ -0,0 +1,25 @@ +package service + +import ( + "bitbucket.org/nemt/nemt-portal-api/domain/entity" +) + +// userService is the domain service for user operations +type zipcodeService struct { + svc *Service +} + +// newUserService returns an instance of userService +func newZipcodeService(svc *Service) *zipcodeService { + return &zipcodeService{ + svc: svc, + } +} + +func (s *zipcodeService) GetAll() ([]entity.Zipcode, error) { + return s.svc.db.Zipcodes().GetAll() +} + +func (s *zipcodeService) GetByParticipatingZipcode(zipcode string) (entity.Zipcode, error) { + return s.svc.db.Zipcodes().GetByParticipatingZipcode(zipcode) +} \ No newline at end of file diff --git a/server/router/routeutils/response.go b/server/router/routeutils/response.go index f4981ac..c68fed6 100644 --- a/server/router/routeutils/response.go +++ b/server/router/routeutils/response.go @@ -92,6 +92,15 @@ func ResponseAPINotFoundError(c echo.Context) error { return ResponseAPIError(c, http.StatusNotFound, "Not Found", false) } +//ResponseAPINotEligible returns a standard API not eligible to the response +func ResponseAPINotEligibleError(c echo.Context) error { + return ResponseAPIError(c, http.StatusForbidden, "Eligibility Not Found or Error", false) +} + +func ResponseAPINotEligibleWithMessageError(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 06e8bb2..b06d0d6 100644 --- a/server/router/usersroute/controller.go +++ b/server/router/usersroute/controller.go @@ -382,11 +382,6 @@ func (c *controller) handleMember(ctx echo.Context) error { } user.Profiles = append(user.Profiles, profile) - user, err = c.svc.Users.Create(user, authUser) - if err != nil { - return routeutils.HandleAPIError(ctx, err) - } - eligibility := viewmodel.Eligibility{} eligibility.Provider.ProviderNPI = "1699849786" eligibility.Provider.ProviderName = "LITHOLINK CORPORATION" @@ -403,41 +398,61 @@ func (c *controller) handleMember(ctx echo.Context) error { resp, err := c.bcbsi.BXE.Get271(eligibility) if err != nil { fmt.Println("Eligibility Not Found or Error: ", err.Error()) - } else { - address := viewmodel.Address{} - header := resp.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950 - body := resp.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960 + return routeutils.ResponseAPINotEligibleError(ctx) + } + + address := viewmodel.Address{} + header := resp.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950 + body := resp.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960 - address.AddressTypeName = "Home" - address.AddressType = "home" - address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401) - address.Address = fmt.Sprintf("%s, %s", header.N301, body.N401) - address.CreatedUserUUID = authUser.ID - address.User = user + address.AddressTypeName = "Home" + address.AddressType = "home" + address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401) + address.Address = fmt.Sprintf("%s, %s", header.N301, body.N401) + address.CreatedUserUUID = authUser.ID + address.User = user - googleMapsAPI, err := maps.NewClient(maps.WithClientIDAndSignature("gme-bluecrossandblue1", "msqgD-jdqCyR0M_1u5C1HION5iI=")) + zipCodeFromAddress := body.N403 + zipCodeObj, err := c.svc.Zipcodes.GetByParticipatingZipcode(zipCodeFromAddress) + + if err != nil{ + return routeutils.HandleAPIError(ctx, err) + } + + if !zipCodeObj.Participating { + return routeutils.ResponseAPINotEligibleWithMessageError(ctx, "Member's Home zipcode, " + zipCodeFromAddress + ", is not currently eligible for participation in this program") + } + + googleMapsAPI, err := maps.NewClient(maps.WithClientIDAndSignature("gme-bluecrossandblue1", "msqgD-jdqCyR0M_1u5C1HION5iI=")) + if err != nil { + fmt.Println("Error to instantiate googles api: ", err.Error()) + return routeutils.ResponseAPINotEligibleError(ctx) + } + + r := &maps.GeocodingRequest{ + Address: address.Address + " " + body.N402 + ", " + body.N403, + } + + result, err := googleMapsAPI.Geocode(context.Background(), r) + if err != nil { + fmt.Println("Error to instantiate googles api: ", err.Error()) + return routeutils.ResponseAPINotEligibleError(ctx) + } + + if len(result) > 0 { + address.Latitude = result[0].Geometry.Location.Lat + address.Longitude = result[0].Geometry.Location.Lng + + _, err := c.svc.Users.SaveAddress(address) if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) + fmt.Println("Error to save address: ", err.Error()) + return routeutils.ResponseAPINotEligibleError(ctx) } + } - r := &maps.GeocodingRequest{ - Address: address.Address + " " + body.N402 + ", " + body.N403, - } - - result, err := googleMapsAPI.Geocode(context.Background(), r) - if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) - } - - if len(result) > 0 { - address.Latitude = result[0].Geometry.Location.Lat - address.Longitude = result[0].Geometry.Location.Lng - - _, err := c.svc.Users.SaveAddress(address) - if err != nil { - fmt.Println("Error to save address: ", err.Error()) - } - } + user, err = c.svc.Users.Create(user, authUser) + if err != nil { + return routeutils.HandleAPIError(ctx, err) } return routeutils.ResponseAPIOK(ctx, user)