Compare commits

4 Commits

Author SHA1 Message Date
Senad Uka
f22af46a26 Upstream sync 2018-06-01 16:50:57 +02:00
Senad Uka
79f39b32c9 Upstream sync 2018-06-01 16:46:46 +02:00
Senad Uka
dfa43d09e4 Upstream sync 2018-06-01 16:14:56 +02:00
Senad Uka
69853b026b Upstream sync 2018-06-01 10:39:46 +02:00
39 changed files with 1100 additions and 661 deletions

View File

@@ -5,6 +5,7 @@ import (
"bitbucket.org/nemt/nemt-portal-api/application/entitymapping"
"bitbucket.org/nemt/nemt-portal-api/application/notificationservice"
"bitbucket.org/nemt/nemt-portal-api/application/third/eligibility/bcbsi"
"bitbucket.org/nemt/nemt-portal-api/domain/service"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
)
@@ -16,30 +17,34 @@ 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
func New(svc *service.Service, mapper *entitymapping.Mapper, notification *notificationservice.Service, cfg *config.Config) *Service {
once.Do(func() {
bcbsi := bcbsi.New(cfg)
instance = &Service{
Users: newUserService(svc, mapper),
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),
}
})

View File

@@ -0,0 +1,62 @@
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 passwordResetService struct {
svc *service.Service
mapEntity *entitymapping.Mapper
}
// newZipcodeService returns a zipcodeService instance
func newPasswordResetService(svc *service.Service, mapper *entitymapping.Mapper) *passwordResetService {
return &passwordResetService{
svc: svc,
mapEntity: mapper,
}
}
func (s *passwordResetService) GetAll() ([]viewmodel.PasswordReset, error) {
result, err := s.svc.PasswordReset.GetAll()
if err != nil {
return nil, err
}
return s.mapEntity.PasswordReset.ToPasswordResetModelSlice(result), nil
}
func (s *passwordResetService) CreatePasswordResetEntry(passwordResetEntry viewmodel.PasswordReset) (viewmodel.PasswordReset, error) {
passwordResetEntity := s.mapEntity.PasswordReset.ToPasswordResetEntity(passwordResetEntry)
result, err := s.svc.PasswordReset.CreatePasswordResetEntry(passwordResetEntity)
if err != nil {
return viewmodel.PasswordReset{}, err
}
return s.mapEntity.PasswordReset.ToPasswordResetModel(result), nil
}
func (s *passwordResetService) GetByID(ID int64) (viewmodel.PasswordReset, error) {
result, err := s.svc.PasswordReset.GetByID(ID)
if err != nil {
return viewmodel.PasswordReset{}, err
}
return s.mapEntity.PasswordReset.ToPasswordResetModel(result), nil
}
func (s *passwordResetService) GetByToken(token string) (viewmodel.PasswordReset, error) {
result, err := s.svc.PasswordReset.GetByToken(token)
if err != nil {
return viewmodel.PasswordReset{}, err
}
return s.mapEntity.PasswordReset.ToPasswordResetModel(result), nil
}
func (s *passwordResetService) SetTokenOpened(token string) error {
return s.svc.PasswordReset.SetTokenOpened(token)
}
func (s *passwordResetService) SetTokenUsed(token string) error {
return s.svc.PasswordReset.SetTokenUsed(token)
}

View File

@@ -1,24 +1,40 @@
package applicationservice
import (
"context"
"encoding/xml"
"fmt"
"html"
"math/rand"
"strings"
"time"
"bitbucket.org/nemt/nemt-portal-api/application/entitymapping"
"bitbucket.org/nemt/nemt-portal-api/application/third/eligibility/bcbsi"
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
"bitbucket.org/nemt/nemt-portal-api/domain/entity"
"bitbucket.org/nemt/nemt-portal-api/domain/service"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
"bitbucket.org/nemt/nemt-portal-api/server/validation"
"googlemaps.github.io/maps"
)
// userService holds methods to user application service
type userService struct {
svc *service.Service
mapEntity *entitymapping.Mapper
bcbsi *bcbsi.Service
cfg *config.Config
}
// newUserService returns a userService instance
func newUserService(svc *service.Service, mapper *entitymapping.Mapper) *userService {
func newUserService(svc *service.Service, mapper *entitymapping.Mapper, bcbsi *bcbsi.Service, cfg *config.Config) *userService {
return &userService{
svc: svc,
mapEntity: mapper,
bcbsi: bcbsi,
cfg: cfg,
}
}
@@ -61,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)
@@ -184,3 +210,220 @@ func (s *userService) RemoveContact(contact viewmodel.Contact) (retVal viewmodel
return s.mapEntity.User.ToContactModel(entity), err
}
func (s *userService) rangeIn(low, hi int) string {
result := low + rand.Intn(hi-low)
return fmt.Sprintf("%v", result)
}
func (s *userService) generatePassword(n int) string {
const (
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
return s.stringWithCharset(n, charset)
}
func (s *userService) stringWithCharset(length int, charset string) string {
b := make([]byte, length)
var seededRand *rand.Rand = rand.New(
rand.NewSource(time.Now().UnixNano()))
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}
func (s *userService) CheckAndCreateMember(user viewmodel.User, provider viewmodel.ProviderResp, authorUser viewmodel.User) (viewmodel.User, error) {
if validationErrors := validation.ValidateEligibility(&user); len(validationErrors) > 0 {
return viewmodel.User{}, &viewmodel.ValidationError{Message: "Eligibility validation failed", Errors: validationErrors}
}
entityUser := s.mapEntity.User.ToUserEntity(user)
entityAuthorUser := s.mapEntity.User.ToUserEntity(authorUser)
entityProvider := s.mapEntity.Provider.ToProviderRespEntity(provider)
newEmail := entityUser.Email
newPhoneNumber := entityUser.PhoneNumber
var err error
if entityUser.UUID != "" {
entityUser, err = s.svc.Users.GetByUUID(entityUser.UUID, "US")
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding user by UUID: %s", err.Error())}
}
} else {
entityUser, err = s.svc.Users.GetByMemberID(entityUser.Member)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding user by Subscriber ID: %s", err.Error())}
}
}
if entityUser.UUID == "" {
entityUser = s.mapEntity.User.ToUserEntity(user)
}
var eligibility viewmodel.Eligibility
loc, _ := time.LoadLocation("America/Chicago")
eligibility.TrackingID = s.rangeIn(1000000, 9999999)
eligibility.ServiceInfo.DateOfService = time.Now().In(loc)
eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"}
if entityProvider.InternalID == "" && entityProvider.ProviderUUID != "" {
entityProvider, err = s.svc.Provider.GetByUUID(entityProvider.ProviderUUID, entityAuthorUser)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding provider by UUID: %s", err.Error())}
}
} 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())}
}
}
if entityProvider.InternalID == "" {
eligibility.Provider.ProviderNPI = "1699849786"
eligibility.Provider.ProviderName = "LITHOLINK CORPORATION"
} else {
eligibility.Provider.ProviderNPI = entityProvider.InternalID
eligibility.Provider.ProviderName = entityProvider.OrganizatioName
}
plan, err := s.svc.Plans.GetByAlphaPrefix(entityUser.Member[:3])
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding Plan: %s", err.Error())}
}
eligibility.Payer.PayerID = fmt.Sprintf("%v", plan.PayerID)
eligibility.Payer.PayerName = plan.PayerName
eligibility.Subscriber.SubscriberID = entityUser.Member
eligibility.Subscriber.PatientType = entityUser.Type
eligibility.Subscriber.Name.First = strings.Split(entityUser.Name, " ")[0]
eligibility.Subscriber.Name.Last = strings.Split(entityUser.Name, " ")[1]
eligibility.Subscriber.DemographicInfo.DateOfBirth = entityUser.BirthDate
eligibility.Subscriber.DemographicInfo.Gender = entityUser.Gender
ret, err := s.bcbsi.BXE.CheckEligibility(eligibility)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error checking eligibility: %s", err.Error())}
}
if ret.QueryResult.QueryResultSummary.BenefitsFound {
xmlString := html.UnescapeString(ret.QueryResult.HIPPA271.T271)
xmlReader := strings.NewReader(xmlString)
var f viewmodel.Interchange271
err = xml.NewDecoder(xmlReader).Decode(&f)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error unmarshaling 271 response: %s", err.Error())}
}
header := f.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950
body := f.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960
zipCode := strings.TrimSpace(body.N403)
if len(strings.TrimSpace(zipCode)) > 5 {
zipCode = strings.TrimSpace(zipCode)[:5]
}
if !s.cfg.App.DisableZipValidation {
_, err = s.svc.Zipcodes.GetByParticipatingZipcode(zipCode)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Member's Home zipcode, %s, is not currently eligible for participation in this program", zipCode)}
}
}
if entityUser.ID == 0 {
profile, err := s.svc.Profile.GetByKey("US")
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error getting user profile: %s", err.Error())}
}
entityUser.Profiles = append(entityUser.Profiles, profile)
if len(entityUser.PhoneNumber) == 10 && !strings.Contains(entityUser.PhoneNumber, "+1") {
phoneNumber := fmt.Sprintf("+1%s", entityUser.PhoneNumber)
entityUser.PhoneNumber = phoneNumber
}
entityUser.Test = false
entityUser, err = s.svc.Users.Create(entityUser)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error creating user: %s", err.Error())}
}
} else {
if len(newEmail) > 0 && newEmail != entityUser.Email {
entityUser.Email = newEmail
}
if len(newPhoneNumber) > 0 && len(newPhoneNumber) == 10 && !strings.Contains(newPhoneNumber, "+1") {
newPhoneNumber = fmt.Sprintf("+1%s", newPhoneNumber)
}
if len(newPhoneNumber) > 0 && len(newPhoneNumber) == 12 && strings.Contains(newPhoneNumber, "+1") {
entityUser.PhoneNumber = newPhoneNumber
}
err = s.svc.Users.UpdateLogin(entityUser)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error updating login data: %s", err.Error())}
}
}
address := entity.Address{}
address.AddressType = entity.Params{
Key: "home",
Name: "Home",
}
address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401)
address.Address = fmt.Sprintf("%s, %s (%s)", header.N301, body.N401, zipCode)
address.CreatedUser = entityAuthorUser
address.User = entityUser
address.Origin = entity.Params{
Key: "provider",
Name: "Provider",
}
googleMapsAPI, err := maps.NewClient(maps.WithClientIDAndSignature("gme-bluecrossandblue1", "msqgD-jdqCyR0M_1u5C1HION5iI="))
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error instanciating google maps: %s", err.Error())}
}
r := &maps.GeocodingRequest{
Address: address.Address + " " + body.N402 + ", " + zipCode,
}
result, err := googleMapsAPI.Geocode(context.Background(), r)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error getting geocode info: %s", err.Error())}
}
if len(result) > 0 {
address.Latitude = result[0].Geometry.Location.Lat
address.Longitude = result[0].Geometry.Location.Lng
}
if len(user.Addresses) > 0 {
for _, a := range user.Addresses {
if a.AddressType == "home" {
err := s.svc.Users.RemoveAddress(a.UUID)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error removing old address: %s", err.Error())}
}
}
}
}
address, err = s.svc.Users.SaveAddress(address)
if err != nil {
return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error saving new address: %s", err.Error())}
}
entityUser.Addresses = append(entityUser.Addresses, address)
return s.mapEntity.User.ToUserModel(entityUser), nil
} else {
return viewmodel.User{}, &viewmodel.ValidationError{Message: "No benefits found for the member"}
}
return viewmodel.User{}, nil
}

View File

@@ -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

View File

@@ -0,0 +1,59 @@
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 passwordResetMapping struct {
mapper *Mapper
}
// ToUserEntitySlice maps a User entity slice to User view model slice
func (mapping *passwordResetMapping) ToPasswordResetEntitySlice(list []viewmodel.PasswordReset) (retVal []entity.PasswordReset) {
retVal = make([]entity.PasswordReset, 0)
for _, item := range list {
retVal = append(retVal, mapping.ToPasswordResetEntity(item))
}
return retVal
}
func (mapping *passwordResetMapping) ToPasswordResetEntity(model viewmodel.PasswordReset) entity.PasswordReset {
return entity.PasswordReset{
ID: model.ID,
UUID: model.UUID,
User: mapping.mapper.User.ToUserEntity(model.User),
Token: model.Token,
Created: model.Created,
Expires: model.Expires,
Used: model.Used,
Opened: model.Opened,
}
}
// ToUserEntitySlice maps a User entity slice to User view model slice
func (mapping *passwordResetMapping) ToPasswordResetModelSlice(list []entity.PasswordReset) (retVal []viewmodel.PasswordReset) {
retVal = make([]viewmodel.PasswordReset, 0)
for _, item := range list {
retVal = append(retVal, mapping.ToPasswordResetModel(item))
}
return retVal
}
func (mapping *passwordResetMapping) ToPasswordResetModel(model entity.PasswordReset) viewmodel.PasswordReset {
return viewmodel.PasswordReset{
ID: model.ID,
UUID: model.UUID,
User: mapping.mapper.User.ToUserModel(model.User),
Token: model.Token,
Created: model.Created,
Expires: model.Expires,
Used: model.Used,
Opened: model.Opened,
}
}

View File

@@ -51,6 +51,7 @@ func (mapping *userMapping) ToUserModel(item entity.User) viewmodel.User {
Organizations: mapping.mapper.Organization.ToOrganizationModelSlice(item.Organizations),
Types: mapping.mapper.Organization.ToOrganizationTypeModelSlice(item.Types),
Type: &item.Type,
Test: item.Test,
}
}
@@ -78,6 +79,7 @@ func (mapping *userMapping) ToUserEntity(item viewmodel.User) entity.User {
Profiles: mapping.mapper.Profile.ToProfileEntitySlice(item.Profiles),
Organizations: mapping.mapper.Organization.ToOrganizationEntitySlice(item.Organizations),
Types: mapping.mapper.Organization.ToOrganizationTypeEntitySlice(item.Types),
Test: item.Test,
}
if item.Type == nil {

View File

@@ -6,12 +6,13 @@ import (
)
type Eligibility struct {
TrackingID string `json:"tracking_id,omitempty"`
Payer Payer `json:"payer,omitempty"`
Provider Provider `json:"provider,omitempty"`
Subscriber Subscriber `json:"subscriber,omitempty"`
ServiceInfo ServiceInfo `json:"service_info,omitempty"`
User User `json:"user,omitempty"`
TrackingID string `json:"tracking_id,omitempty"`
Payer Payer `json:"payer,omitempty"`
Provider Provider `json:"provider,omitempty"`
Subscriber Subscriber `json:"subscriber,omitempty"`
ServiceInfo ServiceInfo `json:"service_info,omitempty"`
User User `json:"user,omitempty"`
RawProvider ProviderResponse `json:"raw_provider,omitempty"`
}
type Payer struct {

View File

@@ -0,0 +1,16 @@
package viewmodel
import (
"fmt"
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
)
type ValidationError struct {
Errors []errors.ValidationError
Message string
}
func (ve *ValidationError) Error() string {
return fmt.Sprintf("Error: %s", ve.Message)
}

View File

@@ -0,0 +1,14 @@
package viewmodel
import "time"
type PasswordReset struct {
ID int64 `json:"-"`
UUID string `json:"uuid"`
User User `json:"user"`
Token string `json:"token"`
Created time.Time `json:"create_date"`
Expires time.Time `json:"expire_date"`
Used bool `json:"used"`
Opened bool `json:"opened"`
}

View File

@@ -26,6 +26,7 @@ type User struct {
Organizations []Organization `json:"organizations,omitempty"`
Provider *ProviderResp `json:"provider,omitempty"`
Consent bool `json:"consent,omitempty"`
Test bool `json:"test,omitempty"`
}
type Contact struct {

View File

@@ -1,6 +1,7 @@
[app]
name = "nemt"
debug = true
disable-zip-validation = true
[app.docs]
swagger-path = "./static/swagger-ui"

View File

@@ -1,6 +1,7 @@
[app]
name = "nemt"
debug = true
disable-zip-validation = true
[app.docs]
swagger-path = "/opt/app/nemt-portal-api/static/swagger-ui"
@@ -9,7 +10,7 @@ debug = true
[db]
host = "db01.cj5318jcaupw.us-east-2.rds.amazonaws.com"
port = 3306
name = "nemt_clean"
name = "nemt"
user = "nemt-user"
pass = "OL&!n#p6J8Lu"
max-life-minutes = 5
@@ -74,8 +75,8 @@ user-uuid = "11a49fa4-fbc7-4fe9-87fe-52a5bc3b71f8"
[bxe]
url = "https://api-pve.bcbs.com/BXDirectConnect/V30"
key = "dacg7jtsmb6ajr3z553jbczg"
secret = "vFhNeWE8JdJpbDZQtcm6AHjX"
key = "c63z7gv9a5qz2zgx7zcm3253"
secret = "hK4Yc9jtBrxaZTr4UU2VsfX9"
[blue365]
url = "https://api-cloud.bcbs.com/blue365deals-stg/v1/validatePrefix/"

View File

@@ -1,6 +1,7 @@
[app]
name = "nemt"
debug = true
disable-zip-validation = true
[app.docs]
swagger-path = "/opt/app/nemt-portal-api/static/swagger-ui"

View File

@@ -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

View File

@@ -0,0 +1,144 @@
package datamysql
import (
"database/sql"
"fmt"
"bitbucket.org/nemt/nemt-portal-api/domain/entity"
"bitbucket.org/nemt/nemt-portal-api/infra/errors"
uuid "github.com/satori/go.uuid"
)
// rideRepo maps methods to database
type passwordResetRepo struct {
conn executor
}
func newPasswordResetRepo(conn executor) *passwordResetRepo {
return &passwordResetRepo{
conn: conn,
}
}
func (c *passwordResetRepo) getQuery() string {
const (
query = `SELECT
a.password_reset_id,
a.password_reset_uuid,
a.user_id,
b.user_uuid,
a.token,
a.create_date,
a.expire_date,
(IFNULL(a.used, b'0') = b'1') used,
(IFNULL(a.opened, b'0') = b'1') opened
FROM
tab_password_reset a
INNER JOIN tab_user b
ON a.user_id = b.user_id`
)
return query
}
// parseSet parses a result set result to an entity array
func (c *passwordResetRepo) parseSet(rows *sql.Rows, err error) ([]entity.PasswordReset, error) {
if err != nil {
return nil, errors.Wrap(err)
}
result := make([]entity.PasswordReset, 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 *passwordResetRepo) parseEntity(row scanner) (retVal entity.PasswordReset, err error) {
err = row.Scan(
&retVal.ID, &retVal.UUID, &retVal.User.ID, &retVal.User.UUID, &retVal.Token, &retVal.Created, &retVal.Expires, &retVal.Used, &retVal.Opened)
return retVal, errors.Wrap(err)
}
func (c *passwordResetRepo) GetAll() ([]entity.PasswordReset, error) {
return c.parseSet(c.conn.Query(c.getQuery()))
}
func (c *passwordResetRepo) CreatePasswordResetEntry(passwordResetEntry entity.PasswordReset) (entity.PasswordReset, error) {
const (
insertQuery = `INSERT INTO tab_password_reset(password_reset_uuid, user_id, token, expire_date, used, opened) VALUES(?, ?, ?, ?, 0, 0);`
)
retVal := passwordResetEntry
guid, _ := uuid.NewV4()
userRepo := newUserRepo(c.conn)
fullUser, err := userRepo.GetByEmail(passwordResetEntry.User.Email)
if err != nil {
return retVal, err
}
if fullUser.Email != passwordResetEntry.User.Email {
return retVal, fmt.Errorf("User not found")
}
results, err := c.conn.Exec(insertQuery, guid, fullUser.ID, passwordResetEntry.Token, passwordResetEntry.Expires)
if err != nil {
return retVal, err
}
retVal.ID, err = results.LastInsertId()
if err != nil {
return retVal, err
}
return c.GetByID(retVal.ID)
}
func (c *passwordResetRepo) GetByID(ID int64) (entity.PasswordReset, error) {
return c.parseEntity(c.conn.QueryRow(c.getQuery()+" WHERE a.password_reset_id = ?; ", ID))
}
func (c *passwordResetRepo) GetByToken(token string) (entity.PasswordReset, error) {
return c.parseEntity(c.conn.QueryRow(c.getQuery()+" WHERE a.token = ? AND a.used = 0; ", token))
}
func (c *passwordResetRepo) SetTokenOpened(token string) error {
const (
query = `UPDATE tab_password_reset a
SET a.opened = 1
WHERE a.token = ? AND a.used = 0 AND a.expire_date > CURRENT_TIMESTAMP`
)
result, err := c.conn.Exec(query, token)
if err != nil {
return err
}
if updateCount, err := result.RowsAffected(); err != nil || updateCount == 0 {
return fmt.Errorf("Invalid token")
}
return nil
}
func (c *passwordResetRepo) SetTokenUsed(token string) error {
const (
query = `UPDATE tab_password_reset a
SET a.opened = 1,
a.used = 1
WHERE a.token = ? AND a.used = 0`
)
if _, err := c.conn.Exec(query, token); err != nil {
return err
}
return nil
}

View File

@@ -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()

View File

@@ -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)
@@ -192,7 +218,7 @@ func (c *userRepo) parseEntity(row scanner) (retVal entity.User, err error) {
err = row.Scan(&retVal.ID, &retVal.UUID, &retVal.Name, &retVal.Member, &birthDate, &retVal.LoginID, &retVal.LoginUUID, &retVal.Email, &retVal.PhoneNumber, &retVal.LoginKey, &retVal.Gender, &retVal.Active, &retVal.Created, &retVal.Updated, &profile.ID, &profile.Name, &profile.Key, &profile.Active, &profile.Blocked, &profile.Suspended, &profile.Created, &profile.Updated, &profile.Organization.ID, &profile.Organization.UUID, &profile.Organization.Type.ID, &profile.Organization.Type.Name, &profile.Organization.Type.Key, &profile.Organization.Type.Description, &profile.Organization.Name, &profile.Organization.Description, &profile.Organization.ReferenceID, &profile.Organization.ParentID, &profile.Organization.Main,
&homeAddress.ID, &homeAddress.UUID, &homeAddress.AddressType.ID, &homeAddress.AddressType.Key, &homeAddress.AddressType.Name, &homeAddress.Name, &homeAddress.Address, &homeAddress.Latitude, &homeAddress.Longitude,
&workAddress.ID, &workAddress.UUID, &workAddress.AddressType.ID, &workAddress.AddressType.Key, &workAddress.AddressType.Name, &workAddress.Name, &workAddress.Address, &workAddress.Latitude, &workAddress.Longitude, &retVal.Type)
&workAddress.ID, &workAddress.UUID, &workAddress.AddressType.ID, &workAddress.AddressType.Key, &workAddress.AddressType.Name, &workAddress.Name, &workAddress.Address, &workAddress.Latitude, &workAddress.Longitude, &retVal.Type, &retVal.Test)
if err != nil {
if err != sql.ErrNoRows {
return retVal, errors.Wrap(err)
@@ -278,7 +304,8 @@ func (c *userRepo) getQuery() string {
IFNULL(i.address, '') work_address,
IFNULL(i.lat, 0) work_lat,
IFNULL(i.long, 0) work_long,
IFNULL(a.member_type, 'S') member_type
IFNULL(a.member_type, 'S') member_type,
user_test
FROM
tab_user a
INNER JOIN
@@ -587,11 +614,11 @@ func (c *userRepo) createLogin(user entity.User) (int64, string, error) {
func (c *userRepo) createUser(user entity.User) (int64, string, error) {
const (
query = "INSERT INTO tab_user(user_uuid, `name`, subscriber_id, birth_date, gender, member_type, user_hash) VALUES(?, ?, ?, ?, ?, ?, SHA2(CONCAT_WS('-',?,?,?,?,?), 512)) ON DUPLICATE KEY UPDATE user_uuid = ?, `name` = ?, subscriber_id = ?, birth_date = ?, gender = ?;"
query = "INSERT INTO tab_user(user_uuid, `name`, subscriber_id, birth_date, gender, member_type, user_hash, user_test) VALUES(?, ?, ?, ?, ?, ?, SHA2(CONCAT_WS('-',?,?,?,?,?), 512), ?) ON DUPLICATE KEY UPDATE user_uuid = ?, `name` = ?, subscriber_id = ?, birth_date = ?, gender = ?, user_test = ?;"
)
guid, _ := uuid.NewV4()
result, err := c.conn.Exec(query, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender))
result, err := c.conn.Exec(query, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, user.Test, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Test)
if err != nil {
return 0, "", err
}

View File

@@ -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
}

View File

@@ -0,0 +1,14 @@
package entity
import "time"
type PasswordReset struct {
ID int64 `db:"password_reset_id" json:"-"`
UUID string `db:"password_reset_uuid" json:"uuid"`
User User `db:"-" json:"user"`
Token string `db:"token" json:"token"`
Created time.Time `db:"create_date" json:"create_date"`
Expires time.Time `db:"expire_date" json:"expire_date"`
Used bool `db:"used" json:"used"`
Opened bool `db:"opend" json:"opened"`
}

View File

@@ -31,6 +31,7 @@ type User struct {
Types []OrganizationType `json:"types,omitempty"`
Organizations []Organization `json:"organizations,omitempty"`
Type string `json:"type,omitempty"`
Test bool `json:"test,omitempty"`
}
type ContactInfo struct {

View File

@@ -0,0 +1,41 @@
package service
import (
"bitbucket.org/nemt/nemt-portal-api/domain/entity"
)
// userService is the domain service for user operations
type passwordResetService struct {
svc *Service
}
// newUserService returns an instance of userService
func newPasswordResetService(svc *Service) *passwordResetService {
return &passwordResetService{
svc: svc,
}
}
func (s *passwordResetService) GetAll() ([]entity.PasswordReset, error) {
return s.svc.db.PasswordReset().GetAll()
}
func (s *passwordResetService) GetByID(ID int64) (entity.PasswordReset, error) {
return s.svc.db.PasswordReset().GetByID(ID)
}
func (s *passwordResetService) GetByToken(token string) (entity.PasswordReset, error) {
return s.svc.db.PasswordReset().GetByToken(token)
}
func (s *passwordResetService) CreatePasswordResetEntry(passwordResetEntry entity.PasswordReset) (entity.PasswordReset, error) {
return s.svc.db.PasswordReset().CreatePasswordResetEntry(passwordResetEntry)
}
func (s *passwordResetService) SetTokenOpened(token string) error {
return s.svc.db.PasswordReset().SetTokenOpened(token)
}
func (s *passwordResetService) SetTokenUsed(token string) error {
return s.svc.db.PasswordReset().SetTokenUsed(token)
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -25,14 +25,15 @@ type Config struct {
Blue365 Blue365Config
Email EmailConfig
GoogleShortener GoogleShortenerConfig
Eligibility EligibilityConfig
Eligibility EligibilityConfig
}
// AppConfig represents the configuration values about the application.
type AppConfig struct {
Name string
Debug bool
Docs DocsConfig
Name string
Debug bool
Docs DocsConfig
DisableZipValidation bool
}
// TwilioConfig represents the configuration values about the twilio.
@@ -139,7 +140,7 @@ type GoogleShortenerConfig struct {
}
type EligibilityConfig struct {
Url string
Url string
}
// Read returns the configuration values,
@@ -160,8 +161,9 @@ func Read() (*Config, error) {
return &Config{
App: AppConfig{
Name: viper.GetString("app.name"),
Debug: viper.GetBool("app.debug"),
Name: viper.GetString("app.name"),
Debug: viper.GetBool("app.debug"),
DisableZipValidation: viper.GetBool("app.disable-zip-validation"),
Docs: DocsConfig{
YAMLPath: viper.GetString("app.docs.yaml-path"),
SwaggerPath: viper.GetString("app.docs.swagger-path"),
@@ -243,8 +245,8 @@ func Read() (*Config, error) {
ClientID: viper.GetString("google-shortener.client-id"),
SecretKey: viper.GetString("google-shortener.secret-key"),
},
Eligibility : EligibilityConfig{
Url : viper.GetString("eligibility.url"),
Eligibility: EligibilityConfig{
Url: viper.GetString("eligibility.url"),
},
}, nil
}

View File

@@ -1,12 +1,8 @@
package eligibilityroute
import (
"context"
"encoding/xml"
"fmt"
"html"
"math/rand"
"strings"
"sync"
"time"
@@ -16,9 +12,7 @@ import (
"bitbucket.org/nemt/nemt-portal-api/infra/auth"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"bitbucket.org/nemt/nemt-portal-api/server/validation"
"github.com/labstack/echo"
"googlemaps.github.io/maps"
)
var (
@@ -78,185 +72,53 @@ func (c *controller) handleEligibility(ctx echo.Context) error {
return routeutils.HandleAPIError(ctx, err)
}
if validationErrors := validation.ValidateEligibility(&eligibility.User); len(validationErrors) > 0 {
return routeutils.ResponseAPICustomValidationError(ctx, "eligibility validation failed", validationErrors)
}
if eligibility.Provider.ProviderNPI == "" {
provider, err := c.svc.Provider.GetByOrganization(authUser.Profiles[0].Organization.UUID, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
eligibility.Provider.ProviderNPI = provider.InternalID
eligibility.Provider.ProviderName = provider.OrganizatioName
}
loc, _ := time.LoadLocation("America/Chicago")
eligibility.TrackingID = c.rangeIn(1000000, 9999999)
eligibility.ServiceInfo.DateOfService = time.Now().In(loc)
eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"}
var plan viewmodel.Plan
if eligibility.Subscriber.SubscriberID != "" {
plan, err = c.svc.Plan.GetByAlphaPrefix(eligibility.Subscriber.SubscriberID[:3])
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
eligibility.Payer.PayerID = fmt.Sprintf("%v", plan.PayerID)
eligibility.Payer.PayerName = plan.PayerName
} else {
return routeutils.ResponseAPIFieldValidationError(ctx, "subscriber_id", "member # is required.")
}
ret, err := c.bcbsi.BXE.CheckEligibility(eligibility)
var provider viewmodel.ProviderResp
provider, err = c.svc.Provider.GetByNPI(eligibility.RawProvider.FivePartKeyGroups[0].ProviderNum, authUser)
if err != nil {
fmt.Println("Error Here: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
if ret.QueryResult.QueryResultSummary.BenefitsFound {
xmlString := html.UnescapeString(ret.QueryResult.HIPPA271.T271)
xmlReader := strings.NewReader(xmlString)
var f viewmodel.Interchange271
err = xml.NewDecoder(xmlReader).Decode(&f)
if err != nil {
fmt.Println("Error to unmarshal: ", err.Error())
if provider.ProviderUUID == "" {
org := viewmodel.Organization{
Type: viewmodel.OrganizationType{
Key: "provider",
Name: "Provider",
},
Name: eligibility.RawProvider.OrgName,
Description: eligibility.RawProvider.OrgName,
Main: false,
Author: authUser,
LastEditor: authUser,
Reference: eligibility.RawProvider,
}
user, err := c.svc.Users.GetByMemberID(eligibility.Subscriber.SubscriberID)
org, err = c.svc.Organization.AddOrganization(org, authUser)
if err != nil {
fmt.Println(err)
return routeutils.HandleAPIError(ctx, err)
}
if user.ID == "" {
user.Pass = c.generatePassword(8)
user.BirthDate = &eligibility.Subscriber.DemographicInfo.DateOfBirth
user.Member = &eligibility.Subscriber.SubscriberID
user.Gender = &eligibility.Subscriber.DemographicInfo.Gender
user.First = eligibility.Subscriber.Name.First
user.Last = eligibility.Subscriber.Name.Last
user.Active = true
user.Type = &eligibility.Subscriber.PatientType
user.PhoneNumber = eligibility.User.PhoneNumber
user.Email = eligibility.User.Email
provider, err = c.svc.Provider.GetByOrganization(org.UUID, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
}
profile, err := c.svc.Profile.GetByKey("US")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
user.Profiles = append(user.Profiles, profile)
if user.BirthDate == nil || user.BirthDate.IsZero() {
return routeutils.ResponseAPIAuthError(ctx, "birthdate is required", false)
}
if user.Member == nil || len(*user.Member) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "member is required", false)
}
if user.Gender == nil || len(*user.Gender) == 0 || (*user.Gender != "M" && *user.Gender != "F" && *user.Gender != "U") {
return routeutils.ResponseAPIAuthError(ctx, "gender is required", false)
}
if len(user.Name) == 0 && len(user.First) == 0 && len(user.Last) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "name is required", false)
}
if len(user.First) != 0 && len(user.Last) != 0 {
user.Name = fmt.Sprintf("%s %s", user.First, user.Last)
}
if user.PhoneNumber != nil && len(*user.PhoneNumber) == 10 && !strings.Contains(*user.PhoneNumber, "+1") {
phoneNumber := fmt.Sprintf("+1%s", *user.PhoneNumber)
user.PhoneNumber = &phoneNumber
user, err := c.svc.Users.CheckAndCreateMember(eligibility.User, provider, authUser)
if err != nil {
if validationError, ok := err.(*viewmodel.ValidationError); ok {
if len(validationError.Errors) > 0 {
return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors)
} else {
return routeutils.ResponseAPIFieldValidationError(ctx, "phonenumber", "Phone number is required")
}
user, err = c.svc.Users.Create(user, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
return routeutils.ResponseAPIServiceError(ctx, validationError.Message)
}
} else {
if eligibility.User.Email != nil && len(*eligibility.User.Email) > 0 {
user.Email = eligibility.User.Email
}
if eligibility.User.PhoneNumber != nil {
if len(*eligibility.User.PhoneNumber) == 10 && !strings.Contains(*eligibility.User.PhoneNumber, "+1") {
phoneNumber := fmt.Sprintf("+1%s", *eligibility.User.PhoneNumber)
eligibility.User.PhoneNumber = &phoneNumber
}
if len(*eligibility.User.PhoneNumber) > 0 {
user.PhoneNumber = eligibility.User.PhoneNumber
}
}
err = c.svc.Users.UpdateLogin(user)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
}
address := viewmodel.Address{}
header := f.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950
body := f.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960
zipCode := strings.TrimSpace(body.N403)
address.AddressTypeName = "Home"
address.AddressType = "home"
address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401)
address.Address = fmt.Sprintf("%s, %s (%s)", header.N301, body.N401, zipCode)
address.CreatedUserUUID = authUser.ID
address.User = user
address.Type = "provider"
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.HandleAPIError(ctx, err)
}
}
r := &maps.GeocodingRequest{
Address: address.Address + " " + body.N402 + ", " + zipCode,
}
result, err := googleMapsAPI.Geocode(context.Background(), r)
if err != nil {
fmt.Println("Error to instantiate googles api: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
if len(result) > 0 {
address.Latitude = result[0].Geometry.Location.Lat
address.Longitude = result[0].Geometry.Location.Lng
}
if len(user.Addresses) > 0 {
for _, a := range user.Addresses {
if a.AddressType == "home" {
err := c.svc.Users.RemoveAddress(a.UUID)
if err != nil {
fmt.Println("Error to remove old address: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
}
}
}
address, err = c.svc.Users.SaveAddress(address)
if err != nil {
fmt.Println("Error to save address: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
user.Addresses = append(user.Addresses, address)
return ctx.JSON(200, user)
if authUser.Profiles[0].Key == "VIRPT" {
return ctx.JSON(204, nil)
} else {
return routeutils.ResponseAPINotEligibleWithMessageError(ctx, "No benefits found for member")
return ctx.JSON(200, user)
}
}

View File

@@ -63,12 +63,17 @@ func (c *controller) handleCancel(ctx echo.Context) error {
return routeutils.HandleAPIError(ctx, err)
}
authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
lyftRide := viewmodel.RideRequest{RideID: ride.InternalID}
if ride.Status.Key == "scheduled" && !strings.Contains(ride.InternalID, "s_") {
lyftRide.RideID = "s_" + ride.InternalID
}
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if authUser.Test {
if err = c.tnc.Lyft.CancelRide(lyftRide); err != nil {
if err.Error() != "ride_not_found" {
fmt.Println("Error to cancel with Lyft: ", err.Error())
@@ -144,13 +149,18 @@ func (c *controller) handle(ctx echo.Context) error {
return routeutils.HandleAPIError(ctx, err)
}
authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "")
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" {
var lyftRide viewmodel.RideRequest
var err error
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if authUser.Test {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
}
if err != nil {
@@ -236,7 +246,7 @@ func (c *controller) handleReady(ctx echo.Context) error {
}
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if authUser.Test {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
@@ -258,7 +268,7 @@ func (c *controller) handleReady(ctx echo.Context) error {
lyftRide.Passenger.PhoneNumber = *nextRide.User.PhoneNumber
lyftRide.RideType = "lyft"
if c.cfg.LyftProd.UserUUID != nextRide.CreatedUser.ID {
if authUser.Test {
lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide)
} else {
fmt.Println("In Production")

View File

@@ -57,8 +57,14 @@ func (c *controller) handleStateChange(ctx echo.Context) error {
return routeutils.HandleAPIError(ctx, err)
}
authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "")
if err != nil {
fmt.Println("Error: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
// var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if authUser.Test {
_, err = c.tnc.Lyft.GetRideStatus(viewmodel.RideRequest{RideID: ride.InternalID}, status)
go func() {
secondCall := func() {

View File

@@ -0,0 +1,168 @@
package passwordresetroute
import (
"crypto/sha256"
"fmt"
"math/rand"
"strings"
"sync"
"time"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"github.com/labstack/echo"
)
const (
tokenExpirationTime = 90 // in minutes
randomStringLength = 15
baseURL = "http://localhost:5000"
passwordResetEmailSubject = "Reset Your Password"
passwordResetEmailMainBody = "To reset your password click here or copy the following link and paste it into your browser: \n\n " + baseURL + "/#/reset-password/"
passwordResetEmailFooter = "\nThis link expires in 90 minutes"
passwordResetCompleteEmailSubject = "Your Password Has been Reset"
passwordResetCompleteEmailBody = "Your password has been reset. To login click here or copy the following link and paste it into your browser: \n\n" + baseURL + "/#/login"
)
var (
instance *controller
once sync.Once
)
type controller struct {
svc *applicationservice.Service
cfg *config.Config
}
func controllerInstance(svc *applicationservice.Service, cfg *config.Config) *controller {
once.Do(func() {
instance = &controller{
svc: svc,
cfg: cfg,
}
rand.Seed(time.Now().UTC().UnixNano())
})
return instance
}
func (c *controller) handleResetRequest(ctx echo.Context) error {
userEmail, err := routeutils.GetAndValidateStringParam(ctx, "email", "mandatory field")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
//find if user with email exists
user, err := c.svc.Users.GetByEmail(userEmail)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if user.Email == nil || (*user.Email != userEmail) {
return routeutils.ResponseAPIOK(ctx, nil) //more secure, don't inform user (attacker) that email doesn't exists
}
//create and store reset token
timeNow := time.Now()
expirationTime := timeNow.Add(time.Minute * tokenExpirationTime)
randomArray := make([]byte, randomStringLength)
rand.Read(randomArray)
token := fmt.Sprintf("%x", sha256.Sum256(randomArray))
passwordResetEntry := viewmodel.PasswordReset{
User: user,
Token: token,
Expires: expirationTime,
Opened: false,
Used: false,
}
_, err = c.svc.PasswordReset.CreatePasswordResetEntry(passwordResetEntry)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
//Send email with reset link
notification := viewmodel.Notification{
Type: applicationservice.NotificationTypeEmail,
From: c.cfg.Email.Sender,
To: *user.Email,
Subject: passwordResetEmailSubject,
Message: passwordResetEmailMainBody + token + passwordResetEmailFooter,
}
notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, nil)
}
func (c *controller) handleResetComplete(ctx echo.Context) error {
userToken, err := routeutils.GetAndValidateStringParam(ctx, "token", "mandatory field")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
var user viewmodel.User
if err = ctx.Bind(&user); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if len(strings.TrimSpace(user.Pass)) < 1 {
routeutils.ResponseAPIPasswordResetFailed(ctx, "No password")
}
passwordResetEntry, err := c.svc.PasswordReset.GetByToken(userToken)
if err != nil || len(passwordResetEntry.Token) < 1 || passwordResetEntry.Expires.Before(time.Now()) || passwordResetEntry.Used == true {
routeutils.ResponseAPIPasswordResetFailed(ctx, "Token error")
}
fullUserData, err := c.svc.Users.GetByUUID(passwordResetEntry.User.ID, "")
if err != nil {
routeutils.ResponseAPIPasswordResetFailed(ctx, "User problem")
}
fmt.Println(fullUserData)
//write new password in database
//TODO
if err := c.svc.PasswordReset.SetTokenUsed(userToken); err != nil {
routeutils.ResponseAPIPasswordResetFailed(ctx, "Reset failed")
}
//Send email with reset link
notification := viewmodel.Notification{
Type: applicationservice.NotificationTypeEmail,
From: c.cfg.Email.Sender,
To: *user.Email,
Subject: passwordResetCompleteEmailSubject,
Message: passwordResetCompleteEmailBody,
}
notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, nil)
}
func (c *controller) handleTokenOpen(ctx echo.Context) error {
token, err := routeutils.GetAndValidateStringParam(ctx, "token", "mandatory field")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
if err := c.svc.PasswordReset.SetTokenOpened(token); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, nil)
}

View File

@@ -0,0 +1,21 @@
package passwordresetroute
import (
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/infra/config"
"github.com/labstack/echo"
)
const (
resetRequest = "/request/:email"
resetComplete = "/complete/:token"
tokenOpen = "/open/:token"
)
func Register(r *echo.Group, cfg *config.Config, svc *applicationservice.Service) {
ctrl := controllerInstance(svc, cfg)
r.POST(resetRequest, ctrl.handleResetRequest)
r.POST(resetComplete, ctrl.handleResetComplete)
r.POST(tokenOpen, ctrl.handleTokenOpen)
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -2,6 +2,7 @@ package selfregisterroute
import (
"fmt"
"strings"
"sync"
b64 "encoding/base64"
@@ -22,6 +23,10 @@ const (
notificationSmsBody = "You have registered as a Visit Reporter for CHM NEMT. Login: https://portal.bcbsinstitute.com Reset PW: https://portal.bcbsinstitute.com/forgot"
)
const (
phoneNumberMaxLength = 12
)
var (
instance *controller
once sync.Once
@@ -44,12 +49,34 @@ func controllerInstance(svc *applicationservice.Service, cfg *config.Config) *co
return instance
}
func removeNonNumberChars(input string) string {
result := ""
for _, char := range input {
if char >= '0' && char <= '9' {
result += string(char)
}
}
return result
}
func (c *controller) handle(ctx echo.Context) error {
var user viewmodel.User
if err := ctx.Bind(&user); err != nil {
return routeutils.HandleAPIError(ctx, err)
}
//format phone number - max length in database is 12 chars
formatedPhoneNumber := strings.TrimSpace(*user.PhoneNumber)
formatedPhoneNumber = strings.Replace(formatedPhoneNumber, "+1", "", -1)
formatedPhoneNumber = removeNonNumberChars(formatedPhoneNumber)
if len(formatedPhoneNumber) > phoneNumberMaxLength {
formatedPhoneNumber = formatedPhoneNumber[:phoneNumberMaxLength]
}
*user.PhoneNumber = formatedPhoneNumber
authUser, err := c.svc.Users.GetByUUID("573c70ff-733d-11e7-ba8f-0a6ad3fcdeaa", "")
if err != nil {
return routeutils.HandleAPIError(ctx, err)
@@ -132,10 +159,14 @@ func (c *controller) handle(ctx echo.Context) error {
notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
logger := ctx.Logger()
logger.Warnf("Application Error: Could not send email notification to user email : %s", *user.Email)
}
//Send sms notification to Authorized user
formatedPhoneNumber = *user.PhoneNumber
notification = viewmodel.Notification{
Type: applicationservice.NOtificationTypeSMS,
To: *user.PhoneNumber,
@@ -144,7 +175,8 @@ func (c *controller) handle(ctx echo.Context) error {
notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
logger := ctx.Logger()
logger.Warnf("Application Error: Could not send sms notification to user mobile : %s", *user.PhoneNumber)
}
return routeutils.ResponseAPIOK(ctx, user)

View File

@@ -289,7 +289,7 @@ func (c *controller) handle(ctx echo.Context) error {
requestRide.Destination = newOrigin
}
if c.cfg.LyftProd.UserUUID != createdUser.ID {
if createdUser.Test {
resp, err = c.tnc.Lyft.RequestRide(requestRide)
} else {
fmt.Println("In Production")
@@ -404,7 +404,7 @@ func (c *controller) handle(ctx echo.Context) error {
newRide.Passenger.LastName = " "
newRide.Passenger.PhoneNumber = *user.PhoneNumber
if c.cfg.LyftProd.UserUUID != createdUser.ID {
if createdUser.Test {
newRide, err = c.tnc.Lyft.RequestRide(newRide)
} else {
fmt.Println("In Production")
@@ -514,7 +514,7 @@ func (c *controller) handleRawLyft(ctx echo.Context) error {
}
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if user.Test {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
@@ -546,7 +546,7 @@ func (c *controller) handleRideETA(ctx echo.Context) error {
if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" {
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if user.Test {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
@@ -614,7 +614,7 @@ func (c *controller) handleCancel(ctx echo.Context) error {
requestRide.RideID = "s_" + ride.InternalID
}
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if user.Test {
err = c.tnc.Lyft.CancelRide(requestRide)
if err != nil && err.Error() == "ride_not_found" {
err = nil
@@ -719,7 +719,7 @@ func (c *controller) handleRide(ctx echo.Context) error {
if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" {
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if user.Test {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
@@ -858,7 +858,7 @@ func (c *controller) handleReady(ctx echo.Context) error {
}
var lyftRide viewmodel.RideRequest
if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if user.Test {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID})
} else {
fmt.Println("In Production")
@@ -880,7 +880,7 @@ func (c *controller) handleReady(ctx echo.Context) error {
lyftRide.Passenger.PhoneNumber = *nextRide.User.PhoneNumber
lyftRide.RideType = "lyft"
if c.cfg.LyftProd.UserUUID != nextRide.CreatedUser.ID {
if user.Test {
lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide)
} else {
fmt.Println("In Production")

View File

@@ -128,6 +128,13 @@ func (c *controller) handleTwilio(ctx echo.Context) error {
message = fmt.Sprintf("We received a request to cancel a ride from you at %s, but cannot find a ride for this mobile number.", libphonenumber.Format(num, libphonenumber.NATIONAL))
}
}
authUser, err := c.svc.Users.GetByUUID(lastRide.CreatedUser.ID, "")
if err != nil {
fmt.Println("Error to author of the ride: ", err.Error())
message = "There was a problem to call your ride"
}
if !isDriver {
if requestMessage == "I AM READY" && lastRide.UUID != "" {
if (lastRide.Visit.TripType.Key == "roundtrip_call" && (lastRide.TripType.Key == "to_visit" || lastRide.TripType.Key == "from_visit_call")) || lastRide.Visit.TripType.Key == "from_visit_call" {
@@ -161,7 +168,7 @@ func (c *controller) handleTwilio(ctx echo.Context) error {
lyftRide.Passenger.PhoneNumber = *readyRide.User.PhoneNumber
lyftRide.RideType = "lyft"
if c.cfg.LyftProd.UserUUID != readyRide.CreatedUser.ID {
if authUser.Test {
lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide)
} else {
fmt.Println("In Production")
@@ -209,7 +216,7 @@ func (c *controller) handleTwilio(ctx echo.Context) error {
if requestMessage == "YES" && lastRide.UUID != "" {
var lyftRide viewmodel.RideRequest
if lastRide.CreatedUser.ID != c.cfg.LyftProd.UserUUID {
if authUser.Test {
lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: lastRide.InternalID})
if err != nil {
fmt.Println("Error: ", err.Error())

View File

@@ -1,13 +1,9 @@
package usersroute
import (
"bytes"
b64 "encoding/base64"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"strings"
"sync"
"time"
@@ -22,8 +18,6 @@ import (
"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
"bitbucket.org/nemt/nemt-portal-api/server/validation"
"github.com/labstack/echo"
"golang.org/x/net/context"
"googlemaps.github.io/maps"
)
const (
@@ -345,11 +339,6 @@ func (c *controller) handleMember(ctx echo.Context) error {
return routeutils.HandleAPIError(ctx, err)
}
//Validate member
if validationErrors := validation.ValidateEligibility(&user); len(validationErrors) > 0 {
return routeutils.ResponseAPICustomValidationError(ctx, "eligibility validation failed", validationErrors)
}
authUser, err := auth.GetUserDetail(ctx, c.cfg)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
@@ -359,153 +348,20 @@ func (c *controller) handleMember(ctx echo.Context) error {
return routeutils.ResponseAPIAuthError(ctx, "phonenumber or email is required", false)
}
if len(user.Pass) == 0 {
user.Pass = c.generatePassword(8)
} else {
pass, err := b64.StdEncoding.DecodeString(user.Pass)
if err != nil {
return routeutils.ResponseAPIAuthError(ctx, "Invalid password", false)
}
user.Pass = string(pass)
}
if user.BirthDate == nil || user.BirthDate.IsZero() {
return routeutils.ResponseAPIAuthError(ctx, "birthdate is required", false)
}
if user.Member == nil || len(*user.Member) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "member is required", false)
}
if user.Gender == nil || len(*user.Gender) == 0 || (*user.Gender != "M" && *user.Gender != "F" && *user.Gender != "U") {
return routeutils.ResponseAPIAuthError(ctx, "gender is required", false)
}
if len(user.Name) == 0 && len(user.First) == 0 && len(user.Last) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "name is required", false)
}
if len(user.First) != 0 && len(user.Last) != 0 {
user.Name = fmt.Sprintf("%s %s", user.First, user.Last)
}
profile, err := c.svc.Profile.GetByKey("US")
provider := viewmodel.ProviderResp{}
user, err = c.svc.Users.CheckAndCreateMember(user, provider, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
user.Profiles = append(user.Profiles, profile)
eligibility := viewmodel.Eligibility{}
eligibility.Provider.ProviderNPI = "1699849786"
eligibility.Provider.ProviderName = "LITHOLINK CORPORATION"
eligibility.TrackingID = user.ID
eligibility.Subscriber.SubscriberID = *user.Member
eligibility.Subscriber.PatientType = "S"
eligibility.Subscriber.Name.First = user.First
eligibility.Subscriber.Name.Last = user.Last
eligibility.Subscriber.DemographicInfo.DateOfBirth = *user.BirthDate
eligibility.Subscriber.DemographicInfo.Gender = *user.Gender
eligibility.ServiceInfo.DateOfService = time.Now()
eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"}
/*
resp, err := c.bcbsi.BXE.Get271(eligibility)
if err != nil {
fmt.Println("Eligibility Not Found or Error: ", err.Error())
return routeutils.ResponseAPINotEligibleError(ctx)
}
*/
//This part is emulating eligibility check for testing purposes
client := &http.Client{}
eligibilityJson, err := json.Marshal(eligibility)
if err != nil {
return routeutils.ResponseAPINotEligibleError(ctx)
}
req, _ := http.NewRequest("POST", c.cfg.Eligibility.Url, bytes.NewBuffer(eligibilityJson))
req.Header.Add("App", c.cfg.HTTP.Auth.AppKey)
req.Header.Add("Token", ctx.Request().Header.Get("Token"))
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return routeutils.ResponseAPINotEligibleError(ctx)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 300 {
return routeutils.ResponseAPINotEligibleError(ctx)
}
eligibilityResponse := viewmodel.Interchange271{}
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&eligibilityResponse)
if err != nil {
return routeutils.ResponseAPINotEligibleError(ctx)
}
//================================================================
if len(eligibilityResponse.Division.HealthCareEligibilityResponse.LoopHL0030) < 1 {
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
header := eligibilityResponse.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950
body := eligibilityResponse.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
cleanZipcode := strings.TrimSpace(body.N403)
trimmedZipcode := cleanZipcode
if len(cleanZipcode) > zipcodeTrimLength {
trimmedZipcode = cleanZipcode[:zipcodeTrimLength]
}
_, err = c.svc.Zipcodes.GetByParticipatingZipcode(trimmedZipcode)
if err != nil {
return routeutils.ResponseAPINotEligibleWithMessageError(ctx, "Member's Home zipcode, "+trimmedZipcode+", 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.HandleAPIError(ctx, err)
}
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.HandleAPIError(ctx, err)
}
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())
if validationError, ok := err.(*viewmodel.ValidationError); ok {
if len(validationError.Errors) > 0 {
return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors)
} else {
return routeutils.ResponseAPIServiceError(ctx, validationError.Message)
}
} else {
return routeutils.HandleAPIError(ctx, err)
}
}
user, err = c.svc.Users.Create(user, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, user)
}
@@ -547,6 +403,7 @@ func (c *controller) handleBulkPortal(ctx echo.Context) error {
return routeutils.ResponseAPIAuthError(ctx, "name is required", false)
}
users[i].Test = true
if len(users[i].First) != 0 && len(users[i].Last) != 0 {
users[i].Name = fmt.Sprintf("%s %s", users[i].First, users[i].Last)
}
@@ -608,6 +465,7 @@ func (c *controller) handlePortal(ctx echo.Context) error {
if len(user.First) != 0 && len(user.Last) != 0 {
user.Name = fmt.Sprintf("%s %s", user.First, user.Last)
}
user.Test = true
user, err = c.svc.Users.Create(user, authUser)
if err != nil {

View File

@@ -1,15 +1,12 @@
package visitroute
import (
"context"
"fmt"
"math/rand"
"strings"
"sync"
"time"
b64 "encoding/base64"
"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
"bitbucket.org/nemt/nemt-portal-api/application/third/eligibility/bcbsi"
"bitbucket.org/nemt/nemt-portal-api/application/tncservice"
@@ -20,7 +17,6 @@ import (
"bitbucket.org/nemt/nemt-portal-api/server/validation"
"github.com/labstack/echo"
uuid "github.com/satori/go.uuid"
"googlemaps.github.io/maps"
)
var (
@@ -81,103 +77,39 @@ func (c *controller) handleRide(ctx echo.Context) error {
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
visit.User.PhoneNumber = ride.User.PhoneNumber
visit.User.Email = ride.User.Email
visit.User.Consent = true
ride.Visit = visit
user, err := c.svc.Users.GetByMemberID(*visit.User.Member)
if err != nil {
fmt.Println(err)
return routeutils.HandleAPIError(ctx, err)
}
user.PhoneNumber = ride.User.PhoneNumber
user.Email = ride.User.Email
ride.Visit.User = user
if ride.Visit.User.Type == nil {
subscriber := "S"
ride.Visit.User.Type = &subscriber
}
var provider viewmodel.ProviderResp
provider, err = c.svc.Provider.GetByUUID(ride.Visit.Provider.ProviderUUID, authUser)
if err != nil {
return routeutils.HandleAPIError(ctx, err)
}
visit.Provider = provider
ride.Visit.Provider = provider
eligibility := viewmodel.Eligibility{}
eligibility.Provider.ProviderNPI = provider.InternalID
eligibility.Provider.ProviderName = provider.OrganizatioName
eligibility.TrackingID = c.rangeIn(1000000, 9999999)
eligibility.Subscriber.SubscriberID = *ride.Visit.User.Member
eligibility.Subscriber.PatientType = *ride.Visit.User.Type
eligibility.Subscriber.Name.First = ride.Visit.User.First
eligibility.Subscriber.Name.Last = ride.Visit.User.Last
eligibility.Subscriber.DemographicInfo.DateOfBirth = *ride.Visit.User.BirthDate
eligibility.Subscriber.DemographicInfo.Gender = *ride.Visit.User.Gender
loc, _ := time.LoadLocation("America/Chicago")
eligibility.ServiceInfo.DateOfService = time.Now().In(loc)
eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"}
resp271, err := c.bcbsi.BXE.Get271(eligibility)
// user, err := c.svc.Users.GetByUUID(ride.User.ID, "US")
// if err != nil {
// return routeutils.HandleAPIError(ctx, err)
// }
user, err := c.svc.Users.CheckAndCreateMember(ride.Visit.User, provider, authUser)
if err != nil {
fmt.Println(err)
return routeutils.ResponseAPIValidationError(ctx, err.Error())
}
address := viewmodel.Address{}
header := resp271.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950
body := resp271.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960
zipCode := strings.TrimSpace(body.N403)
address.AddressTypeName = "Home"
address.AddressType = "home"
address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401)
address.Address = fmt.Sprintf("%s, %s (%s)", header.N301, body.N401, zipCode)
address.CreatedUserUUID = authUser.ID
address.User = user
address.Type = "provider"
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.HandleAPIError(ctx, err)
}
r := &maps.GeocodingRequest{
Address: address.Address + " " + body.N402 + ", " + zipCode,
}
result, err := googleMapsAPI.Geocode(context.Background(), r)
if err != nil {
fmt.Println("Error to instantiate googles api: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
if len(result) > 0 {
address.Latitude = result[0].Geometry.Location.Lat
address.Longitude = result[0].Geometry.Location.Lng
}
if address.Latitude != 0 && address.Longitude != 0 {
if len(user.Addresses) > 0 {
for _, a := range user.Addresses {
if a.AddressType == "home" {
err := c.svc.Users.RemoveAddress(a.UUID)
if err != nil {
fmt.Println("Error to remove old address: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
}
if validationError, ok := err.(*viewmodel.ValidationError); ok {
if len(validationError.Errors) > 0 {
return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors)
} else {
return routeutils.ResponseAPIServiceError(ctx, validationError.Message)
}
}
address, err = c.svc.Users.SaveAddress(address)
if err != nil {
fmt.Println("Error to save address: ", err.Error())
} else {
return routeutils.HandleAPIError(ctx, err)
}
user.Addresses = append(user.Addresses, address)
}
ride.Visit.User = user
ride.User = user
visit.User = user
homeAddress := viewmodel.Address{}
for _, a := range visit.User.Addresses {
@@ -206,9 +138,9 @@ func (c *controller) handleRide(ctx echo.Context) error {
}
ride.Notes = ride.Notes
ride.Passenger.FirstName = visit.User.First
ride.Passenger.FirstName = ride.Visit.User.First
ride.Passenger.LastName = " "
ride.Passenger.PhoneNumber = *visit.User.PhoneNumber
ride.Passenger.PhoneNumber = *ride.Visit.User.PhoneNumber
ride.RideType = "lyft"
ride.VisitDate = &visit.VisitDatetime
ride.VisitTime = &visit.VisitDatetime
@@ -226,7 +158,7 @@ func (c *controller) handleRide(ctx echo.Context) error {
ride.Destination = newOrigin
}
if c.cfg.LyftProd.UserUUID != authUser.ID {
if authUser.Test {
resp, err = c.tnc.Lyft.RequestRide(ride)
} else {
fmt.Println("In Production")
@@ -340,7 +272,7 @@ func (c *controller) handleRide(ctx echo.Context) error {
newRide.Passenger.LastName = " "
newRide.Passenger.PhoneNumber = *visit.User.PhoneNumber
if c.cfg.LyftProd.UserUUID != authUser.ID {
if authUser.Test {
newRide, err = c.tnc.Lyft.RequestRide(newRide)
} else {
fmt.Println("In Production")
@@ -471,154 +403,19 @@ func (c *controller) handle(ctx echo.Context) error {
return routeutils.ResponseAPICustomValidationError(ctx, "visit validation failed", validationErrors)
}
eligibility := viewmodel.Eligibility{}
eligibility.Provider.ProviderNPI = provider.InternalID
eligibility.Provider.ProviderName = provider.OrganizatioName
eligibility.TrackingID = c.rangeIn(1000000, 9999999)
eligibility.Subscriber.SubscriberID = *visit.User.Member
eligibility.Subscriber.PatientType = *visit.User.Type
eligibility.Subscriber.Name.First = visit.User.First
eligibility.Subscriber.Name.Last = visit.User.Last
eligibility.Subscriber.DemographicInfo.DateOfBirth = *visit.User.BirthDate
eligibility.Subscriber.DemographicInfo.Gender = *visit.User.Gender
loc, _ := time.LoadLocation("America/Chicago")
eligibility.ServiceInfo.DateOfService = time.Now().In(loc)
eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"}
resp, err := c.bcbsi.BXE.Get271(eligibility)
user, err := c.svc.Users.CheckAndCreateMember(visit.User, provider, authUser)
if err != nil {
fmt.Println("Error to get eligibility: ", err.Error())
return routeutils.ResponseAPIValidationError(ctx, err.Error())
}
user, err := c.svc.Users.GetByMemberID(*visit.User.Member)
if err != nil {
fmt.Println("Error to get user by memberID: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
if user.ID == "" {
if len(user.Pass) == 0 {
user.Pass = c.generatePassword(8)
if validationError, ok := err.(*viewmodel.ValidationError); ok {
if len(validationError.Errors) > 0 {
return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors)
} else {
return routeutils.ResponseAPIServiceError(ctx, validationError.Message)
}
} else {
pass, err := b64.StdEncoding.DecodeString(user.Pass)
if err != nil {
return routeutils.ResponseAPIAuthError(ctx, "Invalid password", false)
}
user.Pass = string(pass)
}
if user.BirthDate == nil || user.BirthDate.IsZero() {
return routeutils.ResponseAPIAuthError(ctx, "birthdate is required", false)
}
if user.Member == nil || len(*user.Member) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "member is required", false)
}
if user.Gender == nil || len(*user.Gender) == 0 || (*user.Gender != "M" && *user.Gender != "F" && *user.Gender != "U") {
return routeutils.ResponseAPIAuthError(ctx, "gender is required", false)
}
if len(user.Name) == 0 && len(user.First) == 0 && len(user.Last) == 0 {
return routeutils.ResponseAPIAuthError(ctx, "name is required", false)
}
if len(user.First) != 0 && len(user.Last) != 0 {
user.Name = fmt.Sprintf("%s %s", user.First, user.Last)
}
if user.PhoneNumber != nil && len(*user.PhoneNumber) == 10 && !strings.Contains(*user.PhoneNumber, "+1") {
phoneNumber := fmt.Sprintf("+1%s", *user.PhoneNumber)
user.PhoneNumber = &phoneNumber
}
profile, err := c.svc.Profile.GetByKey("US")
if err != nil {
fmt.Println("Error to get Profile: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
user.Profiles = append(user.Profiles, profile)
user, err = c.svc.Users.Create(user, authUser)
if err != nil {
fmt.Println("Error to create the user: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
} else {
if visit.User.Email != nil && len(*visit.User.Email) > 0 {
user.Email = visit.User.Email
}
if visit.User.PhoneNumber != nil {
if len(*visit.User.PhoneNumber) == 10 && !strings.Contains(*visit.User.PhoneNumber, "+1") {
phoneNumber := fmt.Sprintf("+1%s", *visit.User.PhoneNumber)
visit.User.PhoneNumber = &phoneNumber
}
user.PhoneNumber = visit.User.PhoneNumber
}
err = c.svc.Users.UpdateLogin(user)
if err != nil {
fmt.Println("Error to update login: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
}
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
zipCode := strings.TrimSpace(body.N403)
address.AddressTypeName = "Home"
address.AddressType = "home"
address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401)
address.Address = fmt.Sprintf("%s, %s (%s)", header.N301, body.N401, zipCode)
address.CreatedUserUUID = authUser.ID
address.User = user
address.Type = "provider"
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.HandleAPIError(ctx, err)
}
r := &maps.GeocodingRequest{
Address: address.Address + " " + body.N402 + ", " + zipCode,
}
result, err := googleMapsAPI.Geocode(context.Background(), r)
if err != nil {
fmt.Println("Error to instantiate googles api: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
if len(result) > 0 {
address.Latitude = result[0].Geometry.Location.Lat
address.Longitude = result[0].Geometry.Location.Lng
}
if address.Latitude != 0 && address.Longitude != 0 {
if len(user.Addresses) > 0 {
for _, a := range user.Addresses {
if a.AddressType == "home" {
err := c.svc.Users.RemoveAddress(a.UUID)
if err != nil {
fmt.Println("Error to remove old address: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
}
}
}
address, err = c.svc.Users.SaveAddress(address)
if err != nil {
fmt.Println("Error saving address: ", err.Error())
return routeutils.HandleAPIError(ctx, err)
}
user.Addresses = append(user.Addresses, address)
}
visit.User = user
visit.TripType = viewmodel.TripType{
Key: "no_trip",
@@ -634,7 +431,11 @@ func (c *controller) handle(ctx echo.Context) error {
return routeutils.HandleAPIError(ctx, err)
}
return routeutils.ResponseAPIOK(ctx, visit)
if authUser.Profiles[0].Key == "VIRPT" {
return routeutils.ResponseNoContent(ctx, nil)
} else {
return routeutils.ResponseAPIOK(ctx, visit)
}
}
func (c *controller) handleGetByID(ctx echo.Context) error {

View File

@@ -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

View File

@@ -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

BIN
svijetlastrana Executable file

Binary file not shown.