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
4 changes: 4 additions & 0 deletions api/requests.http
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ Authorization: Basic {{credentials}}
GET {{baseUrl}}/3rdparty/v1/messages?from=2025-01-01T00:00:00.000Z&to=2025-12-31T23:59:59Z&state=Pending&deviceId=fL2m4IirEvh9BvTf6TIB0&limit=50&offset=0 HTTP/1.1
Authorization: Basic {{credentials}}

###
GET {{baseUrl}}/3rdparty/v1/messages?sort=created_at HTTP/1.1
Authorization: Basic {{credentials}}

###
POST {{baseUrl}}/3rdparty/v1/messages/inbox/export HTTP/1.1
Authorization: Basic {{credentials}}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.25.8

require (
firebase.google.com/go/v4 v4.20.0
github.com/android-sms-gateway/client-go v1.14.0
github.com/android-sms-gateway/client-go v1.14.2-0.20260709051643-3f84f44a4653
github.com/ansrivas/fiberprometheus/v2 v2.17.0
github.com/capcom6/go-helpers v0.4.0
github.com/capcom6/go-infra-fx v0.5.8
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
github.com/android-sms-gateway/client-go v1.14.0 h1:CyKb8NgmOJZ+xOpBjY+Qv5Cj3vSSy6MoaDuI6G5oRLY=
github.com/android-sms-gateway/client-go v1.14.0/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
github.com/android-sms-gateway/client-go v1.14.2-0.20260709051643-3f84f44a4653 h1:tBUdjLI28WlkpbJSUkpba023B78Ectlf48cSSbFWGvA=
github.com/android-sms-gateway/client-go v1.14.2-0.20260709051643-3f84f44a4653/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/ansrivas/fiberprometheus/v2 v2.17.0 h1:p0gqs5LsSCWGoSFF44fCJkyU+XcE6TLRqEMu80b2iCo=
Expand Down
19 changes: 12 additions & 7 deletions internal/sms-gateway/handlers/messages/3rdparty.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,14 @@ func (h *ThirdPartyController) post(userID string, c *fiber.Ctx) error {
// @Security JWTAuth
// @Tags User, Messages
// @Produce json
// @Param from query string false "Start date in RFC3339 format" Format(date-time)
// @Param to query string false "End date in RFC3339 format" Format(date-time)
// @Param state query string false "Filter messages by processing state" Enum(Pending, Processed, Sent, Delivered, Failed)
// @Param deviceId query string false "Filter by device ID" min(21) max(21)
// @Param limit query int false "Pagination limit" default(50) min(1) max(100)
// @Param offset query int false "Pagination offset" default(0)
// @Param includeContent query bool false "Include textMessage/dataMessage content for each message. Default is false" default(false)
// @Param from query string false "Start date in RFC3339 format" Format(date-time)
// @Param to query string false "End date in RFC3339 format" Format(date-time)
// @Param state query string false "Filter messages by processing state" Enum(Pending, Processed, Sent, Delivered, Failed)
// @Param deviceId query string false "Filter by device ID" min(21) max(21)
// @Param limit query int false "Pagination limit" default(50) min(1) max(100)
// @Param offset query int false "Pagination offset" default(0)
// @Param includeContent query bool false "Include textMessage/dataMessage content for each message. Default is false" default(false)
// @Param sort query string false "Sort order per JSON:API spec. Use created_at (ascending) or -created_at (descending)" Enums(created_at, -created_at) default(-created_at)
// @Success 200 {object} smsgateway.GetMessagesResponse "A list of messages"
// @Header 200 {integer} X-Total-Count "Total number of items available"
// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request"
Expand All @@ -203,6 +204,10 @@ func (h *ThirdPartyController) list(userID string, c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}

if params.Sort == nil {
params.Sort = lo.ToPtr(smsgateway.CreatedAtDescending)
}

messages, total, err := h.messagesSvc.SelectStates(userID, params.ToFilter(), params.ToOptions())
if err != nil {
h.Logger.Error("failed to get message history", zap.Error(err), zap.String("user_id", userID))
Expand Down
64 changes: 27 additions & 37 deletions internal/sms-gateway/handlers/messages/params.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package messages

import (
"errors"
"time"

"github.com/android-sms-gateway/client-go/smsgateway"
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/messages"
)

Expand All @@ -12,45 +10,25 @@ type thirdPartyPostQueryParams struct {
DeviceActiveWithin int `query:"deviceActiveWithin" validate:"omitempty,min=1"`
}

type thirdPartyGetQueryParams struct {
StartDate string `query:"from" validate:"omitempty,datetime=2006-01-02T15:04:05Z07:00"`
EndDate string `query:"to" validate:"omitempty,datetime=2006-01-02T15:04:05Z07:00"`
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"`
}

func (p *thirdPartyGetQueryParams) Validate() error {
if p.StartDate != "" && p.EndDate != "" && p.StartDate > p.EndDate {
return errors.New("`from` date must be before `to` date") //nolint:err113 // won't be used directly
}

return nil
}
type thirdPartyGetQueryParams smsgateway.ListMessagesOptions

func (p *thirdPartyGetQueryParams) ToFilter() messages.SelectFilter {
var filter messages.SelectFilter

if p.StartDate != "" {
if t, err := time.Parse(time.RFC3339, p.StartDate); err == nil {
filter.StartDate = t
}
if p.From != nil {
filter.StartDate = *p.From
}

if p.EndDate != "" {
if t, err := time.Parse(time.RFC3339, p.EndDate); err == nil {
filter.EndDate = t
}
if p.To != nil {
filter.EndDate = *p.To
}

if p.State != "" {
filter.State = append(filter.State, messages.ProcessingState(p.State))
if p.State != nil {
filter.State = append(filter.State, messages.ProcessingState(*p.State))
}

if p.DeviceID != "" {
filter.DeviceID = p.DeviceID
if p.DeviceID != nil {
filter.DeviceID = *p.DeviceID
}

return filter
Expand All @@ -62,16 +40,28 @@ func (p *thirdPartyGetQueryParams) ToOptions() messages.SelectOptions {
var options messages.SelectOptions
options.WithRecipients = true
options.WithStates = true
options.WithContent = p.IncludeContent

if p.Limit > 0 {
options.Limit = min(p.Limit, maxLimit)
if p.IncludeContent != nil {
options.WithContent = *p.IncludeContent
}

if p.Limit != nil {
options.Limit = max(min(*p.Limit, maxLimit), 1)
} else {
options.Limit = 50
}
Comment thread
capcom6 marked this conversation as resolved.

if p.Offset > 0 {
options.Offset = p.Offset
if p.Offset != nil {
options.Offset = max(*p.Offset, 0)
}

if p.Sort != nil {
switch *p.Sort {
case smsgateway.CreatedAtAscending:
options.SortField = messages.SortFieldCreatedAtAsc
case smsgateway.CreatedAtDescending:
options.SortField = messages.SortFieldCreatedAtDesc
}
}

return options
Expand Down
9 changes: 7 additions & 2 deletions internal/sms-gateway/modules/messages/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,14 @@ func (r *Repository) list(filter SelectFilter, options SelectOptions) ([]message
}

// Apply ordering
if options.OrderBy == MessagesOrderFIFO {
switch {
case options.SortField == SortFieldCreatedAtAsc:
query = query.Order("messages.created_at ASC")
case options.SortField == SortFieldCreatedAtDesc:
query = query.Order("messages.created_at DESC")
case options.OrderBy == MessagesOrderFIFO:
query = query.Order("messages.schedule_at ASC, messages.priority DESC, messages.id ASC")
} else {
default:
query = query.Order("messages.schedule_at ASC, messages.priority DESC, messages.id DESC")
}

Expand Down
17 changes: 17 additions & 0 deletions internal/sms-gateway/modules/messages/repository_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ const (
MessagesOrderFIFO Order = "fifo"
)

// SortField defines the allowed column sort directions.
// Only values defined here may be used in SelectOptions.SortField.
type SortField string

const (
// SortFieldNone is the zero value (no explicit sort; falls through to OrderBy/default).
SortFieldNone SortField = ""
// SortFieldCreatedAtAsc sorts by created_at ascending.
SortFieldCreatedAtAsc SortField = "created_at ASC"
// SortFieldCreatedAtDesc sorts by created_at descending.
SortFieldCreatedAtDesc SortField = "created_at DESC"
)

type SelectFilter struct {
ExtID string
UserID string
Expand Down Expand Up @@ -58,6 +71,10 @@ type SelectOptions struct {
// Empty (zero) value defaults to "lifo".
OrderBy Order

// SortField selects a column sort direction.
// When non-zero, it overrides OrderBy.
SortField SortField

Limit int
Offset int
}
Expand Down
23 changes: 23 additions & 0 deletions internal/sms-gateway/openapi/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,17 @@ const docTemplate = `{
"description": "Include textMessage/dataMessage content for each message. Default is false",
"name": "includeContent",
"in": "query"
},
{
"enum": [
"created_at",
"-created_at"
],
"type": "string",
"default": "-created_at",
"description": "Sort order per JSON:API spec. Use created_at (ascending) or -created_at (descending)",
"name": "sort",
"in": "query"
}
],
"responses": {
Expand Down Expand Up @@ -2289,6 +2300,18 @@ const docTemplate = `{
"$ref": "#/definitions/smsgateway.SimSelectionMode"
}
]
},
"work_hours_enabled": {
"description": "WorkHoursEnabled enables restricting message delivery to a configurable time window.",
"type": "boolean"
},
"work_hours_end": {
"description": "WorkHoursEnd is the end of the working hours window in HH:mm format (24-hour).",
"type": "string"
},
"work_hours_start": {
"description": "WorkHoursStart is the start of the working hours window in HH:mm format (24-hour).",
"type": "string"
}
}
},
Expand Down
Loading