diff --git a/mongodbatlas/organization_invitations.go b/mongodbatlas/organization_invitations.go index c9de4b2e7..1c25519f9 100644 --- a/mongodbatlas/organization_invitations.go +++ b/mongodbatlas/organization_invitations.go @@ -32,6 +32,8 @@ type Invitation struct { ID string `json:"id,omitempty"` OrgID string `json:"orgId,omitempty"` OrgName string `json:"orgName,omitempty"` + GroupID string `json:"groupId,omitempty"` + GroupName string `json:"groupName,omitempty"` CreatedAt string `json:"createdAt,omitempty"` ExpiresAt string `json:"expiresAt,omitempty"` InviterUsername string `json:"inviterUsername,omitempty"` diff --git a/mongodbatlas/project_invitations.go b/mongodbatlas/project_invitations.go new file mode 100644 index 000000000..389a67225 --- /dev/null +++ b/mongodbatlas/project_invitations.go @@ -0,0 +1,176 @@ +// Copyright 2021 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongodbatlas + +import ( + "context" + "fmt" + "net/http" +) + +const projectInvitationBasePath = projectBasePath + "/%s/invites" + +// Invitations gets all unaccepted invitations to the specified Atlas project. +// +// See more: https://docs.atlas.mongodb.com/reference/api/project-get-invitations/ +func (s *ProjectsServiceOp) Invitations(ctx context.Context, groupID string, opts *InvitationOptions) ([]*Invitation, *Response, error) { + if groupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + + basePath := fmt.Sprintf(projectInvitationBasePath, groupID) + path, err := setListOptions(basePath, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.Client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + var root []*Invitation + resp, err := s.Client.Do(ctx, req, &root) + if err != nil { + return nil, resp, err + } + + return root, resp, nil +} + +// Invitation gets details for one unaccepted invitation to the specified Atlas project. +// +// See more: https://docs.atlas.mongodb.com/reference/api/project-get-one-invitation/ +func (s *ProjectsServiceOp) Invitation(ctx context.Context, groupID, invitationID string) (*Invitation, *Response, error) { + if groupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + + if invitationID == "" { + return nil, nil, NewArgError("invitationID", "must be set") + } + + basePath := fmt.Sprintf(projectInvitationBasePath, groupID) + path := fmt.Sprintf("%s/%s", basePath, invitationID) + + req, err := s.Client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(Invitation) + resp, err := s.Client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, nil +} + +// InviteUser invites one user to the Atlas project that you specify. +func (s *ProjectsServiceOp) InviteUser(ctx context.Context, invitation *Invitation) (*Invitation, *Response, error) { + if invitation.GroupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + + path := fmt.Sprintf(projectInvitationBasePath, invitation.GroupID) + + req, err := s.Client.NewRequest(ctx, http.MethodPost, path, invitation) + if err != nil { + return nil, nil, err + } + + root := new(Invitation) + resp, err := s.Client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, nil +} + +// UpdateInvitation updates one pending invitation to the Atlas project that you specify. +// +// See more: https://docs.atlas.mongodb.com/reference/api/project-update-one-invitation/ +func (s *ProjectsServiceOp) UpdateInvitation(ctx context.Context, invitation *Invitation) (*Invitation, *Response, error) { + if invitation.GroupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + + return s.updateInvitation(ctx, invitation) +} + +// UpdateInvitationByID updates one invitation to the Atlas project. +// +// See more: https://docs.atlas.mongodb.com/reference/api/project-update-one-invitation-by-id/ +func (s *ProjectsServiceOp) UpdateInvitationByID(ctx context.Context, invitationID string, invitation *Invitation) (*Invitation, *Response, error) { + if invitation.GroupID == "" { + return nil, nil, NewArgError("groupID", "must be set") + } + + if invitationID == "" { + return nil, nil, NewArgError("invitationID", "must be set") + } + + invitation.ID = invitationID + + return s.updateInvitation(ctx, invitation) +} + +// DeleteInvitation deletes one unaccepted invitation to the specified Atlas project. You can't delete an invitation that a user has accepted. +// +// See more: https://docs.atlas.mongodb.com/reference/api/project-delete-invitation/ +func (s *ProjectsServiceOp) DeleteInvitation(ctx context.Context, groupID, invitationID string) (*Response, error) { + if groupID == "" { + return nil, NewArgError("groupID", "must be set") + } + + if invitationID == "" { + return nil, NewArgError("invitationID", "must be set") + } + + basePath := fmt.Sprintf(projectInvitationBasePath, groupID) + path := fmt.Sprintf("%s/%s", basePath, invitationID) + + req, err := s.Client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + resp, err := s.Client.Do(ctx, req, nil) + + return resp, err +} + +func (s *ProjectsServiceOp) updateInvitation(ctx context.Context, invitation *Invitation) (*Invitation, *Response, error) { + path := fmt.Sprintf(projectInvitationBasePath, invitation.GroupID) + + if invitation.ID != "" { + path = fmt.Sprintf("%s/%s", path, invitation.ID) + } + + req, err := s.Client.NewRequest(ctx, http.MethodPatch, path, invitation) + if err != nil { + return nil, nil, err + } + + root := new(Invitation) + resp, err := s.Client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, nil +} diff --git a/mongodbatlas/project_invitations_test.go b/mongodbatlas/project_invitations_test.go new file mode 100644 index 000000000..b1aa21832 --- /dev/null +++ b/mongodbatlas/project_invitations_test.go @@ -0,0 +1,297 @@ +// Copyright 2021 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongodbatlas + +import ( + "fmt" + "net/http" + "testing" + + "github.com/go-test/deep" +) + +func TestProjects_Invitations(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc(fmt.Sprintf("/api/atlas/v1.0/groups/%s/invites", groupID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + _, _ = fmt.Fprint(w, `[ + { + "createdAt": "2021-02-18T21:05:40Z", + "expiresAt": "2021-03-20T21:05:40Z", + "id": "5a0a1e7e0f2912c554080adc", + "inviterUsername": "admin@example.com", + "groupId": "1", + "groupName": "jww-12-16", + "roles": [ + "ORG_OWNER" + ], + "username": "wyatt.smith@example.com"}, + {"createdAt": "2021-02-18T21:05:40Z", + "expiresAt": "2021-03-20T21:05:40Z", + "id": "5a0a1e7e0f2912c554080adc", + "inviterUsername": "admin@example.com", + "groupId": "1", + "groupName": "jww-12-16", + "roles": [ + "ORG_OWNER" + ], + "teamIds": ["2"], + "username": "wyatt.smith@example.com"}]`) + }) + + invitation, _, err := client.Projects.Invitations(ctx, groupID, nil) + if err != nil { + t.Fatalf("Projects.Invitations returned error: %v", err) + } + + expected := []*Invitation{ + { + ID: "5a0a1e7e0f2912c554080adc", + GroupID: groupID, + GroupName: "jww-12-16", + CreatedAt: "2021-02-18T21:05:40Z", + ExpiresAt: "2021-03-20T21:05:40Z", + InviterUsername: "admin@example.com", + Username: "wyatt.smith@example.com", + Roles: []string{"ORG_OWNER"}, + }, + { + ID: "5a0a1e7e0f2912c554080adc", + GroupID: groupID, + GroupName: "jww-12-16", + CreatedAt: "2021-02-18T21:05:40Z", + ExpiresAt: "2021-03-20T21:05:40Z", + InviterUsername: "admin@example.com", + Username: "wyatt.smith@example.com", + Roles: []string{"ORG_OWNER"}, + TeamIDs: []string{"2"}, + }, + } + + if diff := deep.Equal(invitation, expected); diff != nil { + t.Error(diff) + } +} + +func TestProjects_Invitation(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc(fmt.Sprintf("/api/atlas/v1.0/groups/%s/invites/%s", groupID, invitationID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + _, _ = fmt.Fprint(w, `{ + "createdAt": "2021-02-18T21:05:40Z", + "expiresAt": "2021-03-20T21:05:40Z", + "id": "5a0a1e7e0f2912c554080adc", + "inviterUsername": "admin@example.com", + "groupId": "1", + "groupName": "jww-12-16", + "roles": [ + "ORG_OWNER" + ], + "username": "wyatt.smith@example.com" + }`) + }) + + invitation, _, err := client.Projects.Invitation(ctx, groupID, invitationID) + if err != nil { + t.Fatalf("Projects.Invitation returned error: %v", err) + } + + expected := &Invitation{ + ID: "5a0a1e7e0f2912c554080adc", + GroupID: groupID, + GroupName: "jww-12-16", + CreatedAt: "2021-02-18T21:05:40Z", + ExpiresAt: "2021-03-20T21:05:40Z", + InviterUsername: "admin@example.com", + Username: "wyatt.smith@example.com", + Roles: []string{"ORG_OWNER"}, + } + + if diff := deep.Equal(invitation, expected); diff != nil { + t.Error(diff) + } +} + +func TestProjects_InviteUser(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc(fmt.Sprintf("/api/atlas/v1.0/groups/%s/invites", groupID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + _, _ = fmt.Fprint(w, `{ + "createdAt": "2021-02-18T21:05:40Z", + "expiresAt": "2021-03-20T21:05:40Z", + "id": "5a0a1e7e0f2912c554080adc", + "inviterUsername": "admin@example.com", + "groupId": "1", + "groupName": "jww-12-16", + "roles": [ + "ORG_OWNER" + ], + "username": "wyatt.smith@example.com" + }`) + }) + + body := &Invitation{ + GroupID: groupID, + GroupName: "jww-12-16", + CreatedAt: "2021-02-18T21:05:40Z", + ExpiresAt: "2021-03-20T21:05:40Z", + InviterUsername: "admin@example.com", + Username: "wyatt.smith@example.com", + Roles: []string{"ORG_OWNER"}, + } + + invitation, _, err := client.Projects.InviteUser(ctx, body) + if err != nil { + t.Fatalf("Projects.InviteUser returned error: %v", err) + } + + expected := &Invitation{ + ID: "5a0a1e7e0f2912c554080adc", + GroupID: groupID, + GroupName: "jww-12-16", + CreatedAt: "2021-02-18T21:05:40Z", + ExpiresAt: "2021-03-20T21:05:40Z", + InviterUsername: "admin@example.com", + Username: "wyatt.smith@example.com", + Roles: []string{"ORG_OWNER"}, + } + + if diff := deep.Equal(invitation, expected); diff != nil { + t.Error(diff) + } +} + +func TestProjects_UpdateInvitation(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc(fmt.Sprintf("/api/atlas/v1.0/groups/%s/invites", groupID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPatch) + _, _ = fmt.Fprint(w, `{ + "createdAt": "2021-02-18T21:05:40Z", + "expiresAt": "2021-03-20T21:05:40Z", + "id": "5a0a1e7e0f2912c554080adc", + "inviterUsername": "admin@example.com", + "groupId": "1", + "groupName": "jww-12-16", + "roles": [ + "ORG_OWNER" + ], + "username": "wyatt.smith@example.com" + }`) + }) + + body := &Invitation{ + GroupID: groupID, + GroupName: "jww-12-16", + CreatedAt: "2021-02-18T21:05:40Z", + ExpiresAt: "2021-03-20T21:05:40Z", + InviterUsername: "admin@example.com", + Username: "wyatt.smith@example.com", + Roles: []string{"ORG_OWNER"}, + } + + invitation, _, err := client.Projects.UpdateInvitation(ctx, body) + if err != nil { + t.Fatalf("Projects.UpdateInvitation returned error: %v", err) + } + + expected := &Invitation{ + ID: "5a0a1e7e0f2912c554080adc", + GroupID: groupID, + GroupName: "jww-12-16", + CreatedAt: "2021-02-18T21:05:40Z", + ExpiresAt: "2021-03-20T21:05:40Z", + InviterUsername: "admin@example.com", + Username: "wyatt.smith@example.com", + Roles: []string{"ORG_OWNER"}, + } + + if diff := deep.Equal(invitation, expected); diff != nil { + t.Error(diff) + } +} + +func TestProjects_UpdateInvitationByID(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc(fmt.Sprintf("/api/atlas/v1.0/groups/%s/invites/%s", groupID, invitationID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPatch) + _, _ = fmt.Fprint(w, `{ + "createdAt": "2021-02-18T21:05:40Z", + "expiresAt": "2021-03-20T21:05:40Z", + "id": "5a0a1e7e0f2912c554080adc", + "inviterUsername": "admin@example.com", + "groupId": "1", + "groupName": "jww-12-16", + "roles": [ + "ORG_OWNER" + ], + "username": "wyatt.smith@example.com" + }`) + }) + + body := &Invitation{ + ID: invitationID, + GroupID: groupID, + GroupName: "jww-12-16", + CreatedAt: "2021-02-18T21:05:40Z", + ExpiresAt: "2021-03-20T21:05:40Z", + InviterUsername: "admin@example.com", + Username: "wyatt.smith@example.com", + Roles: []string{"ORG_OWNER"}, + } + + invitation, _, err := client.Projects.UpdateInvitationByID(ctx, invitationID, body) + if err != nil { + t.Fatalf("Projects.UpdateInvitationByID returned error: %v", err) + } + + expected := &Invitation{ + ID: "5a0a1e7e0f2912c554080adc", + GroupID: groupID, + GroupName: "jww-12-16", + CreatedAt: "2021-02-18T21:05:40Z", + ExpiresAt: "2021-03-20T21:05:40Z", + InviterUsername: "admin@example.com", + Username: "wyatt.smith@example.com", + Roles: []string{"ORG_OWNER"}, + } + + if diff := deep.Equal(invitation, expected); diff != nil { + t.Error(diff) + } +} + +func TestProjects_DeleteInvitation(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc(fmt.Sprintf("/api/atlas/v1.0/groups/%s/invites/%s", groupID, invitationID), func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodDelete) + }) + + _, err := client.Projects.DeleteInvitation(ctx, groupID, invitationID) + if err != nil { + t.Fatalf("Projects.DeleteInvitation returned error: %v", err) + } +} diff --git a/mongodbatlas/projects.go b/mongodbatlas/projects.go index 584811243..bbd9e9481 100644 --- a/mongodbatlas/projects.go +++ b/mongodbatlas/projects.go @@ -47,6 +47,12 @@ type ProjectsService interface { GetProjectTeamsAssigned(context.Context, string) (*TeamsAssigned, *Response, error) AddTeamsToProject(context.Context, string, []*ProjectTeam) (*TeamsAssigned, *Response, error) RemoveUserFromProject(context.Context, string, string) (*Response, error) + Invitations(context.Context, string, *InvitationOptions) ([]*Invitation, *Response, error) + Invitation(context.Context, string, string) (*Invitation, *Response, error) + InviteUser(context.Context, *Invitation) (*Invitation, *Response, error) + UpdateInvitation(context.Context, *Invitation) (*Invitation, *Response, error) + UpdateInvitationByID(context.Context, string, *Invitation) (*Invitation, *Response, error) + DeleteInvitation(context.Context, string, string) (*Response, error) } // ProjectsServiceOp handles communication with the Projects related methods of the