Skip to content

Commit 5f05f17

Browse files
committed
Add implementation for heartbeat and message thread API's
1 parent 0f0403a commit 5f05f17

16 files changed

+324
-83
lines changed

.pre-commit-config.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
repos:
22
- repo: https://github.com/tekwizely/pre-commit-golang
3-
rev: master
3+
rev: v1.0.0-rc.1
44
hooks:
55
- id: go-fumpt
66
- id: go-mod-tidy
77
- id: go-lint
88
- id: go-imports
99
- repo: https://github.com/pre-commit/pre-commit-hooks
10-
rev: v2.3.0
10+
rev: v4.4.0
1111
hooks:
1212
- id: check-yaml
1313
- id: end-of-file-fixer

README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,14 @@ import "github.com/NdoleStudio/httpsms-go"
2828

2929
## Implemented
3030

31-
- [Messages](#messages)
32-
- `POST /v1/messages/send`: Send a new SMS Message
31+
- [x] **[Messages](#messages)**
32+
- [x] `POST /v1/messages/send`: Send a new SMS
33+
- [x] `GET /v1/messages`: Get list of messages which are exchanged between 2 phone numbers.
34+
- [x] **Heartbeats**
35+
- [x] `GET /v1/heartbeats`: Get the heartbeats of an Android Phone
36+
- [x] **Message Threads**
37+
- [x] `GET /v1/message-threads`: Get the message threads of a phone number
38+
3339

3440
## Usage
3541

client.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/json"
77
"fmt"
88
"io"
9-
"io/ioutil"
109
"net/http"
1110
)
1211

@@ -22,7 +21,9 @@ type Client struct {
2221
baseURL string
2322
apiKey string
2423

25-
Messages *messagesService
24+
MessageThreads *MessageThreadService
25+
Heartbeats *HeartbeatService
26+
Messages *MessageService
2627
}
2728

2829
// New creates and returns a new campay.Client from a slice of campay.ClientOption.
@@ -40,7 +41,9 @@ func New(options ...Option) *Client {
4041
}
4142

4243
client.common.client = client
43-
client.Messages = (*messagesService)(&client.common)
44+
client.Messages = (*MessageService)(&client.common)
45+
client.Heartbeats = (*HeartbeatService)(&client.common)
46+
client.MessageThreads = (*MessageThreadService)(&client.common)
4447
return client
4548
}
4649

@@ -89,7 +92,7 @@ func (client *Client) do(req *http.Request) (*Response, error) {
8992
return resp, err
9093
}
9194

92-
_, err = io.Copy(ioutil.Discard, httpResponse.Body)
95+
_, err = io.Copy(io.Discard, httpResponse.Body)
9396
if err != nil {
9497
return resp, err
9598
}
@@ -106,7 +109,7 @@ func (client *Client) newResponse(httpResponse *http.Response) (*Response, error
106109
resp := new(Response)
107110
resp.HTTPResponse = httpResponse
108111

109-
buf, err := ioutil.ReadAll(resp.HTTPResponse.Body)
112+
buf, err := io.ReadAll(resp.HTTPResponse.Body)
110113
if err != nil {
111114
return nil, err
112115
}

contracts.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package httpsms
2+
3+
type ApiResponse[T any] struct {
4+
Data T `json:"data"`
5+
Message string `json:"message"`
6+
Status string `json:"status"`
7+
}

go.mod

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
module github.com/NdoleStudio/httpsms-go
22

3-
go 1.17
3+
go 1.18
44

5-
require github.com/stretchr/testify v1.7.0
5+
require (
6+
github.com/google/uuid v1.3.0
7+
github.com/stretchr/testify v1.7.0
8+
)
69

710
require (
811
github.com/davecgh/go-spew v1.1.0 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
22
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
4+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
35
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
46
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
57
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

heartbeat.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package httpsms
2+
3+
import (
4+
"time"
5+
6+
"github.com/google/uuid"
7+
)
8+
9+
// Heartbeat represents is a pulse from an active phone
10+
type Heartbeat struct {
11+
ID uuid.UUID `json:"id" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
12+
Owner string `json:"owner" gorm:"index:idx_heartbeats_owner_timestamp" example:"+18005550199"`
13+
UserID string `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
14+
Timestamp time.Time `json:"timestamp" example:"2022-06-05T14:26:01.520828+03:00"`
15+
}
16+
17+
// HeartbeatIndexParams is the payload for fetching entities.Heartbeat of a phone number
18+
type HeartbeatIndexParams struct {
19+
Skip int `json:"skip"`
20+
Owner string `json:"owner"`
21+
Query *string `json:"query"`
22+
Limit int `json:"limit"`
23+
}
24+
25+
// HeartbeatsResponse is the response gotten with a message content
26+
type HeartbeatsResponse ApiResponse[[]Heartbeat]

heartbeat_service.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package httpsms
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"strconv"
8+
)
9+
10+
// HeartbeatService is the API client for the `/heartbeats` endpoint
11+
type HeartbeatService service
12+
13+
// Index returns a list of heartbeats from an android phone. It will be sorted by timestamp in descending order.
14+
//
15+
// API Docs: https://api.httpsms.com/index.html#/Heartbeats/get_heartbeats
16+
func (service *HeartbeatService) Index(ctx context.Context, params *HeartbeatIndexParams) (*HeartbeatsResponse, *Response, error) {
17+
request, err := service.client.newRequest(ctx, http.MethodGet, "/v1/heartbeats", nil)
18+
if err != nil {
19+
return nil, nil, err
20+
}
21+
22+
q := request.URL.Query()
23+
q.Add("skip", strconv.Itoa(params.Skip))
24+
q.Add("owner", params.Owner)
25+
q.Add("limit", strconv.Itoa(params.Limit))
26+
27+
if params.Query != nil {
28+
q.Add("query", *params.Query)
29+
}
30+
31+
request.URL.RawQuery = q.Encode()
32+
33+
response, err := service.client.do(request)
34+
if err != nil {
35+
return nil, response, err
36+
}
37+
38+
heartbeats := new(HeartbeatsResponse)
39+
if err = json.Unmarshal(*response.Body, heartbeats); err != nil {
40+
return nil, response, err
41+
}
42+
43+
return heartbeats, response, nil
44+
}

internal/stubs/message.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,35 @@ func MessagesSendResponse() []byte {
55
return []byte(`
66
{
77
"data": {
8+
"can_be_polled": false,
89
"contact": "+18005550100",
910
"content": "This is a sample text message",
1011
"created_at": "2022-06-05T14:26:02.302718+03:00",
11-
"failure_reason": "",
12+
"delivered_at": "2022-06-05T14:26:09.527976+03:00",
13+
"expired_at": "2022-06-05T14:26:09.527976+03:00",
14+
"failed_at": "2022-06-05T14:26:09.527976+03:00",
15+
"failure_reason": null,
1216
"id": "32343a19-da5e-4b1b-a767-3298a73703cb",
1317
"last_attempted_at": "2022-06-05T14:26:09.527976+03:00",
18+
"max_send_attempts": 1,
1419
"order_timestamp": "2022-06-05T14:26:09.527976+03:00",
1520
"owner": "+18005550199",
1621
"received_at": "2022-06-05T14:26:09.527976+03:00",
22+
"request_id": "153554b5-ae44-44a0-8f4f-7bbac5657ad4",
1723
"request_received_at": "2022-06-05T14:26:01.520828+03:00",
24+
"scheduled_at": "2022-06-05T14:26:09.527976+03:00",
25+
"send_attempt_count": 0,
1826
"send_time": 133414,
1927
"sent_at": "2022-06-05T14:26:09.527976+03:00",
28+
"sim": "SIM1",
2029
"status": "pending",
2130
"type": "mobile-terminated",
2231
"updated_at": "2022-06-05T14:26:10.303278+03:00",
2332
"user_id": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC"
2433
},
25-
"message": "message created successfully",
34+
"message": "item created successfully",
2635
"status": "success"
27-
}
36+
}
2837
`)
2938
}
3039

message.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package httpsms
2+
3+
import (
4+
"time"
5+
6+
"github.com/google/uuid"
7+
)
8+
9+
// MessageSendParams is the request payload for sending a message
10+
type MessageSendParams struct {
11+
Content string `json:"content"`
12+
From string `json:"from"`
13+
RequestID string `json:"request_id,omitempty"`
14+
To string `json:"to"`
15+
}
16+
17+
// MessageIndexParams is the payload fetching entities.Message sent between 2 numbers
18+
type MessageIndexParams struct {
19+
Skip int `json:"skip"`
20+
Contact string `json:"contact"`
21+
Owner string `json:"owner"`
22+
Query *string `json:"query"`
23+
Limit int `json:"limit"`
24+
}
25+
26+
// MessageResponse is the response gotten with a message content
27+
type MessageResponse ApiResponse[Message]
28+
29+
// MessagesResponse is the response with multiple messages
30+
type MessagesResponse ApiResponse[[]Message]
31+
32+
// Message represents and incoming or outgoing SMS message
33+
type Message struct {
34+
ID uuid.UUID `json:"id" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
35+
RequestID *string `json:"request_id" example:"153554b5-ae44-44a0-8f4f-7bbac5657ad4"`
36+
Owner string `json:"owner" example:"+18005550199"`
37+
UserID string `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
38+
Contact string `json:"contact" example:"+18005550100"`
39+
Content string `json:"content" example:"This is a sample text message"`
40+
Type string `json:"type" example:"mobile-terminated"`
41+
Status string `json:"status" example:"pending"`
42+
// SIM is the SIM card to use to send the message
43+
// * SMS1: use the SIM card in slot 1
44+
// * SMS2: use the SIM card in slot 2
45+
SIM string `json:"sim" example:"SIM1"`
46+
47+
// SendDuration is the number of nanoseconds from when the request was received until when the mobile phone send the message
48+
SendDuration *int64 `json:"send_time" example:"133414"`
49+
50+
RequestReceivedAt time.Time `json:"request_received_at" example:"2022-06-05T14:26:01.520828+03:00"`
51+
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"`
52+
UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"`
53+
OrderTimestamp time.Time `json:"order_timestamp" gorm:"index:idx_messages_order_timestamp" example:"2022-06-05T14:26:09.527976+03:00"`
54+
LastAttemptedAt *time.Time `json:"last_attempted_at" example:"2022-06-05T14:26:09.527976+03:00"`
55+
NotificationScheduledAt *time.Time `json:"scheduled_at" example:"2022-06-05T14:26:09.527976+03:00"`
56+
SentAt *time.Time `json:"sent_at" example:"2022-06-05T14:26:09.527976+03:00"`
57+
DeliveredAt *time.Time `json:"delivered_at" example:"2022-06-05T14:26:09.527976+03:00"`
58+
ExpiredAt *time.Time `json:"expired_at" example:"2022-06-05T14:26:09.527976+03:00"`
59+
FailedAt *time.Time `json:"failed_at" example:"2022-06-05T14:26:09.527976+03:00"`
60+
CanBePolled bool `json:"can_be_polled" example:"false"`
61+
SendAttemptCount uint `json:"send_attempt_count" example:"0"`
62+
MaxSendAttempts uint `json:"max_send_attempts" example:"1"`
63+
ReceivedAt *time.Time `json:"received_at" example:"2022-06-05T14:26:09.527976+03:00"`
64+
FailureReason *string `json:"failure_reason" example:"UNKNOWN"`
65+
}

message_service.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package httpsms
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"strconv"
8+
)
9+
10+
// MessageService is the API client for the `/` endpoint
11+
type MessageService service
12+
13+
// Send adds a new SMS message to be sent by the android phone
14+
//
15+
// API Docs: https://api.httpsms.com/index.html#/Messages/post_messages_send
16+
func (service *MessageService) Send(ctx context.Context, params *MessageSendParams) (*MessageResponse, *Response, error) {
17+
request, err := service.client.newRequest(ctx, http.MethodPost, "/v1/messages/send", params)
18+
if err != nil {
19+
return nil, nil, err
20+
}
21+
22+
response, err := service.client.do(request)
23+
if err != nil {
24+
return nil, response, err
25+
}
26+
27+
message := new(MessageResponse)
28+
if err = json.Unmarshal(*response.Body, message); err != nil {
29+
return nil, response, err
30+
}
31+
32+
return message, response, nil
33+
}
34+
35+
// Index returns a list of messages which are sent between 2 phone numbers. It will be sorted by timestamp in descending order.
36+
//
37+
// API Docs: https://api.httpsms.com/index.html#/Messages/get_messages
38+
func (service *MessageService) Index(ctx context.Context, params *MessageIndexParams) (*MessagesResponse, *Response, error) {
39+
request, err := service.client.newRequest(ctx, http.MethodGet, "/v1/messages", nil)
40+
if err != nil {
41+
return nil, nil, err
42+
}
43+
44+
q := request.URL.Query()
45+
q.Add("skip", strconv.Itoa(params.Skip))
46+
q.Add("owner", params.Owner)
47+
q.Add("contact", params.Contact)
48+
q.Add("limit", strconv.Itoa(params.Limit))
49+
50+
if params.Query != nil {
51+
q.Add("query", *params.Query)
52+
}
53+
54+
request.URL.RawQuery = q.Encode()
55+
56+
response, err := service.client.do(request)
57+
if err != nil {
58+
return nil, response, err
59+
}
60+
61+
messages := new(MessagesResponse)
62+
if err = json.Unmarshal(*response.Body, messages); err != nil {
63+
return nil, response, err
64+
}
65+
66+
return messages, response, nil
67+
}
File renamed without changes.

message_thread.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package httpsms
2+
3+
import (
4+
"time"
5+
6+
"github.com/google/uuid"
7+
)
8+
9+
// MessageThreadIndexParams is the payload fetching entities.MessageThread sent between 2 numbers
10+
type MessageThreadIndexParams struct {
11+
IsArchived bool `json:"is_archived"`
12+
Skip int `json:"skip"`
13+
Query *string `json:"query"`
14+
Limit int `json:"limit"`
15+
Owner string `json:"owner"`
16+
}
17+
18+
// MessageThread represents a message thread between 2 phone numbers
19+
type MessageThread struct {
20+
ID uuid.UUID `json:"id" example:"32343a19-da5e-4b1b-a767-3298a73703ca"`
21+
Owner string `json:"owner" example:"+18005550199"`
22+
Contact string `json:"contact" example:"+18005550100"`
23+
IsArchived bool `json:"is_archived" example:"false"`
24+
UserID string `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
25+
Color string `json:"color" example:"indigo"`
26+
LastMessageContent string `json:"last_message_content" example:"This is a sample message content"`
27+
LastMessageID uuid.UUID `json:"last_message_id" example:"32343a19-da5e-4b1b-a767-3298a73703ca"`
28+
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:09.527976+03:00"`
29+
UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:09.527976+03:00"`
30+
OrderTimestamp time.Time `json:"order_timestamp" example:"2022-06-05T14:26:09.527976+03:00"`
31+
}
32+
33+
// MessageThreadsResponse is the response gotten with a message content
34+
type MessageThreadsResponse ApiResponse[[]MessageThread]

0 commit comments

Comments
 (0)