Skip to content

Commit

Permalink
feat: create client-user profile (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
Salaton committed Oct 22, 2021
1 parent 25e56bc commit 17dc65f
Show file tree
Hide file tree
Showing 35 changed files with 1,812 additions and 313 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ require (
gorm.io/datatypes v1.0.2
gorm.io/driver/postgres v1.1.2
gorm.io/gorm v1.21.16
moul.io/http2curl v1.0.0 // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
Expand Down Expand Up @@ -473,6 +474,7 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
Expand Down Expand Up @@ -678,7 +680,9 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
Expand Down Expand Up @@ -1323,6 +1327,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
Expand Down
1 change: 1 addition & 0 deletions gqlgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ autobind:
- "github.com/savannahghi/onboarding/pkg/onboarding/application/dto"
- "github.com/savannahghi/onboarding-service/pkg/onboarding/domain"
- "github.com/savannahghi/onboarding-service/pkg/onboarding/application/dto"
- "github.com/savannahghi/onboarding-service/pkg/onboarding/application/enums"
- "github.com/savannahghi/enumutils"
- "github.com/savannahghi/scalarutils"
- "github.com/savannahghi/serverutils"
Expand Down
48 changes: 24 additions & 24 deletions pkg/onboarding/application/dto/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

"github.com/savannahghi/enumutils"
"github.com/savannahghi/feedlib"
"github.com/savannahghi/onboarding-service/pkg/onboarding/domain"
"github.com/savannahghi/onboarding-service/pkg/onboarding/application/enums"
"gorm.io/datatypes"
)

Expand Down Expand Up @@ -71,7 +71,7 @@ func (i *FacilitySortInput) ToURLValues() (values url.Values) {
type MetricInput struct {

// TODO Metric types should be a controlled list i.e enum
Type domain.MetricType `json:"metric_type"`
Type enums.MetricType `json:"metric_type"`

// this will vary by context
// should not identify the user (there's a UID field)
Expand Down Expand Up @@ -99,28 +99,6 @@ type LoginInput struct {
Flavour feedlib.Flavour `json:"flavour"`
}

// UserInput contains the input for the User
type UserInput struct {
Username string // @handle, also globally unique; nickname

DisplayName string // user's preferred display name

// TODO Consider making the names optional in DB; validation in frontends
FirstName string // given name
MiddleName string
LastName string

// UserType string // TODO enum; e.g client, health care worker

// Gender string // TODO enum; genders; keep it simple

// Contacts []*domain.Contact // TODO: validate, ensure

// // for the preferred language list, order matters
// Languages []string // TODO: turn this into a slice of enums, start small (en, sw)
Flavour feedlib.Flavour
}

// StaffProfileInput contains input required to register a staff
type StaffProfileInput struct {
StaffNumber string
Expand All @@ -136,3 +114,25 @@ type StaffProfileInput struct {

// Addresses []*domain.UserAddress
}

// ClientProfileInput is used to supply the client profile input
type ClientProfileInput struct {
ClientType enums.ClientType `json:"clientType"`
}

// UserInput is used to supply user input for registration
type UserInput struct {
// TODO Consider making the names optional in DB; validation in frontends
FirstName string `json:"firstName"`
MiddleName string `json:"middleName"`
LastName string `json:"lastName"`
UserName string `json:"userName"`
DisplayName string `json:"displayName"`
Gender enumutils.Gender `json:"gender"`
Flavour feedlib.Flavour `json:"flavour"`

// UserType string // TODO enum; e.g client, health care worker
// Contacts []*domain.Contact // TODO: validate, ensure
// // for the preferred language list, order matters
// Languages []string // TODO: turn this into a slice of enums, start small (en, sw)
}
57 changes: 57 additions & 0 deletions pkg/onboarding/application/enums/client_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package enums

import (
"fmt"
"io"
"strconv"
)

// ClientType defines various client types
type ClientType string

const (
// ClientTypePmtct represents a Prevention of mother-to-child transmission client type
ClientTypePmtct ClientType = "PMTCT"

// ClientTypeOvc represents an Orphan and Vulnerable Children client type
ClientTypeOvc ClientType = "OVC"
)

// AllClientType represents a slice of all possible `ClientType` values
var AllClientType = []ClientType{
ClientTypePmtct,
ClientTypeOvc,
}

// IsValid returns true if a client type is valid
func (e ClientType) IsValid() bool {
switch e {
case ClientTypePmtct, ClientTypeOvc:
return true
}
return false
}

// String ...
func (e ClientType) String() string {
return string(e)
}

// UnmarshalGQL converts the supplied value to a metric type.
func (e *ClientType) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}

*e = ClientType(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ClientType", str)
}
return nil
}

// MarshalGQL writes the metric type to the supplied writer
func (e ClientType) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package domain
package enums

import (
"fmt"
Expand Down
1 change: 0 additions & 1 deletion pkg/onboarding/domain/enum_test.go

This file was deleted.

29 changes: 19 additions & 10 deletions pkg/onboarding/domain/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import (
"time"

"github.com/google/uuid"
"github.com/savannahghi/enumutils"
"github.com/savannahghi/feedlib"
"github.com/savannahghi/onboarding-service/pkg/onboarding/application/enums"
"github.com/savannahghi/scalarutils"
"gorm.io/datatypes"
)

Expand Down Expand Up @@ -56,14 +59,14 @@ type User struct {

// UserType string // TODO enum; e.g client, health care worker

// Gender string // TODO enum; genders; keep it simple
Gender enumutils.Gender

Active bool

// Contacts []*Contact // TODO: validate, ensure

// // for the preferred language list, order matters
// Languages []string // TODO: turn this into a slice of enums, start small (en, sw)
// for the preferred language list, order matters
Languages []enumutils.Language // TODO: turn this into a slice of enums, start small (en, sw)

// PushTokens []string

Expand Down Expand Up @@ -133,11 +136,11 @@ type ClientProfile struct {
// every client is a user first
// biodata is linked to the user record
// the client record is for bridging to other identifiers e.g patient record IDs
UserID uuid.UUID // TODO: Foreign key to User
UserID *string // TODO: Foreign key to User

TreatmentEnrollmentDate *time.Time // use for date of treatment enrollment
TreatmentEnrollmentDate *scalarutils.Date // use for date of treatment enrollment

ClientType string // TODO: enum; e.g PMTCT, OVC
ClientType enums.ClientType

Active bool

Expand Down Expand Up @@ -187,9 +190,9 @@ type RelatedPerson struct {
OtherName string // TODO: optional
Gender string // TODO: enum

DateOfBirth *time.Time // TODO: optional
Addresses []*UserAddress // TODO: optional
Contacts []*Contact // TODO: optional
DateOfBirth *scalarutils.Date // TODO: optional
Addresses []*UserAddress // TODO: optional
Contacts []*Contact // TODO: optional
}

// ClientProfileRegistrationPayload holds the registration input we need to register a client
Expand Down Expand Up @@ -237,7 +240,7 @@ type Metric struct {
MetricID *string

// TODO Metric types should be a controlled list i.e enum
Type MetricType
Type enums.MetricType

// this will vary by context
// should not identify the user (there's a UID field)
Expand Down Expand Up @@ -284,3 +287,9 @@ type StaffUserProfile struct {
User *User
Staff *StaffProfile
}

// ClientUserProfile represents the clients profile and associated user profile
type ClientUserProfile struct {
User *User `json:"user"`
Client *ClientProfile `json:"client"`
}
47 changes: 47 additions & 0 deletions pkg/onboarding/infrastructure/database/postgres/gorm/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ type Create interface {
CollectMetrics(ctx context.Context, metrics *Metric) (*Metric, error)
SetUserPIN(ctx context.Context, pinData *PINData) (bool, error)
RegisterStaffUser(ctx context.Context, user *User, staff *StaffProfile) (*StaffUserProfile, error)
RegisterClient(
ctx context.Context,
userInput *User,
clientInput *ClientProfile,
) (*ClientUserProfile, error)
}

// GetOrCreateFacility ...
Expand Down Expand Up @@ -85,3 +90,45 @@ func (db *PGInstance) RegisterStaffUser(ctx context.Context, user *User, staff *
Staff: staff,
}, nil
}

// RegisterClient picks the clients registration details and saves them in the database
func (db *PGInstance) RegisterClient(
ctx context.Context,
user *User,
clientProfile *ClientProfile,
) (*ClientUserProfile, error) {
// begin a transaction
tx := db.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()

if err := tx.Error; err != nil {
return nil, err
}

if err := tx.Create(user).Error; err != nil {
tx.Rollback()
return nil, fmt.Errorf("failed to create a user %v", err)
}

clientProfile.UserID = user.UserID

if err := tx.Create(clientProfile).Error; err != nil {
tx.Rollback()
return nil, fmt.Errorf("failed to create a staff profile %v", err)
}

if err := tx.Commit().Error; err != nil {
tx.Rollback()
return nil, fmt.Errorf("transaction commit to create a staff profile failed: %v", err)
}

return &ClientUserProfile{
User: user,
Client: clientProfile,
}, nil

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import (
"time"

"github.com/google/uuid"
"github.com/savannahghi/onboarding-service/pkg/onboarding/domain"
"github.com/savannahghi/enumutils"
"github.com/savannahghi/onboarding-service/pkg/onboarding/application/enums"
"github.com/savannahghi/onboarding-service/pkg/onboarding/infrastructure/database/postgres/gorm"
"github.com/segmentio/ksuid"
"gorm.io/datatypes"
Expand All @@ -26,6 +27,7 @@ type GormMock struct {
GetUserPINByUserIDFn func(ctx context.Context, userID string) (*gorm.PINData, error)
GetUserProfileByUserIDFn func(ctx context.Context, userID string, flavour string) (*gorm.User, error)
RegisterStaffUserFn func(ctx context.Context, user *gorm.User, staff *gorm.StaffProfile) (*gorm.StaffUserProfile, error)
RegisterClientFn func(ctx context.Context, userInput *gorm.User, clientInput *gorm.ClientProfile) (*gorm.ClientUserProfile, error)

//Updates
UpdateUserLastSuccessfulLoginFn func(ctx context.Context, userID string, lastLoginTime time.Time, flavour string) error
Expand All @@ -37,6 +39,22 @@ type GormMock struct {
// NewGormMock initializes a new instance of `GormMock` then mocking the case of success.
func NewGormMock() *GormMock {
return &GormMock{
RegisterClientFn: func(ctx context.Context, userInput *gorm.User, clientInput *gorm.ClientProfile) (*gorm.ClientUserProfile, error) {
return &gorm.ClientUserProfile{
User: &gorm.User{
FirstName: "FirstName",
LastName: "Last Name",
Username: "User Name",
MiddleName: userInput.MiddleName,
DisplayName: "Display Name",
Gender: enumutils.GenderMale,
},
Client: &gorm.ClientProfile{
ClientType: enums.ClientTypeOvc,
},
}, nil
},

GetOrCreateFacilityFn: func(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error) {
id := uuid.New().String()
name := "Kanairo One"
Expand Down Expand Up @@ -95,7 +113,7 @@ func NewGormMock() *GormMock {
metricID := uuid.New().String()
return &gorm.Metric{
MetricID: &metricID,
Type: domain.EngagementMetrics,
Type: enums.EngagementMetrics,
Payload: datatypes.JSON([]byte(`{"who": "test user", "keyword": "suicidal"}`)),
Timestamp: now,
UID: ksuid.New().String(),
Expand Down Expand Up @@ -282,3 +300,12 @@ func (gm *GormMock) UpdateUserNextAllowedLogin(ctx context.Context, userID strin
func (gm *GormMock) RegisterStaffUser(ctx context.Context, user *gorm.User, staff *gorm.StaffProfile) (*gorm.StaffUserProfile, error) {
return gm.RegisterStaffUserFn(ctx, user, staff)
}

// RegisterClient mocks the implementation of RegisterClient method
func (gm *GormMock) RegisterClient(
ctx context.Context,
userInput *gorm.User,
clientInput *gorm.ClientProfile,
) (*gorm.ClientUserProfile, error) {
return gm.RegisterClientFn(ctx, userInput, clientInput)
}
Loading

0 comments on commit 17dc65f

Please sign in to comment.