diff --git a/database/mock/store.go b/database/mock/store.go index a5be611d91..87c982acfa 100644 --- a/database/mock/store.go +++ b/database/mock/store.go @@ -1835,19 +1835,19 @@ func (mr *MockStoreMockRecorder) UpdateEvaluationTimes(arg0, arg1 any) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEvaluationTimes", reflect.TypeOf((*MockStore)(nil).UpdateEvaluationTimes), arg0, arg1) } -// UpdateInvitation mocks base method. -func (m *MockStore) UpdateInvitation(arg0 context.Context, arg1 string) (db.UserInvite, error) { +// UpdateInvitationRole mocks base method. +func (m *MockStore) UpdateInvitationRole(arg0 context.Context, arg1 db.UpdateInvitationRoleParams) (db.UserInvite, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateInvitation", arg0, arg1) + ret := m.ctrl.Call(m, "UpdateInvitationRole", arg0, arg1) ret0, _ := ret[0].(db.UserInvite) ret1, _ := ret[1].(error) return ret0, ret1 } -// UpdateInvitation indicates an expected call of UpdateInvitation. -func (mr *MockStoreMockRecorder) UpdateInvitation(arg0, arg1 any) *gomock.Call { +// UpdateInvitationRole indicates an expected call of UpdateInvitationRole. +func (mr *MockStoreMockRecorder) UpdateInvitationRole(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInvitation", reflect.TypeOf((*MockStore)(nil).UpdateInvitation), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInvitationRole", reflect.TypeOf((*MockStore)(nil).UpdateInvitationRole), arg0, arg1) } // UpdateLease mocks base method. diff --git a/database/query/invitations.sql b/database/query/invitations.sql index 0cf9fe376f..db1e71ec2a 100644 --- a/database/query/invitations.sql +++ b/database/query/invitations.sql @@ -58,9 +58,9 @@ INSERT INTO user_invites (code, email, role, project, sponsor) VALUES ($1, $2, $ -- name: DeleteInvitation :one DELETE FROM user_invites WHERE code = $1 RETURNING *; --- UpdateInvitation updates an invitation by its code. This is intended to be --- called by a user who has issued an invitation and then decided to bump its --- expiration. +-- UpdateInvitationRole updates an invitation by its code. This is intended to be +-- called by a user who has issued an invitation and then decided to change the +-- role of the invitee. --- name: UpdateInvitation :one -UPDATE user_invites SET updated_at = NOW() WHERE code = $1 RETURNING *; \ No newline at end of file +-- name: UpdateInvitationRole :one +UPDATE user_invites SET role = $2, updated_at = NOW() WHERE code = $1 RETURNING *; diff --git a/internal/controlplane/handlers_authz.go b/internal/controlplane/handlers_authz.go index 1e7a9d9672..d736e51095 100644 --- a/internal/controlplane/handlers_authz.go +++ b/internal/controlplane/handlers_authz.go @@ -702,32 +702,16 @@ func (s *Server) updateInvite( } defer s.store.Rollback(tx) - // At this point, there should be exactly 1 invitation. We should either update its expiration or - // discard it and create a new one - if existingInvites[0].Role != authzRole.String() { - // If there's an existing invite with a different role, we should delete it and create a new one - // Delete the existing invitation - _, err = s.store.DeleteInvitation(ctx, existingInvites[0].Code) - if err != nil { - return nil, status.Errorf(codes.Internal, "error deleting previous invitation: %v", err) - } - // Create a new invitation - userInvite, err = s.store.CreateInvitation(ctx, db.CreateInvitationParams{ - Code: invite.GenerateCode(), - Email: email, - Role: authzRole.String(), - Project: targetProject, - Sponsor: currentUser.ID, - }) - if err != nil { - return nil, status.Errorf(codes.Internal, "error creating invitation: %v", err) - } - } else { - // If the role is the same, we should update the expiration - userInvite, err = s.store.UpdateInvitation(ctx, existingInvites[0].Code) - if err != nil { - return nil, status.Errorf(codes.Internal, "error updating invitation: %v", err) - } + // At this point, there should be exactly 1 invitation. + // Depending on the role from the request, we can either update the role and its expiration + // or just bump the expiration date. + // In both cases, we can use the same query. + userInvite, err = s.store.UpdateInvitationRole(ctx, db.UpdateInvitationRoleParams{ + Code: existingInvites[0].Code, + Role: authzRole.String(), + }) + if err != nil { + return nil, status.Errorf(codes.Internal, "error updating invitation: %v", err) } // Resolve the project's display name @@ -747,6 +731,10 @@ func (s *Server) updateInvite( return nil, status.Errorf(codes.Internal, "error committing transaction: %v", err) } + // TODO: Publish the event for sending the invitation email + // This will happen only if the role is updated (existingInvites[0].Role != authzRole.String()) + // or the role stayed the same but the invite was created at least a day ago. + return &minder.UpdateRoleResponse{ Invitations: []*minder.Invitation{ { diff --git a/internal/db/invitations.sql.go b/internal/db/invitations.sql.go index 9ef2c9f865..2490dc6acc 100644 --- a/internal/db/invitations.sql.go +++ b/internal/db/invitations.sql.go @@ -277,16 +277,21 @@ func (q *Queries) ListInvitationsForProject(ctx context.Context, project uuid.UU return items, nil } -const updateInvitation = `-- name: UpdateInvitation :one +const updateInvitationRole = `-- name: UpdateInvitationRole :one -UPDATE user_invites SET updated_at = NOW() WHERE code = $1 RETURNING code, email, role, project, sponsor, created_at, updated_at +UPDATE user_invites SET role = $2, updated_at = NOW() WHERE code = $1 RETURNING code, email, role, project, sponsor, created_at, updated_at ` -// UpdateInvitation updates an invitation by its code. This is intended to be -// called by a user who has issued an invitation and then decided to bump its -// expiration. -func (q *Queries) UpdateInvitation(ctx context.Context, code string) (UserInvite, error) { - row := q.db.QueryRowContext(ctx, updateInvitation, code) +type UpdateInvitationRoleParams struct { + Code string `json:"code"` + Role string `json:"role"` +} + +// UpdateInvitationRole updates an invitation by its code. This is intended to be +// called by a user who has issued an invitation and then decided to change the +// role of the invitee. +func (q *Queries) UpdateInvitationRole(ctx context.Context, arg UpdateInvitationRoleParams) (UserInvite, error) { + row := q.db.QueryRowContext(ctx, updateInvitationRole, arg.Code, arg.Role) var i UserInvite err := row.Scan( &i.Code, diff --git a/internal/db/querier.go b/internal/db/querier.go index 3ec517b539..d55d7394a9 100644 --- a/internal/db/querier.go +++ b/internal/db/querier.go @@ -213,10 +213,10 @@ type Querier interface { SetCurrentVersion(ctx context.Context, arg SetCurrentVersionParams) error UpdateEncryptedSecret(ctx context.Context, arg UpdateEncryptedSecretParams) error UpdateEvaluationTimes(ctx context.Context, arg UpdateEvaluationTimesParams) error - // UpdateInvitation updates an invitation by its code. This is intended to be - // called by a user who has issued an invitation and then decided to bump its - // expiration. - UpdateInvitation(ctx context.Context, code string) (UserInvite, error) + // UpdateInvitationRole updates an invitation by its code. This is intended to be + // called by a user who has issued an invitation and then decided to change the + // role of the invitee. + UpdateInvitationRole(ctx context.Context, arg UpdateInvitationRoleParams) (UserInvite, error) UpdateLease(ctx context.Context, arg UpdateLeaseParams) error UpdateProfile(ctx context.Context, arg UpdateProfileParams) (Profile, error) UpdateProjectMeta(ctx context.Context, arg UpdateProjectMetaParams) (Project, error)