Skip to content
Open
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
2 changes: 1 addition & 1 deletion smsgateway/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func (c *Client) ExportInbox(ctx context.Context, req MessagesExportRequest) err
return nil
}

// ListInboxMessages retrieves incoming messages with filtering and pagination.
// ListInboxMessages retrieves inbox messages with filtering and pagination.
// Returns the messages, total count (from X-Total-Count header), and error.
func (c *Client) ListInboxMessages(ctx context.Context, opts ListInboxOptions) ([]IncomingMessage, int, error) {
path := "/inbox?" + opts.ToURLValues().Encode()
Expand Down
30 changes: 19 additions & 11 deletions smsgateway/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1140,11 +1140,15 @@ func TestClient_ListInboxMessages(t *testing.T) {

client := newClient(server.URL)
msgs, total, err := client.ListInboxMessages(context.Background(), smsgateway.ListInboxOptions{
Type: &msgType,
Limit: &limit,
Offset: &offset,
From: &from,
To: &to,
Type: &msgType,
PaginationOptions: smsgateway.PaginationOptions{
Limit: &limit,
Offset: &offset,
},
DatePeriodOptions: smsgateway.DatePeriodOptions{
From: &from,
To: &to,
},
DeviceID: &deviceID,
})
if err != nil {
Expand Down Expand Up @@ -1311,12 +1315,16 @@ func TestClient_ListMessages(t *testing.T) {

client := newClient(server.URL)
msgs, total, err := client.ListMessages(context.Background(), smsgateway.ListMessagesOptions{
From: &from,
To: &to,
State: &state,
DeviceID: &deviceID,
Limit: &limit,
Offset: &offset,
DatePeriodOptions: smsgateway.DatePeriodOptions{
From: &from,
To: &to,
},
State: &state,
DeviceID: &deviceID,
PaginationOptions: smsgateway.PaginationOptions{
Limit: &limit,
Offset: &offset,
},
IncludeContent: &includeContent,
})
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions smsgateway/domain_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const (

ScopeInboxList JWTScope = "inbox:list"
ScopeInboxRefresh JWTScope = "inbox:refresh"
ScopeInboxRead JWTScope = "inbox:read"

ScopeLogsRead JWTScope = "logs:read"

Expand Down
36 changes: 24 additions & 12 deletions smsgateway/dto_inbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"time"
)

// IncomingMessageType represents the type of incoming message.
// IncomingMessageType represents the type of inbox message.
type IncomingMessageType string

const (
Expand All @@ -14,21 +14,33 @@ const (
IncomingMessageTypeMmsDownloaded IncomingMessageType = "MMS_DOWNLOADED" // Downloaded MMS message
)

// IncomingMessage represents an incoming message received by the device.
// IncomingMessage represents an inbox message received by the device.
//
// ID is the incoming message ID.
// Type is the type of the incoming message (SMS, DATA_SMS, MMS, MMS_DOWNLOADED).
// Sender is the incoming sender phone number.
// ID is the inbox message ID.
// Type is the type of the inbox message (SMS, DATA_SMS, MMS, MMS_DOWNLOADED).
// Sender is the inbox sender phone number.
// Recipient is the recipient phone number on the device.
// SimNumber is the SIM slot number.
// ContentPreview is the message body preview or metadata.
// IsEncrypted indicates whether the message is encrypted.
// Attachments is the list of MMS attachments.
// CreatedAt is the message received timestamp.
type IncomingMessage struct {
ID string `json:"id" example:"PyDmBQZZXYmyxMwED8Fzy" validate:"required"` // Incoming message ID
Type IncomingMessageType `json:"type" example:"SMS" validate:"required"` // Message type
Sender string `json:"sender" example:"+79990001234" validate:"required"` // Incoming sender phone number
Recipient *string `json:"recipient,omitempty" example:"+79990001234" validate:"optional"` // Recipient phone number on the device
SimNumber *uint8 `json:"simNumber,omitempty" example:"1" validate:"optional"` // SIM slot number
ContentPreview string `json:"contentPreview" example:"Hello World!" validate:"required"` // Message body preview or metadata
CreatedAt time.Time `json:"createdAt" example:"2020-01-01T00:00:00Z" validate:"required" format:"date-time"` // Message received timestamp
ID string `json:"id" example:"PyDmBQZZXYmyxMwED8Fzy" validate:"required"` // Inbox message ID
Type IncomingMessageType `json:"type" example:"SMS" validate:"required"` // Message type
Sender string `json:"sender" example:"+79990001234" validate:"required"` // Inbox sender phone number
Recipient *string `json:"recipient,omitempty" example:"+79990001234" validate:"optional"` // Recipient phone number on the device
SimNumber *uint8 `json:"simNumber,omitempty" example:"1" validate:"optional"` // SIM slot number
ContentPreview string `json:"contentPreview" example:"Hello World!" validate:"required"` // Message body preview or metadata
IsEncrypted bool `json:"isEncrypted" example:"true"` // Whether the message is encrypted
Attachments []InboxAttachment `json:"attachments,omitempty"` // MMS attachments
CreatedAt time.Time `json:"createdAt" example:"2020-01-01T00:00:00Z" validate:"required" format:"date-time"` // Message received timestamp
}

// InboxAttachment represents an MMS attachment for an inbox message.
type InboxAttachment struct {
PartID int64 `json:"partId" example:"1"` // Attachment part ID
Name string `json:"name" example:"photo.jpg"` // Attachment file name
Size int64 `json:"size" example:"102400"` // Attachment file size
ContentType string `json:"contentType" example:"image/jpeg"` // Attachment MIME type
}
90 changes: 40 additions & 50 deletions smsgateway/requests_3rdparty.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,45 @@ package smsgateway

import (
"fmt"
"maps"
"math"
"net/url"
"strconv"
"time"
)

type PaginationOptions struct {
Limit *int `query:"limit" validate:"omitempty,min=1,max=100" default:"50"`
Offset *int `query:"offset" validate:"omitempty,min=0"`
}

func (o PaginationOptions) ToURLValues() url.Values {
values := url.Values{}
if o.Limit != nil {
values.Set("limit", strconv.Itoa(*o.Limit))
}
if o.Offset != nil {
values.Set("offset", strconv.Itoa(*o.Offset))
}
return values
}

type DatePeriodOptions struct {
From *time.Time `query:"from"`
To *time.Time `query:"to"`
}

func (o DatePeriodOptions) ToURLValues() url.Values {
values := url.Values{}
if o.From != nil {
values.Set("from", o.From.Format(time.RFC3339))
}
if o.To != nil {
values.Set("to", o.To.Format(time.RFC3339))
}
return values
}

type SendOption func(*SendOptions)

type SendOptions struct {
Expand Down Expand Up @@ -68,49 +101,14 @@ func WithDeviceActiveWithin(hours uint) SendOption {
}
}

// ListInboxOptions holds optional filters for listing inbox messages.
type ListInboxOptions struct {
Type *IncomingMessageType
Limit *int
Offset *int
From *time.Time
To *time.Time
DeviceID *string
}

// ToURLValues returns the ListInboxOptions as URL query parameters.
func (o ListInboxOptions) ToURLValues() url.Values {
values := url.Values{}
if o.Type != nil {
values.Set("type", string(*o.Type))
}
if o.Limit != nil {
values.Set("limit", strconv.Itoa(*o.Limit))
}
if o.Offset != nil {
values.Set("offset", strconv.Itoa(*o.Offset))
}
if o.From != nil {
values.Set("from", o.From.Format(time.RFC3339))
}
if o.To != nil {
values.Set("to", o.To.Format(time.RFC3339))
}
if o.DeviceID != nil {
values.Set("deviceId", *o.DeviceID)
}
return values
}

// ListMessagesOptions holds optional filters and sorting for listing messages.
// Sorting follows the JSON:API specification (sort parameter).
type ListMessagesOptions struct {
From *time.Time `query:"from"`
To *time.Time `query:"to"`
DatePeriodOptions
PaginationOptions

State *string `query:"state" validate:"omitempty,oneof=Pending Cancelling Cancelled Processed Sent Delivered Failed"`
DeviceID *string `query:"deviceId" validate:"omitempty,len=21"`
Limit *int `query:"limit" validate:"omitempty,min=1,max=100"`
Offset *int `query:"offset" validate:"omitempty,min=0"`
IncludeContent *bool `query:"includeContent"`
Sort *MessagesSortOrder `query:"sort" validate:"omitempty,oneof=created_at -created_at"`
}
Expand All @@ -127,30 +125,22 @@ func (o ListMessagesOptions) Validate() error {
// ToURLValues returns the ListMessagesOptions as URL query parameters.
func (o ListMessagesOptions) ToURLValues() url.Values {
values := url.Values{}
if o.From != nil {
values.Set("from", o.From.Format(time.RFC3339))
}
if o.To != nil {
values.Set("to", o.To.Format(time.RFC3339))
}
if o.State != nil {
values.Set("state", *o.State)
}
if o.DeviceID != nil {
values.Set("deviceId", *o.DeviceID)
}
if o.Limit != nil {
values.Set("limit", strconv.Itoa(*o.Limit))
}
if o.Offset != nil {
values.Set("offset", strconv.Itoa(*o.Offset))
}
if o.IncludeContent != nil {
values.Set("includeContent", strconv.FormatBool(*o.IncludeContent))
}
if o.Sort != nil {
values.Set("sort", string(*o.Sort))
}

maps.Copy(values, o.DatePeriodOptions.ToURLValues())
maps.Copy(values, o.PaginationOptions.ToURLValues())

return values
}

Expand Down
Loading
Loading