Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type Client struct {
Heartbeats *HeartbeatService
Messages *MessageService
Cipher *CipherService
Phones *PhoneService
PhoneAPIKeys *PhoneAPIKeyService
Webhooks *WebhookService
Comment on lines +28 to +30
}

// New creates and returns a new campay.Client from a slice of campay.ClientOption.
Expand All @@ -46,6 +49,9 @@ func New(options ...Option) *Client {
client.Heartbeats = (*HeartbeatService)(&client.common)
client.MessageThreads = (*MessageThreadService)(&client.common)
client.Cipher = (*CipherService)(&client.common)
client.Phones = (*PhoneService)(&client.common)
client.PhoneAPIKeys = (*PhoneAPIKeyService)(&client.common)
client.Webhooks = (*WebhookService)(&client.common)

return client
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/NdoleStudio/httpsms-go
go 1.18

require (
github.com/google/uuid v1.3.0
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.7.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
45 changes: 45 additions & 0 deletions internal/stubs/phone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package stubs

// PhoneUpsertResponse response from the PUT /v1/phones endpoint
func PhoneUpsertResponse() []byte {
return []byte(`
{
"data": {
"id": "9d484671-cac2-41de-9171-d9d2c1835d7b",
"user_id": "hT5V2CmN5bbG81glMLmosxPV9Np2",
"phone_number": "+18005550199",
"fcm_token": "test-fcm-token",
"messages_per_minute": 5,
"max_send_attempts": 3,
"message_expiration_seconds": 3600,
"sim": "SIM1",
"created_at": "2024-01-21T13:07:56.203538Z",
"updated_at": "2024-01-21T13:07:56.203538Z"
},
"message": "phone upserted successfully",
"status": "success"
}
`)
}

// PhoneUpsertFCMTokenResponse response from the PUT /v1/phones/fcm-token endpoint
func PhoneUpsertFCMTokenResponse() []byte {
return []byte(`
{
"data": {
"id": "9d484671-cac2-41de-9171-d9d2c1835d7b",
"user_id": "hT5V2CmN5bbG81glMLmosxPV9Np2",
"phone_number": "+18005550199",
"fcm_token": "new-fcm-token",
"messages_per_minute": 5,
"max_send_attempts": 3,
"message_expiration_seconds": 3600,
"sim": "SIM1",
"created_at": "2024-01-21T13:07:56.203538Z",
"updated_at": "2024-01-21T13:07:56.203538Z"
},
"message": "phone FCM token updated successfully",
"status": "success"
}
`)
}
22 changes: 22 additions & 0 deletions internal/stubs/phone_api_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package stubs

// PhoneAPIKeyStoreResponse response from the POST /v1/phone-api-keys/ endpoint
func PhoneAPIKeyStoreResponse() []byte {
return []byte(`
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "test-key",
"user_id": "hT5V2CmN5bbG81glMLmosxPV9Np2",
"user_email": "test@example.com",
"phone_numbers": ["+18005550199"],
"phone_ids": ["9d484671-cac2-41de-9171-d9d2c1835d7b"],
"api_key": "pk_test_1234567890abcdef",
"created_at": "2024-01-21T13:07:56.203538Z",
"updated_at": "2024-01-21T13:07:56.203538Z"
},
"message": "phone API key created successfully",
"status": "success"
}
`)
}
21 changes: 21 additions & 0 deletions internal/stubs/webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package stubs

// WebhookStoreResponse response from the POST /v1/webhooks endpoint
func WebhookStoreResponse() []byte {
return []byte(`
{
"data": {
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"user_id": "hT5V2CmN5bbG81glMLmosxPV9Np2",
"url": "https://example.com/webhook",
"signing_key": "whsec_test_signing_key",
"phone_numbers": ["+18005550199"],
"events": ["message.received", "message.sent"],
"created_at": "2024-01-21T13:07:56.203538Z",
"updated_at": "2024-01-21T13:07:56.203538Z"
},
"message": "webhook created successfully",
"status": "success"
}
`)
}
20 changes: 20 additions & 0 deletions phone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package httpsms

import "time"

// Phone represents a phone registered in the httpSMS API
type Phone struct {
ID string `json:"id"`
UserID string `json:"user_id"`
PhoneNumber string `json:"phone_number"`
FcmToken *string `json:"fcm_token"`
MessagesPerMinute uint `json:"messages_per_minute"`
MaxSendAttempts uint `json:"max_send_attempts"`
MessageExpirationSeconds uint `json:"message_expiration_seconds"`
SIM string `json:"sim"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// PhoneResponse is the response gotten with a phone
type PhoneResponse ApiResponse[Phone]
19 changes: 19 additions & 0 deletions phone_api_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package httpsms

import "time"

// PhoneAPIKey represents a phone API key
type PhoneAPIKey struct {
ID string `json:"id"`
Name string `json:"name"`
UserID string `json:"user_id"`
UserEmail string `json:"user_email"`
PhoneNumbers []string `json:"phone_numbers"`
PhoneIDs []string `json:"phone_ids"`
APIKey string `json:"api_key"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// PhoneAPIKeyResponse is the response gotten with a phone api key
type PhoneAPIKeyResponse ApiResponse[PhoneAPIKey]
35 changes: 35 additions & 0 deletions phone_api_key_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package httpsms

import (
"context"
"encoding/json"
"net/http"
)

// PhoneAPIKeyService is the API client for the phone api key endpoints
type PhoneAPIKeyService service

// PhoneAPIKeyStoreParams is the request payload for creating a phone api key
type PhoneAPIKeyStoreParams struct {
Name string `json:"name"`
}

// Store adds a new phone api key
func (service *PhoneAPIKeyService) Store(ctx context.Context, params *PhoneAPIKeyStoreParams) (*PhoneAPIKeyResponse, *Response, error) {
request, err := service.client.newRequest(ctx, http.MethodPost, "/v1/phone-api-keys/", params)
if err != nil {
return nil, nil, err
}

response, err := service.client.do(request)
if err != nil {
return nil, response, err
}

phoneAPIKey := new(PhoneAPIKeyResponse)
if err = json.Unmarshal(*response.Body, phoneAPIKey); err != nil {
return nil, response, err
}

return phoneAPIKey, response, nil
}
86 changes: 86 additions & 0 deletions phone_api_key_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package httpsms

import (
"context"
"encoding/json"
"net/http"
"testing"

"github.com/NdoleStudio/httpsms-go/internal/helpers"
"github.com/NdoleStudio/httpsms-go/internal/stubs"
"github.com/stretchr/testify/assert"
)

func TestPhoneAPIKeyService_Store(t *testing.T) {
// Setup
t.Parallel()

// Arrange
apiKey := "test-api-key"
server := helpers.MakeTestServer(http.StatusOK, stubs.PhoneAPIKeyStoreResponse())
client := New(WithBaseURL(server.URL), WithAPIKey(apiKey))

// Act
phoneAPIKey, response, err := client.PhoneAPIKeys.Store(context.Background(), &PhoneAPIKeyStoreParams{
Name: "test-key",
})

// Assert
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode)

jsonContent, _ := json.Marshal(phoneAPIKey)
assert.JSONEq(t, string(stubs.PhoneAPIKeyStoreResponse()), string(jsonContent))

// Teardown
server.Close()
}

func TestPhoneAPIKeyService_StoreWithError(t *testing.T) {
// Setup
t.Parallel()

// Arrange
apiKey := "test-api-key"
server := helpers.MakeTestServer(http.StatusInternalServerError, stubs.HttpInternalServerErrorResponse())
client := New(WithBaseURL(server.URL), WithAPIKey(apiKey))

// Act
_, response, err := client.PhoneAPIKeys.Store(context.Background(), &PhoneAPIKeyStoreParams{
Name: "test-key",
})

// Assert
assert.NotNil(t, err)
assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode)
assert.Equal(t, string(stubs.HttpInternalServerErrorResponse()), string(*response.Body))

// Teardown
server.Close()
}

func TestPhoneAPIKeyService_StoreRequest(t *testing.T) {
// Setup
t.Parallel()

// Arrange
apiKey := "test-api-key"
var capturedRequest http.Request
server := helpers.MakeRequestCapturingTestServer(http.StatusOK, stubs.PhoneAPIKeyStoreResponse(), &capturedRequest)
client := New(WithBaseURL(server.URL), WithAPIKey(apiKey))

// Act
_, _, err := client.PhoneAPIKeys.Store(context.Background(), &PhoneAPIKeyStoreParams{
Name: "test-key",
})

// Assert
assert.Nil(t, err)
assert.Equal(t, http.MethodPost, capturedRequest.Method)
assert.Equal(t, "/v1/phone-api-keys/", capturedRequest.URL.Path)
assert.Equal(t, "application/json", capturedRequest.Header.Get("Content-Type"))
assert.Equal(t, apiKey, capturedRequest.Header.Get("x-api-key"))

// Teardown
server.Close()
}
67 changes: 67 additions & 0 deletions phone_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package httpsms

import (
"context"
"encoding/json"
"net/http"
)

// PhoneService is the API client for the phone endpoints
type PhoneService service

// PhoneUpsertParams is the request payload for creating/updating a phone
type PhoneUpsertParams struct {
PhoneNumber string `json:"phone_number"`
FcmToken string `json:"fcm_token,omitempty"`
MessagesPerMinute uint `json:"messages_per_minute,omitempty"`
MaxSendAttempts uint `json:"max_send_attempts,omitempty"`
MessageExpirationSeconds uint `json:"message_expiration_seconds,omitempty"`
SIM string `json:"sim,omitempty"`
Comment on lines +14 to +19
}

// PhoneFCMTokenParams is the request for binding FCM token to a phone via phone API key
type PhoneFCMTokenParams struct {
PhoneNumber string `json:"phone_number"`
FcmToken string `json:"fcm_token"`
SIM string `json:"sim,omitempty"`
}

// Upsert creates or updates a phone
func (service *PhoneService) Upsert(ctx context.Context, params *PhoneUpsertParams) (*PhoneResponse, *Response, error) {
request, err := service.client.newRequest(ctx, http.MethodPut, "/v1/phones", params)
if err != nil {
return nil, nil, err
}

response, err := service.client.do(request)
if err != nil {
return nil, response, err
}

phone := new(PhoneResponse)
if err = json.Unmarshal(*response.Body, phone); err != nil {
return nil, response, err
}

return phone, response, nil
}

// UpsertFCMToken binds an FCM token to a phone via the phone API key
func (service *PhoneService) UpsertFCMToken(ctx context.Context, params *PhoneFCMTokenParams) (*PhoneResponse, *Response, error) {
request, err := service.client.newRequest(ctx, http.MethodPut, "/v1/phones/fcm-token", params)
if err != nil {
return nil, nil, err
}

response, err := service.client.do(request)
if err != nil {
return nil, response, err
}

phone := new(PhoneResponse)
if err = json.Unmarshal(*response.Body, phone); err != nil {
return nil, response, err
}

return phone, response, nil
}
Loading