From 3db8affd67262d3a4022ef48f09ca58dbf9ab8d0 Mon Sep 17 00:00:00 2001 From: Marques Johansson Date: Wed, 28 Sep 2022 14:19:00 -0400 Subject: [PATCH] add Invitations service and User creation methods (#346) * add Invitations service and User creation methods Signed-off-by: Marques Johansson --- invitations.go | 155 +++++++++++++++++++++++++++++++++++++++++++++++++ packngo.go | 4 +- user.go | 37 +++++++++++- 3 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 invitations.go diff --git a/invitations.go b/invitations.go new file mode 100644 index 00000000..ef39eea8 --- /dev/null +++ b/invitations.go @@ -0,0 +1,155 @@ +package packngo + +import ( + "path" +) + +const ( + invitationsBasePath = "/invitations" +) + +// InvitationService interface defines available invitation methods +type InvitationService interface { + Create(string, *InvitationCreateRequest, *GetOptions) (*Invitation, *Response, error) + List(string, *ListOptions) ([]Invitation, *Response, error) + Get(string, *GetOptions) (*Invitation, *Response, error) + Accept(string, *InvitationUpdateRequest) (*Invitation, *Response, error) + Resend(string) (*Invitation, *Response, error) + Delete(string) (*Response, error) +} + +type invitationsRoot struct { + Invitations []Invitation `json:"invitations"` + Meta meta `json:"meta"` +} + +// Invitation represents an Equinix Metal invitation +type Invitation struct { + *Href `json:",inline"` + ID string `json:"id,omitempty"` + Invitation Href `json:"invitation,omitempty"` + InvitedBy Href `json:"invited_by,omitempty"` + Invitee string `json:"invitee,omitempty"` + Nonce string `json:"nonce,omitempty"` + Organization Href `json:"organization,omitempty"` + Projects []Href `json:"projects,omitempty"` + Roles []string `json:"roles,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` +} + +// InvitationCreateRequest struct for InvitationService.Create +type InvitationCreateRequest struct { + // Invitee is the email address of the recipient + Invitee string `json:"invitee"` + Message string `json:"message,omitempty"` + ProjectsIDs []string `json:"projects_ids,omitempty"` + Roles []string `json:"roles,omitempty"` +} + +// InvitationUpdateRequest struct for InvitationService.Update +type InvitationUpdateRequest struct{} + +func (u Invitation) String() string { + return Stringify(u) +} + +// InvitationServiceOp implements InvitationService +type InvitationServiceOp struct { + client *Client +} + +var _ InvitationService = (*InvitationServiceOp)(nil) + +// Lists open invitations to the project +func (s *InvitationServiceOp) List(organizationID string, opts *ListOptions) (invitations []Invitation, resp *Response, err error) { + endpointPath := path.Join(organizationBasePath, organizationID, invitationsBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + + for { + subset := new(invitationsRoot) + + resp, err = s.client.DoRequest("GET", apiPathQuery, nil, subset) + if err != nil { + return nil, resp, err + } + + invitations = append(invitations, subset.Invitations...) + + if apiPathQuery = nextPage(subset.Meta, opts); apiPathQuery != "" { + continue + } + return + } +} + +// Create a Invitation with the given InvitationCreateRequest. New invitation VerificationStage +// will be AccountCreated, unless InvitationCreateRequest contains an valid +// InvitationID and Nonce in which case the VerificationStage will be Verified. +func (s *InvitationServiceOp) Create(organizationID string, createRequest *InvitationCreateRequest, opts *GetOptions) (*Invitation, *Response, error) { + endpointPath := path.Join(organizationBasePath, organizationID, invitationsBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + invitation := new(Invitation) + + resp, err := s.client.DoRequest("POST", apiPathQuery, createRequest, invitation) + if err != nil { + return nil, resp, err + } + + return invitation, resp, err +} + +func (s *InvitationServiceOp) Get(invitationID string, opts *GetOptions) (*Invitation, *Response, error) { + if validateErr := ValidateUUID(invitationID); validateErr != nil { + return nil, nil, validateErr + } + endpointPath := path.Join(invitationsBasePath, invitationID) + apiPathQuery := opts.WithQuery(endpointPath) + invitation := new(Invitation) + + resp, err := s.client.DoRequest("GET", apiPathQuery, nil, invitation) + if err != nil { + return nil, resp, err + } + + return invitation, resp, err +} + +// Update updates the current invitation +func (s *InvitationServiceOp) Delete(id string) (*Response, error) { + opts := &GetOptions{} + endpointPath := path.Join(invitationsBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + + return s.client.DoRequest("DELETE", apiPathQuery, nil, nil) +} + +// Update updates the current invitation +func (s *InvitationServiceOp) Accept(id string, updateRequest *InvitationUpdateRequest) (*Invitation, *Response, error) { + opts := &GetOptions{} + endpointPath := path.Join(invitationsBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + invitation := new(Invitation) + + resp, err := s.client.DoRequest("PUT", apiPathQuery, updateRequest, invitation) + if err != nil { + return nil, resp, err + } + + return invitation, resp, err +} + +// Update updates the current invitation +func (s *InvitationServiceOp) Resend(id string) (*Invitation, *Response, error) { + opts := &GetOptions{} + endpointPath := path.Join(invitationsBasePath, id) + apiPathQuery := opts.WithQuery(endpointPath) + invitation := new(Invitation) + + resp, err := s.client.DoRequest("POST", apiPathQuery, nil, invitation) + if err != nil { + return nil, resp, err + } + + return invitation, resp, err +} diff --git a/packngo.go b/packngo.go index 76d6de8a..f3dcf2e4 100644 --- a/packngo.go +++ b/packngo.go @@ -106,6 +106,7 @@ type Client struct { Events EventService Facilities FacilityService HardwareReservations HardwareReservationService + Invitations InvitationService Metros MetroService Notifications NotificationService OperatingSystems OSService @@ -339,7 +340,6 @@ func NewClient() (*Client, error) { } c := NewClientWithAuth("packngo lib", apiToken, nil) return c, nil - } // NewClientWithAuth initializes and returns a Client, use this to get an API Client to operate on @@ -377,6 +377,7 @@ func NewClientWithBaseURL(consumerToken string, apiKey string, httpClient *http. c.Events = &EventServiceOp{client: c} c.Facilities = &FacilityServiceOp{client: c} c.HardwareReservations = &HardwareReservationServiceOp{client: c} + c.Invitations = &InvitationServiceOp{client: c} c.Metros = &MetroServiceOp{client: c} c.Notifications = &NotificationServiceOp{client: c} c.OperatingSystems = &OSServiceOp{client: c} @@ -403,7 +404,6 @@ func NewClientWithBaseURL(consumerToken string, apiKey string, httpClient *http. } func checkResponse(r *http.Response) error { - if s := r.StatusCode; s >= 200 && s <= 299 { // response is good, return return nil diff --git a/user.go b/user.go index 3d8ae832..5b2a4390 100644 --- a/user.go +++ b/user.go @@ -4,14 +4,17 @@ import ( "path" ) -const usersBasePath = "/users" -const userBasePath = "/user" +const ( + usersBasePath = "/users" + userBasePath = "/user" +) // UserService interface defines available user methods type UserService interface { + Create(*UserCreateRequest) (*User, *Response, error) + Current() (*User, *Response, error) List(*ListOptions) ([]User, *Response, error) Get(string, *GetOptions) (*User, *Response, error) - Current() (*User, *Response, error) Update(*UserUpdateRequest) (*User, *Response, error) } @@ -83,6 +86,17 @@ type UserLite struct { AvatarThumbURL string `json:"avatar_thumb_url,omitempty"` } +// UserCreateRequest struct for UserService.Create +type UserCreateRequest struct { + InvitationID string `json:"invitation_id,omitempty"` + Nonce string `json:"nonce,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + Password string `json:"password,omitempty"` + Customdata *interface{} `json:"customdata,omitempty"` + Emails []EmailRequest `json:"emails,omitempty"` +} + // UserUpdateRequest struct for UserService.Update type UserUpdateRequest struct { FirstName *string `json:"first_name,omitempty"` @@ -124,6 +138,23 @@ func (s *UserServiceOp) List(opts *ListOptions) (users []User, resp *Response, e } } +// Create a User with the given UserCreateRequest. New user VerificationStage +// will be AccountCreated, unless UserCreateRequest contains an valid +// InvitationID and Nonce in which case the VerificationStage will be Verified. +func (s *UserServiceOp) Create(createRequest *UserCreateRequest) (*User, *Response, error) { + opts := &GetOptions{} + endpointPath := path.Join(usersBasePath) + apiPathQuery := opts.WithQuery(endpointPath) + user := new(User) + + resp, err := s.client.DoRequest("POST", apiPathQuery, createRequest, user) + if err != nil { + return nil, resp, err + } + + return user, resp, err +} + // Returns the user object for the currently logged-in user. func (s *UserServiceOp) Current() (*User, *Response, error) { user := new(User)