Skip to content

Commit

Permalink
Merge 69d7f30 into a8ff651
Browse files Browse the repository at this point in the history
  • Loading branch information
hawx authored Mar 11, 2024
2 parents a8ff651 + 69d7f30 commit 90b051b
Showing 20 changed files with 602 additions and 273 deletions.
14 changes: 8 additions & 6 deletions internal/actor/organisation.go
Original file line number Diff line number Diff line change
@@ -30,17 +30,19 @@ type Member struct {
CreatedAt time.Time
// UpdatedAt is when the Member was last updated
UpdatedAt time.Time
// LastLoggedInAt is when the Member last logged in to the service
LastLoggedInAt time.Time
// ID is a unique identifier for the Member
ID string
Email string
FirstNames string
LastName string
ID string
// OrganisationID identifies the organisation the invite is for
OrganisationID string
Email string
FirstNames string
LastName string
// Permission is the type of permissions assigned to the member to set available actions in an Organisation
Permission Permission
// Status controls access to the Organisation
Status Status
// LastLoggedInAt is when the Member last logged in to the service
LastLoggedInAt time.Time
}

func (i Member) FullName() string {
71 changes: 69 additions & 2 deletions internal/app/member_store.go
Original file line number Diff line number Diff line change
@@ -57,14 +57,62 @@ func (s *memberStore) DeleteMemberInvite(ctx context.Context, organisationID, em
return nil
}

func (s *memberStore) Create(ctx context.Context, invite *actor.MemberInvite) error {
func (s *memberStore) Create(ctx context.Context, firstNames, lastName string) (*actor.Member, error) {
data, err := page.SessionDataFromContext(ctx)
if err != nil {
return nil, err
}

if data.SessionID == "" {
return nil, errors.New("memberStore.Create requires SessionID")
}

if data.Email == "" {
return nil, errors.New("memberStore.Create requires Email")
}

organisationID := s.uuidString()

member := &actor.Member{
PK: organisationKey(organisationID),
SK: memberKey(data.SessionID),
ID: s.uuidString(),
OrganisationID: organisationID,
Email: data.Email,
FirstNames: firstNames,
LastName: lastName,
CreatedAt: s.now(),
UpdatedAt: s.now(),
Permission: actor.PermissionAdmin,
Status: actor.StatusActive,
LastLoggedInAt: s.now(),
}

if err := s.dynamoClient.Create(ctx, member); err != nil {
return nil, fmt.Errorf("error creating member: %w", err)
}

link := &organisationLink{
PK: member.PK,
SK: memberIDKey(member.ID),
MemberSK: member.SK,
}

if err := s.dynamoClient.Create(ctx, link); err != nil {
return nil, fmt.Errorf("error creating organisation link: %w", err)
}

return member, nil
}

func (s *memberStore) CreateFromInvite(ctx context.Context, invite *actor.MemberInvite) error {
data, err := page.SessionDataFromContext(ctx)
if err != nil {
return err
}

if data.SessionID == "" {
return errors.New("memberStore.Create requires SessionID")
return errors.New("memberStore.CreateFromInvite requires SessionID")
}

member := &actor.Member{
@@ -73,6 +121,7 @@ func (s *memberStore) Create(ctx context.Context, invite *actor.MemberInvite) er
CreatedAt: s.now(),
UpdatedAt: s.now(),
ID: s.uuidString(),
OrganisationID: invite.OrganisationID,
Email: invite.Email,
FirstNames: invite.FirstNames,
LastName: invite.LastName,
@@ -222,6 +271,24 @@ func (s *memberStore) Get(ctx context.Context) (*actor.Member, error) {
return member, nil
}

func (s *memberStore) GetAny(ctx context.Context) (*actor.Member, error) {
data, err := page.SessionDataFromContext(ctx)
if err != nil {
return nil, err
}

if data.SessionID == "" {
return nil, errors.New("memberStore.Get requires SessionID")
}

var member *actor.Member
if err := s.dynamoClient.OneBySK(ctx, memberKey(data.SessionID), &member); err != nil {
return nil, err
}

return member, nil
}

func (s *memberStore) Put(ctx context.Context, member *actor.Member) error {
member.UpdatedAt = s.now()
return s.dynamoClient.Put(ctx, member)
107 changes: 101 additions & 6 deletions internal/app/member_store_test.go
Original file line number Diff line number Diff line change
@@ -337,6 +337,101 @@ func TestMemberStoreGetWhenErrors(t *testing.T) {
}

func TestMemberStoreCreate(t *testing.T) {
ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "session-id", Email: "[email protected]"})
expectedMember := &actor.Member{
PK: "ORGANISATION#a-uuid",
SK: "MEMBER#session-id",
CreatedAt: testNow,
UpdatedAt: testNow,
ID: "a-uuid",
OrganisationID: "a-uuid",
Email: "[email protected]",
FirstNames: "a",
LastName: "b",
Permission: actor.PermissionAdmin,
Status: actor.StatusActive,
LastLoggedInAt: testNow,
}

dynamoClient := newMockDynamoClient(t)
dynamoClient.EXPECT().
Create(ctx, expectedMember).
Return(nil).
Once()
dynamoClient.EXPECT().
Create(ctx, &organisationLink{
PK: "ORGANISATION#a-uuid",
SK: "MEMBERID#a-uuid",
MemberSK: "MEMBER#session-id",
}).
Return(nil).
Once()

memberStore := &memberStore{dynamoClient: dynamoClient, now: testNowFn, uuidString: func() string { return "a-uuid" }}

member, err := memberStore.Create(ctx, "a", "b")
assert.Nil(t, err)
assert.Equal(t, expectedMember, member)
}

func TestMemberStoreCreateWhenSessionMissing(t *testing.T) {
testCases := map[string]context.Context{
"missing session": context.Background(),
"missing session ID": page.ContextWithSessionData(context.Background(), &page.SessionData{}),
}

for name, ctx := range testCases {
t.Run(name, func(t *testing.T) {
memberStore := &memberStore{dynamoClient: nil, now: testNowFn, uuidString: func() string { return "a-uuid" }}

_, err := memberStore.Create(ctx, "a", "b")
assert.Error(t, err)
})
}
}

func TestMemberStoreCreateWhenDynamoErrors(t *testing.T) {
ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "session-id", Email: "a"})

testcases := map[string]struct {
dynamoClientSetup func(*mockDynamoClient)
}{
"member": {
dynamoClientSetup: func(dynamoClient *mockDynamoClient) {
dynamoClient.EXPECT().
Create(ctx, mock.Anything).
Return(expectedError).
Once()
},
},
"link": {
dynamoClientSetup: func(dynamoClient *mockDynamoClient) {
dynamoClient.EXPECT().
Create(ctx, mock.Anything).
Return(nil).
Once()
dynamoClient.EXPECT().
Create(ctx, mock.Anything).
Return(expectedError).
Once()
},
},
}

for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
dynamoClient := newMockDynamoClient(t)
tc.dynamoClientSetup(dynamoClient)

memberStore := &memberStore{dynamoClient: dynamoClient, now: testNowFn, uuidString: func() string { return "a-uuid" }}

_, err := memberStore.Create(ctx, "a", "b")
assert.ErrorIs(t, err, expectedError)
})
}
}

func TestMemberStoreCreateFromInvite(t *testing.T) {
ctx := page.ContextWithSessionData(context.Background(), &page.SessionData{SessionID: "session-id"})

invite := &actor.MemberInvite{
@@ -357,6 +452,7 @@ func TestMemberStoreCreate(t *testing.T) {
CreatedAt: testNow,
UpdatedAt: testNow,
ID: "a-uuid",
OrganisationID: "org-id",
Email: invite.Email,
FirstNames: invite.FirstNames,
LastName: invite.LastName,
@@ -380,11 +476,11 @@ func TestMemberStoreCreate(t *testing.T) {

memberStore := &memberStore{dynamoClient: dynamoClient, now: testNowFn, uuidString: func() string { return "a-uuid" }}

err := memberStore.Create(ctx, invite)
err := memberStore.CreateFromInvite(ctx, invite)
assert.Nil(t, err)
}

func TestMemberStoreCreateWhenSessionMissing(t *testing.T) {
func TestMemberStoreCreateFromInviteWhenSessionMissing(t *testing.T) {
testCases := map[string]context.Context{
"missing session": context.Background(),
"missing session ID": page.ContextWithSessionData(context.Background(), &page.SessionData{}),
@@ -394,13 +490,13 @@ func TestMemberStoreCreateWhenSessionMissing(t *testing.T) {
t.Run(name, func(t *testing.T) {
memberStore := &memberStore{dynamoClient: nil, now: testNowFn, uuidString: func() string { return "a-uuid" }}

err := memberStore.Create(ctx, &actor.MemberInvite{})
err := memberStore.CreateFromInvite(ctx, &actor.MemberInvite{})
assert.Error(t, err)
})
}
}

func TestMemberStoreCreateWhenDynamoErrors(t *testing.T) {
func TestMemberStoreCreateFromInviteWhenDynamoErrors(t *testing.T) {
testcases := map[string]struct {
createMemberError error
deleteOneError error
@@ -441,11 +537,10 @@ func TestMemberStoreCreateWhenDynamoErrors(t *testing.T) {
}
memberStore := &memberStore{dynamoClient: dynamoClient, now: testNowFn, uuidString: func() string { return "a-uuid" }}

err := memberStore.Create(ctx, &actor.MemberInvite{})
err := memberStore.CreateFromInvite(ctx, &actor.MemberInvite{})
assert.Error(t, err)
})
}

}

func TestMemberStoreGetByID(t *testing.T) {
39 changes: 5 additions & 34 deletions internal/app/organisation_store.go
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ type organisationLink struct {
MemberSK string
}

func (s *organisationStore) Create(ctx context.Context, name string) (*actor.Organisation, error) {
func (s *organisationStore) Create(ctx context.Context, member *actor.Member, name string) (*actor.Organisation, error) {
data, err := page.SessionDataFromContext(ctx)
if err != nil {
return nil, err
@@ -39,16 +39,10 @@ func (s *organisationStore) Create(ctx context.Context, name string) (*actor.Org
return nil, errors.New("organisationStore.Create requires SessionID")
}

if data.Email == "" {
return nil, errors.New("organisationStore.Create requires Email")
}

organisationID := s.uuidString()

organisation := &actor.Organisation{
PK: organisationKey(organisationID),
SK: organisationKey(organisationID),
ID: organisationID,
PK: organisationKey(member.OrganisationID),
SK: organisationKey(member.OrganisationID),
ID: member.OrganisationID,
Name: name,
CreatedAt: s.now(),
}
@@ -57,32 +51,9 @@ func (s *organisationStore) Create(ctx context.Context, name string) (*actor.Org
return nil, fmt.Errorf("error creating organisation: %w", err)
}

member := &actor.Member{
PK: organisationKey(organisationID),
SK: memberKey(data.SessionID),
ID: s.uuidString(),
Email: data.Email,
CreatedAt: s.now(),
Permission: actor.PermissionAdmin,
Status: actor.StatusActive,
}

if err := s.dynamoClient.Create(ctx, member); err != nil {
return nil, fmt.Errorf("error creating organisation member: %w", err)
}

link := &organisationLink{
PK: member.PK,
SK: memberIDKey(member.ID),
MemberSK: member.SK,
}

if err := s.dynamoClient.Create(ctx, link); err != nil {
return nil, fmt.Errorf("error creating organisation link: %w", err)
}

return organisation, nil
}

func (s *organisationStore) Get(ctx context.Context) (*actor.Organisation, error) {
data, err := page.SessionDataFromContext(ctx)
if err != nil {
Loading

0 comments on commit 90b051b

Please sign in to comment.