diff --git a/go.mod b/go.mod index 86f45ad4..eb1e25a0 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/savannahghi/feedlib v0.0.1 github.com/savannahghi/firebasetools v0.0.13 github.com/savannahghi/interserviceclient v0.0.13 - github.com/savannahghi/profileutils v0.0.12 + github.com/savannahghi/profileutils v0.0.13 github.com/savannahghi/pubsubtools v0.0.2 github.com/savannahghi/scalarutils v0.0.2 github.com/savannahghi/serverutils v0.0.3 diff --git a/go.sum b/go.sum index 5baa4025..2f70e8d4 100644 --- a/go.sum +++ b/go.sum @@ -392,8 +392,8 @@ github.com/savannahghi/interserviceclient v0.0.13 h1:hKUrWkUQW7mNZLp/wxJNnzORDpN github.com/savannahghi/interserviceclient v0.0.13/go.mod h1:aGGEc+40bBVHVDs1k0CzK5uPb5080vv4fb25T/cuXQk= github.com/savannahghi/profileutils v0.0.6/go.mod h1:Ct0sjzOW9zjDN58ynT5GTV6M2hHMY3nsFDV08I6gpO4= github.com/savannahghi/profileutils v0.0.7/go.mod h1:Ct0sjzOW9zjDN58ynT5GTV6M2hHMY3nsFDV08I6gpO4= -github.com/savannahghi/profileutils v0.0.12 h1:7ZYbvNVKS2XPHhlw6nRl4Lec8O0CLKQrBYkWEGeyFfI= -github.com/savannahghi/profileutils v0.0.12/go.mod h1:Ct0sjzOW9zjDN58ynT5GTV6M2hHMY3nsFDV08I6gpO4= +github.com/savannahghi/profileutils v0.0.13 h1:qAGoINpVBVxH02Tbn22vQlo5nRpiYBBYeBor6vEdyEI= +github.com/savannahghi/profileutils v0.0.13/go.mod h1:Ct0sjzOW9zjDN58ynT5GTV6M2hHMY3nsFDV08I6gpO4= github.com/savannahghi/pubsubtools v0.0.2 h1:5JqgynuuzduYu57y8TM0bU8unZD/7ob36sZ9BKPmt9o= github.com/savannahghi/pubsubtools v0.0.2/go.mod h1:FQ+BxO4uA1ZkcziY4dR0uT9CKHVHb+4L1lUURBV2UD0= github.com/savannahghi/scalarutils v0.0.0-20210622091443-bad5089abdad/go.mod h1:Z+Dl3wc3vy5zKvthctHAtYzol1p8w27zEVRfOYueoks= diff --git a/pkg/onboarding/application/dto/input.go b/pkg/onboarding/application/dto/input.go index 4ef6ca2d..f704ed4a 100644 --- a/pkg/onboarding/application/dto/input.go +++ b/pkg/onboarding/application/dto/input.go @@ -406,6 +406,6 @@ type RoleInput struct { // RolePermissionInput input required to create a permission type RolePermissionInput struct { - RoleID string `json:"roleID"` - Scope string `json:"scope"` + RoleID string `json:"roleID"` + Scopes []string `json:"scopes"` } diff --git a/pkg/onboarding/application/dto/output.go b/pkg/onboarding/application/dto/output.go index 19f6d01b..92b330c3 100644 --- a/pkg/onboarding/application/dto/output.go +++ b/pkg/onboarding/application/dto/output.go @@ -138,6 +138,7 @@ type RoleOutput struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` + Active bool `json:"active"` Scopes []string `json:"scopes"` Permissions []profileutils.Permission `json:"permissions"` } diff --git a/pkg/onboarding/infrastructure/database/fb/firebase.go b/pkg/onboarding/infrastructure/database/fb/firebase.go index 64ec0657..db57ce30 100644 --- a/pkg/onboarding/infrastructure/database/fb/firebase.go +++ b/pkg/onboarding/infrastructure/database/fb/firebase.go @@ -27,7 +27,6 @@ import ( "github.com/savannahghi/pubsubtools" "github.com/savannahghi/scalarutils" "github.com/savannahghi/serverutils" - "github.com/sirupsen/logrus" ) // Package that generates trace information @@ -3333,8 +3332,6 @@ func (fr *Repository) SaveCoverAutolinkingEvents( ctx, span := tracer.Start(ctx, "SaveCoverAutolinkingEvents") defer span.End() - logrus.Print("SaveCoverAutolinkingEvents") - coverLinkingEvent := &dto.CoverLinkingEvent{ ID: input.ID, CoverLinkingEventTime: input.CoverLinkingEventTime, @@ -3672,74 +3669,101 @@ func (fr *Repository) UpdateAITSessionDetails(ctx context.Context, phoneNumber s return nil } -// CreateRole role details to database +// CreateRole creates a new role and persists it to the database func (fr *Repository) CreateRole( ctx context.Context, - roleDetails profileutils.Role, + profileID string, + input dto.RoleInput, ) (*profileutils.Role, error) { ctx, span := tracer.Start(ctx, "CreateRole") defer span.End() - createCommad := &CreateCommand{ - CollectionName: fr.GetRolesCollectionName(), - Data: roleDetails, + exists, err := fr.CheckIfRoleNameExists(ctx, input.Name) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err } - docRef, err := fr.FirestoreClient.Create(ctx, createCommad) - if err != nil { + if exists { + err := fmt.Errorf("role with similar name exists:%v", input.Name) utils.RecordSpanError(span, err) - return nil, exceptions.InternalServerError(err) + return nil, err } - getRoleQuery := &GetSingleQuery{ - CollectionName: fr.GetCoverLinkingEventsCollectionName(), - Value: docRef.ID, + timestamp := time.Now().In(pubsubtools.TimeLocation) + + role := profileutils.Role{ + ID: uuid.New().String(), + Name: input.Name, + Description: input.Description, + CreatedBy: profileID, + Created: timestamp, + Active: true, + Scopes: input.Scopes, } - docsnapshot, err := fr.FirestoreClient.Get(ctx, getRoleQuery) - if err != nil { - utils.RecordSpanError(span, err) - return nil, exceptions.InternalServerError(err) + createCommad := &CreateCommand{ + CollectionName: fr.GetRolesCollectionName(), + Data: role, } - role := &profileutils.Role{} - err = docsnapshot.DataTo(role) + _, err = fr.FirestoreClient.Create(ctx, createCommad) if err != nil { utils.RecordSpanError(span, err) return nil, exceptions.InternalServerError(err) } - return role, nil + return &role, nil } -// UpdateRoleDetails ... -func (fr *Repository) UpdateRoleDetails(ctx context.Context, roleDetails profileutils.Role) error { +// UpdateRoleDetails updates the details of a role +func (fr *Repository) UpdateRoleDetails( + ctx context.Context, + profileID string, + role profileutils.Role, +) (*profileutils.Role, error) { ctx, span := tracer.Start(ctx, "UpdateRoleDetails") defer span.End() - collectionName := fr.GetRolesCollectionName() query := &GetAllQuery{ - CollectionName: collectionName, + CollectionName: fr.GetRolesCollectionName(), + Value: role.ID, FieldName: "id", - Value: roleDetails.ID, Operator: "==", } docs, err := fr.FirestoreClient.GetAll(ctx, query) if err != nil { - return err + utils.RecordSpanError(span, err) + return nil, err + } + + timestamp := time.Now().In(pubsubtools.TimeLocation) + + updatedRole := profileutils.Role{ + ID: role.ID, + Name: role.Name, + Description: role.Description, + Active: role.Active, + Scopes: role.Scopes, + CreatedBy: role.CreatedBy, + Created: role.Created, + UpdatedBy: profileID, + Updated: timestamp, } updateCommand := &UpdateCommand{ - CollectionName: collectionName, + CollectionName: fr.GetRolesCollectionName(), ID: docs[0].Ref.ID, - Data: roleDetails, + Data: updatedRole, } err = fr.FirestoreClient.Update(ctx, updateCommand) if err != nil { - return exceptions.InternalServerError(err) + utils.RecordSpanError(span, err) + return nil, exceptions.InternalServerError(err) } - return nil + + return &updatedRole, nil } // GetRoleByID gets role with matching id @@ -3747,9 +3771,8 @@ func (fr *Repository) GetRoleByID(ctx context.Context, roleID string) (*profileu ctx, span := tracer.Start(ctx, "GetRoleByID") defer span.End() - collectionName := fr.GetRolesCollectionName() query := &GetAllQuery{ - CollectionName: collectionName, + CollectionName: fr.GetRolesCollectionName(), FieldName: "id", Value: roleID, Operator: "==", @@ -3761,20 +3784,21 @@ func (fr *Repository) GetRoleByID(ctx context.Context, roleID string) (*profileu return nil, exceptions.InternalServerError(err) } - if len(docs) == 0 { + if len(docs) != 1 { err = exceptions.ProfileNotFoundError(fmt.Errorf("role not found")) utils.RecordSpanError(span, err) return nil, err } - dsnap := docs[0] role := &profileutils.Role{} - err = dsnap.DataTo(role) + + err = docs[0].DataTo(role) if err != nil { utils.RecordSpanError(span, err) err = fmt.Errorf("unable to read role") return nil, exceptions.InternalServerError(err) } + return role, nil } @@ -3797,3 +3821,29 @@ func (fr *Repository) GetRolesByIDs( return &roles, nil } + +// CheckIfRoleNameExists checks if a role with a similar name exists +// Ensures unique name for each role during creation +func (fr *Repository) CheckIfRoleNameExists(ctx context.Context, name string) (bool, error) { + ctx, span := tracer.Start(ctx, "CheckIfRoleNameExists") + defer span.End() + + query := &GetAllQuery{ + CollectionName: fr.GetRolesCollectionName(), + FieldName: "name", + Operator: "==", + Value: name, + } + + docs, err := fr.FirestoreClient.GetAll(ctx, query) + if err != nil { + utils.RecordSpanError(span, err) + return false, exceptions.InternalServerError(err) + } + + if len(docs) == 1 { + return true, nil + } + + return false, nil +} diff --git a/pkg/onboarding/infrastructure/database/fb/firebase_test.go b/pkg/onboarding/infrastructure/database/fb/firebase_test.go index 4a590bb1..b5eb48cd 100644 --- a/pkg/onboarding/infrastructure/database/fb/firebase_test.go +++ b/pkg/onboarding/infrastructure/database/fb/firebase_test.go @@ -1335,3 +1335,443 @@ func TestRepository_GetAITDetails_Unnittest(t *testing.T) { }) } } + +func TestRepository_CreateRole(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + role := &profileutils.Role{ + ID: uuid.NewString(), + Name: "Accountant", + Description: "Can handle money", + } + + type args struct { + ctx context.Context + profileID string + input dto.RoleInput + } + tests := []struct { + name string + args args + want *profileutils.Role + wantErr bool + }{ + { + name: "valid:create a new role", + args: args{ + ctx: ctx, + profileID: uuid.NewString(), + input: dto.RoleInput{ + Name: "Accountant", + Description: "Can handle money", + }, + }, + want: role, + wantErr: false, + }, + { + name: "invalid: role with similar name exists", + args: args{ + ctx: ctx, + profileID: uuid.NewString(), + input: dto.RoleInput{ + Name: "Accountant", + Description: "Can handle money", + }, + }, + want: nil, + wantErr: true, + }, + { + name: "invalid: check role with similar name error", + args: args{ + ctx: ctx, + profileID: uuid.NewString(), + input: dto.RoleInput{ + Name: "Accountant", + Description: "Can handle money", + }, + }, + want: nil, + wantErr: true, + }, + { + name: "invalid: firestore create role error", + args: args{ + ctx: ctx, + profileID: uuid.NewString(), + input: dto.RoleInput{ + Name: "Accountant", + Description: "Can handle money", + }, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid:create a new role" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docs := []*firestore.DocumentSnapshot{} + return docs, nil + } + fakeFireStoreClientExt.CreateFn = func(ctx context.Context, command *fb.CreateCommand) (*firestore.DocumentRef, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + return docRef, nil + } + } + + if tt.name == "invalid: role with similar name exists" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + } + + if tt.name == "invalid: check role with similar name error" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + + return nil, fmt.Errorf("cannot get all") + } + } + + if tt.name == "invalid: firestore create role error" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docs := []*firestore.DocumentSnapshot{} + return docs, nil + } + fakeFireStoreClientExt.CreateFn = func(ctx context.Context, command *fb.CreateCommand) (*firestore.DocumentRef, error) { + + return nil, fmt.Errorf("cannot create role") + } + } + + got, err := repo.CreateRole(tt.args.ctx, tt.args.profileID, tt.args.input) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.CreateRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Repository.CreateRole() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRepository_UpdateRoleDetails(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + profileID := uuid.NewString() + role := profileutils.Role{ + ID: uuid.NewString(), + Name: "Accountant", + Description: "Can handle money", + } + + type args struct { + ctx context.Context + profileID string + role profileutils.Role + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "invalid: cannot retrive role", + args: args{ + ctx: ctx, + profileID: profileID, + role: role, + }, + wantErr: true, + }, + { + name: "invalid: cannot update role", + args: args{ + ctx: ctx, + profileID: profileID, + role: role, + }, + wantErr: true, + }, + { + name: "valid:success update role", + args: args{ + ctx: ctx, + profileID: profileID, + role: role, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if tt.name == "invalid: cannot retrive role" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + + return nil, fmt.Errorf("cannot retrieve role") + } + } + + if tt.name == "invalid: cannot update role" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + fakeFireStoreClientExt.UpdateFn = func(ctx context.Context, command *fb.UpdateCommand) error { + return fmt.Errorf("cannot update the role") + } + } + + if tt.name == "valid:success update role" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + fakeFireStoreClientExt.UpdateFn = func(ctx context.Context, command *fb.UpdateCommand) error { + return nil + } + } + + _, err := repo.UpdateRoleDetails(tt.args.ctx, tt.args.profileID, tt.args.role) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.UpdateRoleDetails() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func TestRepository_GetRoleByID(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + type args struct { + ctx context.Context + roleID string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "fail: cannot retrieve firestore docs", + args: args{ + ctx: ctx, + roleID: uuid.NewString(), + }, + wantErr: true, + }, + { + name: "fail: multiple firestore records", + args: args{ + ctx: ctx, + roleID: uuid.NewString(), + }, + wantErr: true, + }, + { + name: "fail: no firestore record", + args: args{ + ctx: ctx, + roleID: uuid.NewString(), + }, + wantErr: true, + }, + { + name: "success: retrieve role", + args: args{ + ctx: ctx, + roleID: uuid.NewString(), + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if tt.name == "fail: cannot retrieve firestore docs" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + + return nil, fmt.Errorf("cannot retrieve firestore docs") + } + } + + if tt.name == "fail: multiple firestore records" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docRef2 := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}, {Ref: docRef2}} + return docs, nil + } + } + + if tt.name == "fail: no firestore record" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docs := []*firestore.DocumentSnapshot{} + return docs, nil + } + } + + if tt.name == "success: retrieve role" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + } + + _, err := repo.GetRoleByID(tt.args.ctx, tt.args.roleID) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.GetRoleByID() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + +func TestRepository_CheckIfRoleNameExists(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + type args struct { + ctx context.Context + name string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "fail: cannot retrieve firestore documents", + args: args{ + ctx: ctx, + name: "accountant", + }, + want: false, + wantErr: true, + }, + { + name: "success: similar role exists", + args: args{ + ctx: ctx, + name: "accountant", + }, + want: true, + wantErr: false, + }, + { + name: "success: similar role doesn't exist", + args: args{ + ctx: ctx, + name: "accountant", + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if tt.name == "fail: cannot retrieve firestore documents" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, fmt.Errorf("cannot get firestore docs") + } + } + + if tt.name == "success: similar role exists" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + } + + if tt.name == "success: similar role doesn't exist" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docs := []*firestore.DocumentSnapshot{} + return docs, nil + } + } + + got, err := repo.CheckIfRoleNameExists(tt.args.ctx, tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.CheckIfRoleNameExists() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Repository.CheckIfRoleNameExists() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRepository_GetRolesByIDs(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + type args struct { + ctx context.Context + roleIDs []string + } + tests := []struct { + name string + args args + want *[]profileutils.Role + wantErr bool + }{ + + { + name: "fail: cannot retrieve role by id", + args: args{ + ctx: ctx, + roleIDs: []string{uuid.NewString(), uuid.NewString()}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + + if tt.name == "success: retrieve role by id" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + } + + if tt.name == "fail: cannot retrieve role by id" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + + return nil, fmt.Errorf("cannot retrieve role") + } + } + + t.Run(tt.name, func(t *testing.T) { + _, err := repo.GetRolesByIDs(tt.args.ctx, tt.args.roleIDs) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.GetRolesByIDs() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} diff --git a/pkg/onboarding/presentation/graph/generated/generated.go b/pkg/onboarding/presentation/graph/generated/generated.go index 65f10705..b0680256 100644 --- a/pkg/onboarding/presentation/graph/generated/generated.go +++ b/pkg/onboarding/presentation/graph/generated/generated.go @@ -465,6 +465,7 @@ type ComplexityRoot struct { } RoleOutput struct { + Active func(childComplexity int) int Description func(childComplexity int) int ID func(childComplexity int) int Name func(childComplexity int) int @@ -2925,6 +2926,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ReceivablesAccount.Tag(childComplexity), true + case "RoleOutput.active": + if e.complexity.RoleOutput.Active == nil { + break + } + + return e.complexity.RoleOutput.Active(childComplexity), true + case "RoleOutput.description": if e.complexity.RoleOutput.Description == nil { break @@ -4028,7 +4036,7 @@ input RoleInput { input RolePermissionInput { roleID: String! - scope: String! + scopes: [String!]! } `, BuiltIn: false}, {Name: "pkg/onboarding/presentation/graph/profile.graphql", Input: `extend type Query { @@ -4681,6 +4689,7 @@ type RoleOutput { id: String! name: String! description: String! + active: Boolean! scopes: [String] permissions: [Permission] } @@ -16025,6 +16034,41 @@ func (ec *executionContext) _RoleOutput_description(ctx context.Context, field g return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _RoleOutput_active(ctx context.Context, field graphql.CollectedField, obj *dto.RoleOutput) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "RoleOutput", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Active, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _RoleOutput_scopes(ctx context.Context, field graphql.CollectedField, obj *dto.RoleOutput) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -20672,11 +20716,11 @@ func (ec *executionContext) unmarshalInputRolePermissionInput(ctx context.Contex if err != nil { return it, err } - case "scope": + case "scopes": var err error - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope")) - it.Scope, err = ec.unmarshalNString2string(ctx, v) + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scopes")) + it.Scopes, err = ec.unmarshalNString2ᚕstringᚄ(ctx, v) if err != nil { return it, err } @@ -23168,6 +23212,11 @@ func (ec *executionContext) _RoleOutput(ctx context.Context, sel ast.SelectionSe if out.Values[i] == graphql.Null { invalids++ } + case "active": + out.Values[i] = ec._RoleOutput_active(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "scopes": out.Values[i] = ec._RoleOutput_scopes(ctx, field, obj) case "permissions": diff --git a/pkg/onboarding/presentation/graph/inputs.graphql b/pkg/onboarding/presentation/graph/inputs.graphql index a9d3b5e7..59e74416 100644 --- a/pkg/onboarding/presentation/graph/inputs.graphql +++ b/pkg/onboarding/presentation/graph/inputs.graphql @@ -333,5 +333,5 @@ input RoleInput { input RolePermissionInput { roleID: String! - scope: String! + scopes: [String!]! } diff --git a/pkg/onboarding/presentation/graph/types.graphql b/pkg/onboarding/presentation/graph/types.graphql index 1acf92c8..3767b714 100644 --- a/pkg/onboarding/presentation/graph/types.graphql +++ b/pkg/onboarding/presentation/graph/types.graphql @@ -486,6 +486,7 @@ type RoleOutput { id: String! name: String! description: String! + active: Boolean! scopes: [String] permissions: [Permission] } diff --git a/pkg/onboarding/repository/mock/onboarding.go b/pkg/onboarding/repository/mock/onboarding.go index 51ae7961..e1569ed0 100644 --- a/pkg/onboarding/repository/mock/onboarding.go +++ b/pkg/onboarding/repository/mock/onboarding.go @@ -184,10 +184,11 @@ type FakeOnboardingRepository struct { GetAITDetailsFn func(ctx context.Context, phoneNumber string) (*domain.USSDLeadDetails, error) //roles - CreateRoleFn func(ctx context.Context, role profileutils.Role) (*profileutils.Role, error) - UpdateRoleDetailsFn func(ctx context.Context, role profileutils.Role) error - GetRolesByIDsFn func(ctx context.Context, roleIDs []string) (*[]profileutils.Role, error) - GetRoleByIDFn func(ctx context.Context, roleID string) (*profileutils.Role, error) + CreateRoleFn func(ctx context.Context, profileID string, role dto.RoleInput) (*profileutils.Role, error) + UpdateRoleDetailsFn func(ctx context.Context, profileID string, role profileutils.Role) (*profileutils.Role, error) + GetRolesByIDsFn func(ctx context.Context, roleIDs []string) (*[]profileutils.Role, error) + GetRoleByIDFn func(ctx context.Context, roleID string) (*profileutils.Role, error) + CheckIfRoleNameExistsFn func(ctx context.Context, name string) (bool, error) } // GetSupplierProfileByID ... @@ -886,17 +887,19 @@ func (f *FakeOnboardingRepository) SaveCoverAutolinkingEvents( //CreateRole ... func (f *FakeOnboardingRepository) CreateRole( ctx context.Context, - role profileutils.Role, + profileID string, + input dto.RoleInput, ) (*profileutils.Role, error) { - return f.CreateRoleFn(ctx, role) + return f.CreateRoleFn(ctx, profileID, input) } //UpdateRoleDetails ... func (f *FakeOnboardingRepository) UpdateRoleDetails( ctx context.Context, + profileID string, role profileutils.Role, -) error { - return f.UpdateRoleDetailsFn(ctx, role) +) (*profileutils.Role, error) { + return f.UpdateRoleDetailsFn(ctx, profileID, role) } //GetRoleByID ... @@ -914,3 +917,8 @@ func (f *FakeOnboardingRepository) GetRolesByIDs( ) (*[]profileutils.Role, error) { return f.GetRolesByIDsFn(ctx, roleIDs) } + +// CheckIfRoleNameExists ... +func (f *FakeOnboardingRepository) CheckIfRoleNameExists(ctx context.Context, name string) (bool, error) { + return f.CheckIfRoleNameExistsFn(ctx, name) +} diff --git a/pkg/onboarding/repository/onboarding.go b/pkg/onboarding/repository/onboarding.go index 69ef281d..f1f0e984 100644 --- a/pkg/onboarding/repository/onboarding.go +++ b/pkg/onboarding/repository/onboarding.go @@ -306,8 +306,21 @@ type UserProfileRepository interface { //RolesRepository interface that provide access to all persistent storage operations for roles type RolesRepository interface { - CreateRole(ctx context.Context, role profileutils.Role) (*profileutils.Role, error) - GetRolesByIDs(ctx context.Context, roleIDs []string) (*[]profileutils.Role, error) + CreateRole( + ctx context.Context, + profileID string, + input dto.RoleInput, + ) (*profileutils.Role, error) + GetRoleByID(ctx context.Context, roleID string) (*profileutils.Role, error) - UpdateRoleDetails(ctx context.Context, role profileutils.Role) error + + GetRolesByIDs(ctx context.Context, roleIDs []string) (*[]profileutils.Role, error) + + CheckIfRoleNameExists(ctx context.Context, name string) (bool, error) + + UpdateRoleDetails( + ctx context.Context, + profileID string, + role profileutils.Role, + ) (*profileutils.Role, error) } diff --git a/pkg/onboarding/usecases/roles.go b/pkg/onboarding/usecases/roles.go index f1f2a402..97bac054 100644 --- a/pkg/onboarding/usecases/roles.go +++ b/pkg/onboarding/usecases/roles.go @@ -3,16 +3,13 @@ package usecases import ( "context" "fmt" - "time" - "github.com/google/uuid" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/onboarding/pkg/onboarding/application/exceptions" "github.com/savannahghi/onboarding/pkg/onboarding/application/extension" "github.com/savannahghi/onboarding/pkg/onboarding/application/utils" "github.com/savannahghi/onboarding/pkg/onboarding/repository" "github.com/savannahghi/profileutils" - "github.com/savannahghi/pubsubtools" ) // RoleUseCase represent the business logic required for management of agents @@ -88,38 +85,24 @@ func (r *RoleUseCaseImpl) CreateRole( return nil, err } - timestamp := time.Now().In(pubsubtools.TimeLocation) - // move some logic to repository - // ID,timestamp,active - role := profileutils.Role{ - ID: uuid.New().String(), - Name: input.Name, - Description: input.Description, - CreatedBy: userProfile.ID, - Created: timestamp, - Active: true, - Scopes: input.Scopes, - } - - // save role to database - newRole, err := r.repo.CreateRole(ctx, role) + role, err := r.repo.CreateRole(ctx, userProfile.ID, input) if err != nil { utils.RecordSpanError(span, err) return nil, err } - // get permissions - perms, err := newRole.Permissions(ctx) + perms, err := role.Permissions(ctx) if err != nil { utils.RecordSpanError(span, err) return nil, err } output := &dto.RoleOutput{ - ID: newRole.ID, - Name: newRole.Name, - Description: newRole.Description, - Scopes: newRole.Scopes, + ID: role.ID, + Name: role.Name, + Description: role.Description, + Active: role.Active, + Scopes: role.Scopes, Permissions: perms, } @@ -140,6 +123,12 @@ func (r *RoleUseCaseImpl) AddPermissionsToRole( return nil, err } + userProfile, err := r.repo.GetUserProfileByUID(ctx, user.UID, false) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + // Check logged in user has the right permissions allowed, err := r.CheckUserHasPermission(ctx, user.UID, profileutils.CanEditRole) if err != nil { @@ -158,34 +147,38 @@ func (r *RoleUseCaseImpl) AddPermissionsToRole( return nil, err } - permission, err := profileutils.GetPermissionByScope(ctx, input.Scope) - if err != nil { - utils.RecordSpanError(span, err) - return nil, err - } + for _, scope := range input.Scopes { + permission, err := profileutils.GetPermissionByScope(ctx, scope) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } - if !role.HasPermission(ctx, permission.Scope) { - role.Scopes = append(role.Scopes, permission.Scope) + if !role.HasPermission(ctx, permission.Scope) { + role.Scopes = append(role.Scopes, permission.Scope) + } } // save role to database - err = r.repo.UpdateRoleDetails(ctx, *role) + updatedRole, err := r.repo.UpdateRoleDetails(ctx, userProfile.ID, *role) if err != nil { utils.RecordSpanError(span, err) return nil, err } // get permissions - perms, err := role.Permissions(ctx) + perms, err := updatedRole.Permissions(ctx) if err != nil { utils.RecordSpanError(span, err) return nil, err } output := &dto.RoleOutput{ - ID: role.ID, - Name: role.Name, - Scopes: role.Scopes, + ID: updatedRole.ID, + Name: updatedRole.Name, + Description: updatedRole.Description, + Active: updatedRole.Active, + Scopes: updatedRole.Scopes, Permissions: perms, } @@ -244,6 +237,7 @@ func (r *RoleUseCaseImpl) GetUserRoles( if err != nil { return nil, err } + return roles, nil } @@ -298,6 +292,8 @@ func (r *RoleUseCaseImpl) GetRole(ctx context.Context, ID string) (*dto.RoleOutp output := dto.RoleOutput{ ID: role.ID, Name: role.Name, + Description: role.Description, + Active: role.Active, Scopes: role.Scopes, Permissions: rolePerms, } diff --git a/pkg/onboarding/usecases/roles_unit_test.go b/pkg/onboarding/usecases/roles_unit_test.go index 8aa86d61..048d7cbc 100644 --- a/pkg/onboarding/usecases/roles_unit_test.go +++ b/pkg/onboarding/usecases/roles_unit_test.go @@ -174,7 +174,7 @@ func TestRoleUseCaseImpl_CreateRole(t *testing.T) { {ID: "123", Scopes: []string{"role.create"}}, }, nil } - fakeRepo.CreateRoleFn = func(ctx context.Context, role profileutils.Role) (*profileutils.Role, error) { + fakeRepo.CreateRoleFn = func(ctx context.Context, profileID string, role dto.RoleInput) (*profileutils.Role, error) { return nil, fmt.Errorf("error un able to create role in db") } } @@ -195,7 +195,7 @@ func TestRoleUseCaseImpl_CreateRole(t *testing.T) { }, nil } - fakeRepo.CreateRoleFn = func(ctx context.Context, role profileutils.Role) (*profileutils.Role, error) { + fakeRepo.CreateRoleFn = func(ctx context.Context, profileID string, role dto.RoleInput) (*profileutils.Role, error) { return &profileutils.Role{ ID: "123", Name: "Agents Role", @@ -230,7 +230,7 @@ func TestRoleUseCaseImpl_AddPermissionsToRole(t *testing.T) { input := dto.RolePermissionInput{ RoleID: "123", - Scope: "role.create", + Scopes: []string{"role.create"}, } expectedOutput := dto.RoleOutput{ @@ -393,8 +393,8 @@ func TestRoleUseCaseImpl_AddPermissionsToRole(t *testing.T) { Scopes: []string{"role.edit"}, }, nil } - fakeRepo.UpdateRoleDetailsFn = func(ctx context.Context, role profileutils.Role) error { - return fmt.Errorf("error unable to update role") + fakeRepo.UpdateRoleDetailsFn = func(ctx context.Context, profileID string, role profileutils.Role) (*profileutils.Role, error) { + return nil, fmt.Errorf("error unable to update role") } } @@ -416,8 +416,11 @@ func TestRoleUseCaseImpl_AddPermissionsToRole(t *testing.T) { Scopes: []string{"role.create"}, }, nil } - fakeRepo.UpdateRoleDetailsFn = func(ctx context.Context, role profileutils.Role) error { - return nil + fakeRepo.UpdateRoleDetailsFn = func(ctx context.Context, profileID string, role profileutils.Role) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "123", + Scopes: []string{"role.create"}, + }, nil } }