From 8f01332869ebf192c1d1dc606fed1c837129ee74 Mon Sep 17 00:00:00 2001 From: Aleksandr Soloshenko Date: Mon, 30 Jun 2025 07:00:44 +0700 Subject: [PATCH] [messages] add data messages support --- go.mod | 4 +- go.sum | 20 +- .../handlers/converters/messages.go | 23 +- .../handlers/converters/messages_test.go | 4 +- .../sms-gateway/handlers/messages/3rdparty.go | 22 +- internal/sms-gateway/models/migration.go | 2 +- .../20250628005423_data_messages_support.sql | 37 + internal/sms-gateway/models/models.go | 46 - internal/sms-gateway/modules/health/cli.go | 6 +- .../sms-gateway/modules/health/service.go | 5 +- .../modules/messages/converters.go | 24 +- .../sms-gateway/modules/messages/domain.go | 7 +- .../sms-gateway/modules/messages/models.go | 127 +++ .../sms-gateway/modules/messages/module.go | 5 + .../modules/messages/repository.go | 17 +- .../sms-gateway/modules/messages/service.go | 50 +- .../modules/messages/service_test.go | 11 +- internal/sms-gateway/modules/push/module.go | 7 +- pkg/swagger/docs/local.http | 29 + pkg/swagger/docs/requests.http | 29 + pkg/swagger/docs/swagger.json | 39 + pkg/swagger/docs/swagger.yaml | 825 +++++++++--------- 22 files changed, 836 insertions(+), 503 deletions(-) create mode 100644 internal/sms-gateway/models/migrations/mysql/20250628005423_data_messages_support.sql create mode 100644 internal/sms-gateway/modules/messages/models.go diff --git a/go.mod b/go.mod index 835ca7b..7ebbb6f 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ toolchain go1.23.2 require ( firebase.google.com/go/v4 v4.12.1 - github.com/android-sms-gateway/client-go v1.6.0 + github.com/android-sms-gateway/client-go v1.8.1 github.com/ansrivas/fiberprometheus/v2 v2.6.1 - github.com/capcom6/go-helpers v0.2.0 + github.com/capcom6/go-helpers v0.3.0 github.com/capcom6/go-infra-fx v0.2.1 github.com/go-playground/assert/v2 v2.2.0 github.com/go-playground/validator/v10 v10.16.0 diff --git a/go.sum b/go.sum index faa9ed3..0ebdca9 100644 --- a/go.sum +++ b/go.sum @@ -26,14 +26,12 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/android-sms-gateway/client-go v1.5.9-0.20250522134006-6e8b4dd3057a h1:TSmfm+KOsR1Ie10nZEjCVDepa1bEPin0NAgEUOSJiqw= -github.com/android-sms-gateway/client-go v1.5.9-0.20250522134006-6e8b4dd3057a/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= -github.com/android-sms-gateway/client-go v1.5.9-0.20250522231449-9e0855eff19f h1:VYrL6YbkQ49pcyiXTYcR5LN1WpNy1Tc684XjeE1UCvw= -github.com/android-sms-gateway/client-go v1.5.9-0.20250522231449-9e0855eff19f/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= -github.com/android-sms-gateway/client-go v1.5.9-0.20250524095300-2e41cae07049 h1:kdyVkqrgKDSI13JOKXVFz1al3IxfJPcbUaJvSXF6z+0= -github.com/android-sms-gateway/client-go v1.5.9-0.20250524095300-2e41cae07049/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= -github.com/android-sms-gateway/client-go v1.6.0 h1:3hN0XEUnNrweBl5Xx3IfE5zyq5ihm7fB0dhuTZBKlns= -github.com/android-sms-gateway/client-go v1.6.0/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= +github.com/android-sms-gateway/client-go v1.7.1-0.20250629114454-6a0c4d8bb90a h1:dAMTNI56fW8l5RrrwYUrvibIkpRCFw9jEFkjEw6mDMQ= +github.com/android-sms-gateway/client-go v1.7.1-0.20250629114454-6a0c4d8bb90a/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= +github.com/android-sms-gateway/client-go v1.8.1-0.20250701232650-4956a99b0da7 h1:rOBF445neI27pKcndrC3lH7buN8HrRvYvdVWCjFIsHg= +github.com/android-sms-gateway/client-go v1.8.1-0.20250701232650-4956a99b0da7/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= +github.com/android-sms-gateway/client-go v1.8.1 h1:cakQc4aw7oBKbcdCJsbP1HjM2BZouYSIKHRwHzBo2TY= +github.com/android-sms-gateway/client-go v1.8.1/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/ansrivas/fiberprometheus/v2 v2.6.1 h1:wac3pXaE6BYYTF04AC6K0ktk6vCD+MnDOJZ3SK66kXM= @@ -43,8 +41,10 @@ github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/capcom6/go-helpers v0.2.0 h1:OUcUnVbjBiwaTzvyaxkxqRKtrOXv1ifYalQ1NXzFBNM= -github.com/capcom6/go-helpers v0.2.0/go.mod h1:WDqc7HZNqHxUTisArkYIBZtqUfJBVyPWeQI+FMwEzAw= +github.com/capcom6/go-helpers v0.2.1-0.20250630235533-8457c7435058 h1:tt64ezShwdmcUk04gBVL1BD49FDAfVZ4ELiw2rrJp+I= +github.com/capcom6/go-helpers v0.2.1-0.20250630235533-8457c7435058/go.mod h1:WDqc7HZNqHxUTisArkYIBZtqUfJBVyPWeQI+FMwEzAw= +github.com/capcom6/go-helpers v0.3.0 h1:ae18fLfluoPubiB2V+j4cIpfZaTuK4acS2entamaDkE= +github.com/capcom6/go-helpers v0.3.0/go.mod h1:WDqc7HZNqHxUTisArkYIBZtqUfJBVyPWeQI+FMwEzAw= github.com/capcom6/go-infra-fx v0.2.1 h1:8rqr2ZV+YC2R07amHMdlE1XKLUhMe5yO+ffCJ/xXlNY= github.com/capcom6/go-infra-fx v0.2.1/go.mod h1:klScvB8QAKgJ19FfJOnUKK5tI0o9b79Aj2RmCJHfbN0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= diff --git a/internal/sms-gateway/handlers/converters/messages.go b/internal/sms-gateway/handlers/converters/messages.go index dfa0c1b..b1362cc 100644 --- a/internal/sms-gateway/handlers/converters/messages.go +++ b/internal/sms-gateway/handlers/converters/messages.go @@ -6,10 +6,29 @@ import ( ) func MessageToDTO(m messages.MessageOut) smsgateway.MobileMessage { + var message string + var textMessage *smsgateway.TextMessage + var dataMessage *smsgateway.DataMessage + + if m.TextContent != nil { + textMessage = &smsgateway.TextMessage{ + Text: m.TextContent.Text, + } + } else if m.DataContent != nil { + dataMessage = &smsgateway.DataMessage{ + Data: m.DataContent.Data, + Port: m.DataContent.Port, + } + } + return smsgateway.MobileMessage{ Message: smsgateway.Message{ - ID: m.ID, - Message: m.Message, + ID: m.ID, + + Message: message, + TextMessage: textMessage, + DataMessage: dataMessage, + SimNumber: m.SimNumber, WithDeliveryReport: m.WithDeliveryReport, IsEncrypted: m.IsEncrypted, diff --git a/internal/sms-gateway/handlers/converters/messages_test.go b/internal/sms-gateway/handlers/converters/messages_test.go index e429a19..9756244 100644 --- a/internal/sms-gateway/handlers/converters/messages_test.go +++ b/internal/sms-gateway/handlers/converters/messages_test.go @@ -26,7 +26,7 @@ func TestMessageToDTO(t *testing.T) { input: messages.MessageOut{ MessageIn: messages.MessageIn{ ID: "msg-123", - Message: "Test message content", + TextContent: &messages.TextMessageContent{Text: "Test message content"}, PhoneNumbers: []string{"+1234567890", "+9876543210"}, IsEncrypted: true, SimNumber: anys.AsPointer(uint8(2)), @@ -57,7 +57,7 @@ func TestMessageToDTO(t *testing.T) { input: messages.MessageOut{ MessageIn: messages.MessageIn{ ID: "msg-456", - Message: "Another test message", + TextContent: &messages.TextMessageContent{Text: "Another test message"}, PhoneNumbers: []string{"+1122334455"}, }, CreatedAt: now, diff --git a/internal/sms-gateway/handlers/messages/3rdparty.go b/internal/sms-gateway/handlers/messages/3rdparty.go index 1cb6b6e..c92d646 100644 --- a/internal/sms-gateway/handlers/messages/3rdparty.go +++ b/internal/sms-gateway/handlers/messages/3rdparty.go @@ -78,9 +78,27 @@ func (h *ThirdPartyController) post(user models.User, c *fiber.Ctx) error { return fmt.Errorf("can't get random device: %w", err) } + var textContent *messages.TextMessageContent + var dataContent *messages.DataMessageContent + if text := req.GetTextMessage(); text != nil { + textContent = &messages.TextMessageContent{ + Text: text.Text, + } + } else if data := req.GetDataMessage(); data != nil { + dataContent = &messages.DataMessageContent{ + Data: data.Data, + Port: data.Port, + } + } else { + return fiber.NewError(fiber.StatusBadRequest, "No message content provided") + } + msg := messages.MessageIn{ - ID: req.ID, - Message: req.Message, + ID: req.ID, + + TextContent: textContent, + DataContent: dataContent, + PhoneNumbers: req.PhoneNumbers, IsEncrypted: req.IsEncrypted, diff --git a/internal/sms-gateway/models/migration.go b/internal/sms-gateway/models/migration.go index 1e29c8d..1ab9851 100644 --- a/internal/sms-gateway/models/migration.go +++ b/internal/sms-gateway/models/migration.go @@ -10,5 +10,5 @@ import ( var migrations embed.FS func Migrate(db *gorm.DB) error { - return db.AutoMigrate(&User{}, &Device{}, &Message{}, &MessageRecipient{}, &MessageState{}) + return db.AutoMigrate(&User{}, &Device{}) } diff --git a/internal/sms-gateway/models/migrations/mysql/20250628005423_data_messages_support.sql b/internal/sms-gateway/models/migrations/mysql/20250628005423_data_messages_support.sql new file mode 100644 index 0000000..629daaa --- /dev/null +++ b/internal/sms-gateway/models/migrations/mysql/20250628005423_data_messages_support.sql @@ -0,0 +1,37 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE `messages` +ADD `type` enum('Text', 'Data') NOT NULL DEFAULT 'Text', + ADD `content` text NOT NULL, + MODIFY `message` text NULL; +-- +goose StatementEnd +-- +goose StatementBegin +UPDATE `messages` +SET `content` = json_object('text', `message`) +WHERE `is_hashed` = 0; +-- +goose StatementEnd +-- +goose StatementBegin +UPDATE `messages` +SET `content` = `message` +WHERE `is_hashed` = 1; +-- +goose StatementEnd +--- +-- +goose Down +-- +goose StatementBegin +UPDATE `messages` +SET `message` = COALESCE( + json_value(`content`, '$.text'), + json_value(`content`, '$.data') + ) +WHERE `is_hashed` = 0; +-- +goose StatementEnd +-- +goose StatementBegin +UPDATE `messages` +SET `message` = `content` +WHERE `is_hashed` = 1; +-- +goose StatementEnd +-- +goose StatementBegin +ALTER TABLE `messages` DROP `type`, + DROP `content`, + MODIFY `message` text NOT NULL; +-- +goose StatementEnd \ No newline at end of file diff --git a/internal/sms-gateway/models/models.go b/internal/sms-gateway/models/models.go index 8213864..f99e9ab 100644 --- a/internal/sms-gateway/models/models.go +++ b/internal/sms-gateway/models/models.go @@ -4,16 +4,6 @@ import ( "time" ) -type ProcessingState string - -const ( - ProcessingStatePending ProcessingState = "Pending" - ProcessingStateProcessed ProcessingState = "Processed" - ProcessingStateSent ProcessingState = "Sent" - ProcessingStateDelivered ProcessingState = "Delivered" - ProcessingStateFailed ProcessingState = "Failed" -) - type TimedModel struct { CreatedAt time.Time `gorm:"->;not null;autocreatetime:false;default:CURRENT_TIMESTAMP(3)"` UpdatedAt time.Time `gorm:"->;not null;autoupdatetime:false;default:CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"` @@ -52,39 +42,3 @@ func (d *Device) IsEmpty() bool { return d.ID == "" } - -type Message struct { - ID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED;autoIncrement"` - DeviceID string `gorm:"not null;type:char(21);uniqueIndex:unq_messages_id_device,priority:2;index:idx_messages_device_state"` - ExtID string `gorm:"not null;type:varchar(36);uniqueIndex:unq_messages_id_device,priority:1"` - Message string `gorm:"not null;type:text"` - State ProcessingState `gorm:"not null;type:enum('Pending','Sent','Processed','Delivered','Failed');default:Pending;index:idx_messages_device_state"` - ValidUntil *time.Time `gorm:"type:datetime"` - SimNumber *uint8 `gorm:"type:tinyint(1) unsigned"` - WithDeliveryReport bool `gorm:"not null;type:tinyint(1) unsigned"` - Priority int8 `gorm:"not null;type:tinyint;default:0"` - - IsHashed bool `gorm:"not null;type:tinyint(1) unsigned;default:0"` - IsEncrypted bool `gorm:"not null;type:tinyint(1) unsigned;default:0"` - - Device Device `gorm:"foreignKey:DeviceID;constraint:OnDelete:CASCADE"` - Recipients []MessageRecipient `gorm:"foreignKey:MessageID;constraint:OnDelete:CASCADE"` - States []MessageState `gorm:"foreignKey:MessageID;constraint:OnDelete:CASCADE"` - - SoftDeletableModel -} - -type MessageRecipient struct { - ID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED;autoIncrement"` - MessageID uint64 `gorm:"uniqueIndex:unq_message_recipients_message_id_phone_number,priority:1;type:BIGINT UNSIGNED"` - PhoneNumber string `gorm:"uniqueIndex:unq_message_recipients_message_id_phone_number,priority:2;type:varchar(128)"` - State ProcessingState `gorm:"not null;type:enum('Pending','Sent','Processed','Delivered','Failed');default:Pending"` - Error *string `gorm:"type:varchar(256)"` -} - -type MessageState struct { - ID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED;autoIncrement"` - MessageID uint64 `gorm:"not null;type:BIGINT UNSIGNED;uniqueIndex:unq_message_states_message_id_state,priority:1"` - State ProcessingState `gorm:"not null;type:enum('Pending','Sent','Processed','Delivered','Failed');uniqueIndex:unq_message_states_message_id_state,priority:2"` - UpdatedAt time.Time `gorm:"<-:create;not null;autoupdatetime:false"` -} diff --git a/internal/sms-gateway/modules/health/cli.go b/internal/sms-gateway/modules/health/cli.go index 678ae62..b20d2bb 100644 --- a/internal/sms-gateway/modules/health/cli.go +++ b/internal/sms-gateway/modules/health/cli.go @@ -23,7 +23,11 @@ func testHealth(shutdowner fx.Shutdowner, logger *zap.Logger, config http.Config } return } - defer res.Body.Close() + defer func() { + if err := res.Body.Close(); err != nil { + logger.Error("Failed to close body", zap.Error(err)) + } + }() body, err := io.ReadAll(res.Body) if err != nil { diff --git a/internal/sms-gateway/modules/health/service.go b/internal/sms-gateway/modules/health/service.go index 9d968ac..6595926 100644 --- a/internal/sms-gateway/modules/health/service.go +++ b/internal/sms-gateway/modules/health/service.go @@ -48,9 +48,10 @@ func (s *Service) HealthCheck(ctx context.Context) (Check, error) { for name, detail := range healthChecks { check.Checks[p.Name()+":"+name] = detail - if detail.Status == StatusFail { + switch detail.Status { + case StatusFail: level = max(level, levelFail) - } else if detail.Status == StatusWarn { + case StatusWarn: level = max(level, levelWarn) } } diff --git a/internal/sms-gateway/modules/messages/converters.go b/internal/sms-gateway/modules/messages/converters.go index 8ffd5e5..221dd4d 100644 --- a/internal/sms-gateway/modules/messages/converters.go +++ b/internal/sms-gateway/modules/messages/converters.go @@ -1,25 +1,37 @@ package messages import ( + "fmt" "math" "time" "github.com/android-sms-gateway/client-go/smsgateway" - "github.com/android-sms-gateway/server/internal/sms-gateway/models" "github.com/capcom6/go-helpers/slices" ) -func messageToDomain(input models.Message) MessageOut { +func messageToDomain(input Message) (MessageOut, error) { var ttl *uint64 = nil if input.ValidUntil != nil { secondsUntil := uint64(math.Max(0, time.Until(*input.ValidUntil).Seconds())) ttl = &secondsUntil } + textContent, err := input.GetTextContent() + if err != nil { + return MessageOut{}, fmt.Errorf("can't get text content: %w", err) + } + dataContent, err := input.GetDataContent() + if err != nil { + return MessageOut{}, fmt.Errorf("can't get data content: %w", err) + } + return MessageOut{ MessageIn: MessageIn{ - ID: input.ExtID, - Message: input.Message, + ID: input.ExtID, + + TextContent: textContent, + DataContent: dataContent, + PhoneNumbers: slices.Map(input.Recipients, recipientToDomain), IsEncrypted: input.IsEncrypted, SimNumber: input.SimNumber, @@ -29,9 +41,9 @@ func messageToDomain(input models.Message) MessageOut { Priority: smsgateway.MessagePriority(input.Priority), }, CreatedAt: input.CreatedAt, - } + }, nil } -func recipientToDomain(input models.MessageRecipient) string { +func recipientToDomain(input MessageRecipient) string { return input.PhoneNumber } diff --git a/internal/sms-gateway/modules/messages/domain.go b/internal/sms-gateway/modules/messages/domain.go index ad03783..2cfd7f9 100644 --- a/internal/sms-gateway/modules/messages/domain.go +++ b/internal/sms-gateway/modules/messages/domain.go @@ -7,8 +7,11 @@ import ( ) type MessageIn struct { - ID string - Message string + ID string + + TextContent *TextMessageContent + DataContent *DataMessageContent + PhoneNumbers []string IsEncrypted bool diff --git a/internal/sms-gateway/modules/messages/models.go b/internal/sms-gateway/modules/messages/models.go new file mode 100644 index 0000000..3b1a4ba --- /dev/null +++ b/internal/sms-gateway/modules/messages/models.go @@ -0,0 +1,127 @@ +package messages + +import ( + "encoding/json" + "time" + + "github.com/android-sms-gateway/server/internal/sms-gateway/models" + "gorm.io/gorm" +) + +type ProcessingState string +type MessageType string + +const ( + ProcessingStatePending ProcessingState = "Pending" + ProcessingStateProcessed ProcessingState = "Processed" + ProcessingStateSent ProcessingState = "Sent" + ProcessingStateDelivered ProcessingState = "Delivered" + ProcessingStateFailed ProcessingState = "Failed" + + MessageTypeText MessageType = "Text" + MessageTypeData MessageType = "Data" +) + +type TextMessageContent struct { + Text string `json:"text"` +} + +type DataMessageContent struct { + Data string `json:"data"` + Port uint16 `json:"port"` +} + +type Message struct { + ID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED;autoIncrement"` + DeviceID string `gorm:"not null;type:char(21);uniqueIndex:unq_messages_id_device,priority:2;index:idx_messages_device_state"` + ExtID string `gorm:"not null;type:varchar(36);uniqueIndex:unq_messages_id_device,priority:1"` + Type MessageType `gorm:"not null;type:enum('Text','Data');default:Text"` + Content string `gorm:"not null;type:text"` + State ProcessingState `gorm:"not null;type:enum('Pending','Sent','Processed','Delivered','Failed');default:Pending;index:idx_messages_device_state"` + ValidUntil *time.Time `gorm:"type:datetime"` + SimNumber *uint8 `gorm:"type:tinyint(1) unsigned"` + WithDeliveryReport bool `gorm:"not null;type:tinyint(1) unsigned"` + Priority int8 `gorm:"not null;type:tinyint;default:0"` + + IsHashed bool `gorm:"not null;type:tinyint(1) unsigned;default:0"` + IsEncrypted bool `gorm:"not null;type:tinyint(1) unsigned;default:0"` + + Device models.Device `gorm:"foreignKey:DeviceID;constraint:OnDelete:CASCADE"` + Recipients []MessageRecipient `gorm:"foreignKey:MessageID;constraint:OnDelete:CASCADE"` + States []MessageState `gorm:"foreignKey:MessageID;constraint:OnDelete:CASCADE"` + + models.SoftDeletableModel +} + +func (m *Message) SetTextContent(content TextMessageContent) error { + contentJSON, err := json.Marshal(content) + if err != nil { + return err + } + + m.Type = MessageTypeText + m.Content = string(contentJSON) + + return nil +} + +func (m *Message) GetTextContent() (*TextMessageContent, error) { + if m.Type != MessageTypeText { + return nil, nil + } + + content := TextMessageContent{} + + err := json.Unmarshal([]byte(m.Content), &content) + if err != nil { + return nil, err + } + + return &content, nil +} + +func (m *Message) SetDataContent(content DataMessageContent) error { + contentJSON, err := json.Marshal(content) + if err != nil { + return err + } + + m.Type = MessageTypeData + m.Content = string(contentJSON) + + return nil +} + +func (m *Message) GetDataContent() (*DataMessageContent, error) { + if m.Type != MessageTypeData { + return nil, nil + } + + content := DataMessageContent{} + + err := json.Unmarshal([]byte(m.Content), &content) + if err != nil { + return nil, err + } + + return &content, nil +} + +type MessageRecipient struct { + ID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED;autoIncrement"` + MessageID uint64 `gorm:"uniqueIndex:unq_message_recipients_message_id_phone_number,priority:1;type:BIGINT UNSIGNED"` + PhoneNumber string `gorm:"uniqueIndex:unq_message_recipients_message_id_phone_number,priority:2;type:varchar(128)"` + State ProcessingState `gorm:"not null;type:enum('Pending','Sent','Processed','Delivered','Failed');default:Pending"` + Error *string `gorm:"type:varchar(256)"` +} + +type MessageState struct { + ID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED;autoIncrement"` + MessageID uint64 `gorm:"not null;type:BIGINT UNSIGNED;uniqueIndex:unq_message_states_message_id_state,priority:1"` + State ProcessingState `gorm:"not null;type:enum('Pending','Sent','Processed','Delivered','Failed');uniqueIndex:unq_message_states_message_id_state,priority:2"` + UpdatedAt time.Time `gorm:"<-:create;not null;autoupdatetime:false"` +} + +func Migrate(db *gorm.DB) error { + return db.AutoMigrate(&Message{}, &MessageRecipient{}, &MessageState{}) +} diff --git a/internal/sms-gateway/modules/messages/module.go b/internal/sms-gateway/modules/messages/module.go index f672cdc..3dd2fc5 100644 --- a/internal/sms-gateway/modules/messages/module.go +++ b/internal/sms-gateway/modules/messages/module.go @@ -2,6 +2,7 @@ package messages import ( "github.com/android-sms-gateway/server/internal/sms-gateway/modules/cleaner" + "github.com/capcom6/go-infra-fx/db" "go.uber.org/fx" "go.uber.org/zap" ) @@ -31,3 +32,7 @@ var Module = fx.Module( fx.Provide(newRepository), fx.Provide(NewHashingTask, fx.Private), ) + +func init() { + db.RegisterMigration(Migrate) +} diff --git a/internal/sms-gateway/modules/messages/repository.go b/internal/sms-gateway/modules/messages/repository.go index 06b8475..99eb9c7 100644 --- a/internal/sms-gateway/modules/messages/repository.go +++ b/internal/sms-gateway/modules/messages/repository.go @@ -6,7 +6,6 @@ import ( "errors" "time" - "github.com/android-sms-gateway/server/internal/sms-gateway/models" "github.com/go-sql-driver/mysql" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -21,9 +20,9 @@ type repository struct { db *gorm.DB } -func (r *repository) SelectPending(deviceID string) (messages []models.Message, err error) { +func (r *repository) SelectPending(deviceID string) (messages []Message, err error) { err = r.db. - Where("device_id = ? AND state = ?", deviceID, models.ProcessingStatePending). + Where("device_id = ? AND state = ?", deviceID, ProcessingStatePending). Order("priority DESC, id DESC"). Limit(100). Preload("Recipients"). @@ -33,7 +32,7 @@ func (r *repository) SelectPending(deviceID string) (messages []models.Message, return } -func (r *repository) Get(ID string, filter MessagesSelectFilter, options ...MessagesSelectOptions) (message models.Message, err error) { +func (r *repository) Get(ID string, filter MessagesSelectFilter, options ...MessagesSelectOptions) (message Message, err error) { query := r.db.Model(&message). Where("ext_id = ?", ID) @@ -58,7 +57,7 @@ func (r *repository) Get(ID string, filter MessagesSelectFilter, options ...Mess return } -func (r *repository) Insert(message *models.Message) error { +func (r *repository) Insert(message *Message) error { err := r.db.Omit("Device").Create(message).Error if err == nil { return nil @@ -70,7 +69,7 @@ func (r *repository) Insert(message *models.Message) error { return err } -func (r *repository) UpdateState(message *models.Message) error { +func (r *repository) UpdateState(message *Message) error { return r.db.Transaction(func(tx *gorm.DB) error { if err := tx.Model(message).Select("State").Updates(message).Error; err != nil { return err @@ -97,7 +96,7 @@ func (r *repository) UpdateState(message *models.Message) error { func (r *repository) HashProcessed(ids []uint64) error { rawSQL := "UPDATE `messages` `m`, `message_recipients` `r`\n" + - "SET `m`.`is_hashed` = true, `m`.`message` = SHA2(m.message, 256), `r`.`phone_number` = LEFT(SHA2(phone_number, 256), 16)\n" + + "SET `m`.`is_hashed` = true, `m`.`content` = SHA2(COALESCE(JSON_VALUE(`content`, '$.text'), JSON_VALUE(`content`, '$.data')), 256), `r`.`phone_number` = LEFT(SHA2(phone_number, 256), 16)\n" + "WHERE `m`.`id` = `r`.`message_id` AND `m`.`is_hashed` = false AND `m`.`is_encrypted` = false AND `m`.`state` <> 'Pending'" params := []interface{}{} if len(ids) > 0 { @@ -130,9 +129,9 @@ func (r *repository) HashProcessed(ids []uint64) error { func (r *repository) removeProcessed(ctx context.Context, until time.Time) (int64, error) { res := r.db. WithContext(ctx). - Where("state <> ?", models.ProcessingStatePending). + Where("state <> ?", ProcessingStatePending). Where("created_at < ?", until). - Delete(&models.Message{}) + Delete(&Message{}) return res.RowsAffected, res.Error } diff --git a/internal/sms-gateway/modules/messages/service.go b/internal/sms-gateway/modules/messages/service.go index 9689816..997ce31 100644 --- a/internal/sms-gateway/modules/messages/service.go +++ b/internal/sms-gateway/modules/messages/service.go @@ -101,7 +101,7 @@ func (s *Service) SelectPending(deviceID string) ([]MessageOut, error) { return nil, err } - return slices.Map(messages, messageToDomain), nil + return slices.MapOrError(messages, messageToDomain) } func (s *Service) UpdateState(deviceID string, message smsgateway.MessageState) error { @@ -114,11 +114,11 @@ func (s *Service) UpdateState(deviceID string, message smsgateway.MessageState) message.State = smsgateway.ProcessingStateProcessed } - existing.State = models.ProcessingState(message.State) - existing.States = slices.Map(maps.Keys(message.States), func(key string) models.MessageState { - return models.MessageState{ + existing.State = ProcessingState(message.State) + existing.States = slices.Map(maps.Keys(message.States), func(key string) MessageState { + return MessageState{ MessageID: existing.ID, - State: models.ProcessingState(key), + State: ProcessingState(key), UpdatedAt: message.States[key], } }) @@ -178,14 +178,13 @@ func (s *Service) Enqueue(device models.Device, message MessageIn, opts EnqueueO } } - var validUntil *time.Time = message.ValidUntil + validUntil := message.ValidUntil if message.TTL != nil && *message.TTL > 0 { validUntil = anys.AsPointer(time.Now().Add(time.Duration(*message.TTL) * time.Second)) } - msg := models.Message{ + msg := Message{ ExtID: message.ID, - Message: message.Message, Recipients: s.recipientsToModel(message.PhoneNumbers), IsEncrypted: message.IsEncrypted, @@ -197,6 +196,19 @@ func (s *Service) Enqueue(device models.Device, message MessageIn, opts EnqueueO Priority: int8(message.Priority), ValidUntil: validUntil, } + + if message.TextContent != nil { + if err := msg.SetTextContent(*message.TextContent); err != nil { + return state, fmt.Errorf("can't set text content: %w", err) + } + } else if message.DataContent != nil { + if err := msg.SetDataContent(*message.DataContent); err != nil { + return state, fmt.Errorf("can't set data content: %w", err) + } + } else { + return state, errors.New("no text or data content") + } + if msg.ExtID == "" { msg.ExtID = s.idgen() } @@ -241,11 +253,11 @@ func (s *Service) Clean(ctx context.Context) error { /////////////////////////////////////////////////////////////////////////////// -func (s *Service) recipientsToModel(input []string) []models.MessageRecipient { - output := make([]models.MessageRecipient, len(input)) +func (s *Service) recipientsToModel(input []string) []MessageRecipient { + output := make([]MessageRecipient, len(input)) for i, v := range input { - output[i] = models.MessageRecipient{ + output[i] = MessageRecipient{ PhoneNumber: v, } } @@ -253,8 +265,8 @@ func (s *Service) recipientsToModel(input []string) []models.MessageRecipient { return output } -func (s *Service) recipientsStateToModel(input []smsgateway.RecipientState, hash bool) []models.MessageRecipient { - output := make([]models.MessageRecipient, len(input)) +func (s *Service) recipientsStateToModel(input []smsgateway.RecipientState, hash bool) []MessageRecipient { + output := make([]MessageRecipient, len(input)) for i, v := range input { phoneNumber := v.PhoneNumber @@ -271,9 +283,9 @@ func (s *Service) recipientsStateToModel(input []smsgateway.RecipientState, hash phoneNumber = fmt.Sprintf("%x", sha256.Sum256([]byte(phoneNumber)))[:16] } - output[i] = models.MessageRecipient{ + output[i] = MessageRecipient{ PhoneNumber: phoneNumber, - State: models.ProcessingState(v.State), + State: ProcessingState(v.State), Error: v.Error, } } @@ -281,7 +293,7 @@ func (s *Service) recipientsStateToModel(input []smsgateway.RecipientState, hash return output } -func modelToMessageState(input models.Message) smsgateway.MessageState { +func modelToMessageState(input Message) smsgateway.MessageState { return smsgateway.MessageState{ ID: input.ExtID, State: smsgateway.ProcessingState(input.State), @@ -290,13 +302,13 @@ func modelToMessageState(input models.Message) smsgateway.MessageState { Recipients: slices.Map(input.Recipients, modelToRecipientState), States: slices.Associate( input.States, - func(state models.MessageState) string { return string(state.State) }, - func(state models.MessageState) time.Time { return state.UpdatedAt }, + func(state MessageState) string { return string(state.State) }, + func(state MessageState) time.Time { return state.UpdatedAt }, ), } } -func modelToRecipientState(input models.MessageRecipient) smsgateway.RecipientState { +func modelToRecipientState(input MessageRecipient) smsgateway.RecipientState { return smsgateway.RecipientState{ PhoneNumber: input.PhoneNumber, State: smsgateway.ProcessingState(input.State), diff --git a/internal/sms-gateway/modules/messages/service_test.go b/internal/sms-gateway/modules/messages/service_test.go index 2c4f9d8..1113b70 100644 --- a/internal/sms-gateway/modules/messages/service_test.go +++ b/internal/sms-gateway/modules/messages/service_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/android-sms-gateway/client-go/smsgateway" - "github.com/android-sms-gateway/server/internal/sms-gateway/models" ) func TestService_recipientsStateToModel(t *testing.T) { @@ -17,7 +16,7 @@ func TestService_recipientsStateToModel(t *testing.T) { name string s *Service args args - want []models.MessageRecipient + want []MessageRecipient }{ { name: "Without +", @@ -30,7 +29,7 @@ func TestService_recipientsStateToModel(t *testing.T) { }, }, }, - want: []models.MessageRecipient{ + want: []MessageRecipient{ { MessageID: 0, PhoneNumber: "+79990001234", @@ -49,7 +48,7 @@ func TestService_recipientsStateToModel(t *testing.T) { }, }, }, - want: []models.MessageRecipient{ + want: []MessageRecipient{ { MessageID: 0, PhoneNumber: "+79990001234", @@ -69,7 +68,7 @@ func TestService_recipientsStateToModel(t *testing.T) { }, hash: true, }, - want: []models.MessageRecipient{ + want: []MessageRecipient{ { MessageID: 0, PhoneNumber: "62d17792b45c5307", @@ -88,7 +87,7 @@ func TestService_recipientsStateToModel(t *testing.T) { }, }, }, - want: []models.MessageRecipient{ + want: []MessageRecipient{ { MessageID: 0, PhoneNumber: "", diff --git a/internal/sms-gateway/modules/push/module.go b/internal/sms-gateway/modules/push/module.go index 269e511..ad39602 100644 --- a/internal/sms-gateway/modules/push/module.go +++ b/internal/sms-gateway/modules/push/module.go @@ -17,11 +17,12 @@ var Module = fx.Module( }), fx.Provide( func(cfg Config, lc fx.Lifecycle) (c client, err error) { - if cfg.Mode == ModeFCM { + switch cfg.Mode { + case ModeFCM: c, err = fcm.New(cfg.ClientOptions) - } else if cfg.Mode == ModeUpstream { + case ModeUpstream: c, err = upstream.New(cfg.ClientOptions) - } else { + default: return nil, errors.New("invalid push mode") } diff --git a/pkg/swagger/docs/local.http b/pkg/swagger/docs/local.http index 9648c21..3afe8f1 100644 --- a/pkg/swagger/docs/local.http +++ b/pkg/swagger/docs/local.http @@ -29,6 +29,35 @@ POST {{localUrl}}/message HTTP/1.1 Content-Type: application/json Authorization: Basic {{localCredentials}} +{ + "textMessage": { + "text": "{{$localDatetime iso8601}}" + }, + "phoneNumbers": [ + "{{phone}}" + ] +} + +### +POST {{localUrl}}/message HTTP/1.1 +Content-Type: application/json +Authorization: Basic {{localCredentials}} + +{ + "dataMessage": { + "data": "SGVsbG8gV29ybGQh", + "port": 12345 + }, + "phoneNumbers": [ + "{{phone}}" + ] +} + +### +POST {{localUrl}}/message HTTP/1.1 +Content-Type: application/json +Authorization: Basic {{localCredentials}} + { "message": "{{$localDatetime iso8601}}", "ttl": 86400, diff --git a/pkg/swagger/docs/requests.http b/pkg/swagger/docs/requests.http index 40fea78..bd5b12a 100644 --- a/pkg/swagger/docs/requests.http +++ b/pkg/swagger/docs/requests.http @@ -30,6 +30,35 @@ POST {{baseUrl}}/3rdparty/v1/messages HTTP/1.1 Content-Type: application/json Authorization: Basic {{credentials}} +{ + "textMessage": { + "text": "{{$localDatetime iso8601}}" + }, + "phoneNumbers": [ + "{{phone}}" + ] +} + +### +POST {{baseUrl}}/3rdparty/v1/messages HTTP/1.1 +Content-Type: application/json +Authorization: Basic {{credentials}} + +{ + "dataMessage": { + "data": "SGVsbG8gRGF0YSBXb3JsZCE=", + "port": 53739 + }, + "phoneNumbers": [ + "{{phone}}" + ] +} + +### +POST {{baseUrl}}/3rdparty/v1/messages HTTP/1.1 +Content-Type: application/json +Authorization: Basic {{credentials}} + { "message": "$aes-256-cbc/pbkdf2-sha1$i=75000$pb+tpPcF0nabV46wDeDMig==$ucdVkMrRYLQ0LAeoXQsWhrD36I9nnop8rRIh3dNmBhvg7Wc4Cwu3h9Petvp1dN3x", "ttl": 600, diff --git a/pkg/swagger/docs/swagger.json b/pkg/swagger/docs/swagger.json index d0f27ed..d5c749d 100644 --- a/pkg/swagger/docs/swagger.json +++ b/pkg/swagger/docs/swagger.json @@ -1138,6 +1138,30 @@ } }, "definitions": { + "smsgateway.DataMessage": { + "type": "object", + "required": [ + "data", + "port" + ], + "properties": { + "data": { + "description": "Base64-encoded payload", + "type": "string", + "format": "byte", + "maxLength": 65535, + "minLength": 4, + "example": "SGVsbG8gV29ybGQh" + }, + "port": { + "description": "Destination port", + "type": "integer", + "maximum": 65535, + "minimum": 1, + "example": 53739 + } + } + }, "smsgateway.Device": { "type": "object", "properties": { @@ -1943,6 +1967,21 @@ "Random" ] }, + "smsgateway.TextMessage": { + "type": "object", + "required": [ + "text" + ], + "properties": { + "text": { + "description": "Message text", + "type": "string", + "maxLength": 65535, + "minLength": 1, + "example": "Hello World!" + } + } + }, "smsgateway.Webhook": { "type": "object", "required": [ diff --git a/pkg/swagger/docs/swagger.yaml b/pkg/swagger/docs/swagger.yaml index 7c94e28..21bc44c 100644 --- a/pkg/swagger/docs/swagger.yaml +++ b/pkg/swagger/docs/swagger.yaml @@ -1,4 +1,23 @@ definitions: + smsgateway.DataMessage: + properties: + data: + description: Base64-encoded payload + example: SGVsbG8gV29ybGQh + format: byte + maxLength: 65535 + minLength: 4 + type: string + port: + description: Destination port + example: 53739 + maximum: 65535 + minimum: 1 + type: integer + required: + - data + - port + type: object smsgateway.Device: properties: createdAt: @@ -30,23 +49,23 @@ definitions: properties: encryption: allOf: - - $ref: '#/definitions/smsgateway.SettingsEncryption' + - $ref: "#/definitions/smsgateway.SettingsEncryption" description: Encryption contains settings related to message encryption. logs: allOf: - - $ref: '#/definitions/smsgateway.SettingsLogs' + - $ref: "#/definitions/smsgateway.SettingsLogs" description: Logs contains settings related to logging. messages: allOf: - - $ref: '#/definitions/smsgateway.SettingsMessages' + - $ref: "#/definitions/smsgateway.SettingsMessages" description: Messages contains settings related to message handling. ping: allOf: - - $ref: '#/definitions/smsgateway.SettingsPing' + - $ref: "#/definitions/smsgateway.SettingsPing" description: Ping contains settings related to ping functionality. webhooks: allOf: - - $ref: '#/definitions/smsgateway.SettingsWebhooks' + - $ref: "#/definitions/smsgateway.SettingsWebhooks" description: Webhooks contains settings related to webhook functionality. type: object smsgateway.ErrorResponse: @@ -74,20 +93,20 @@ definitions: type: integer status: allOf: - - $ref: '#/definitions/smsgateway.HealthStatus' + - $ref: "#/definitions/smsgateway.HealthStatus" description: |- Status of the check. It can be one of the following values: "pass", "warn", or "fail". type: object smsgateway.HealthChecks: additionalProperties: - $ref: '#/definitions/smsgateway.HealthCheck' + $ref: "#/definitions/smsgateway.HealthCheck" type: object smsgateway.HealthResponse: properties: checks: allOf: - - $ref: '#/definitions/smsgateway.HealthChecks' + - $ref: "#/definitions/smsgateway.HealthChecks" description: A map of check names to their respective details. releaseId: description: |- @@ -96,7 +115,7 @@ definitions: type: integer status: allOf: - - $ref: '#/definitions/smsgateway.HealthStatus' + - $ref: "#/definitions/smsgateway.HealthStatus" description: |- Overall status of the application. It can be one of the following values: "pass", "warn", or "fail". @@ -106,32 +125,33 @@ definitions: type: object smsgateway.HealthStatus: enum: - - pass - - warn - - fail + - pass + - warn + - fail type: string x-enum-varnames: - - HealthStatusPass - - HealthStatusWarn - - HealthStatusFail + - HealthStatusPass + - HealthStatusWarn + - HealthStatusFail smsgateway.LimitPeriod: enum: - - Disabled - - PerMinute - - PerHour - - PerDay + - Disabled + - PerMinute + - PerHour + - PerDay type: string x-enum-varnames: - - Disabled - - PerMinute - - PerHour - - PerDay + - Disabled + - PerMinute + - PerHour + - PerDay smsgateway.LogEntry: properties: context: additionalProperties: type: string - description: Additional context information related to the log entry, typically + description: + Additional context information related to the log entry, typically including data relevant to the log event. type: object createdAt: @@ -144,26 +164,27 @@ definitions: description: A message describing the log event. type: string module: - description: The module or component of the system that generated the log + description: + The module or component of the system that generated the log entry. type: string priority: allOf: - - $ref: '#/definitions/smsgateway.LogEntryPriority' + - $ref: "#/definitions/smsgateway.LogEntryPriority" description: The priority level of the log entry. type: object smsgateway.LogEntryPriority: enum: - - DEBUG - - INFO - - WARN - - ERROR + - DEBUG + - INFO + - WARN + - ERROR type: string x-enum-varnames: - - LogEntryPriorityDebug - - LogEntryPriorityInfo - - LogEntryPriorityWarn - - LogEntryPriorityError + - LogEntryPriorityDebug + - LogEntryPriorityInfo + - LogEntryPriorityWarn + - LogEntryPriorityError smsgateway.Message: properties: id: @@ -183,7 +204,7 @@ definitions: phoneNumbers: description: Recipients (phone numbers) example: - - "79990001234" + - "79990001234" items: type: string maxItems: 100 @@ -191,9 +212,10 @@ definitions: type: array priority: allOf: - - $ref: '#/definitions/smsgateway.MessagePriority' + - $ref: "#/definitions/smsgateway.MessagePriority" default: 0 - description: Priority, messages with values greater than `99` will bypass + description: + Priority, messages with values greater than `99` will bypass limits and delays example: 0 maximum: 127 @@ -217,23 +239,23 @@ definitions: example: true type: boolean required: - - message - - phoneNumbers + - message + - phoneNumbers type: object smsgateway.MessagePriority: enum: - - -128 - - 0 - - 100 - - 127 + - -128 + - 0 + - 100 + - 127 type: integer x-enum-comments: PriorityBypassThreshold: Threshold at which messages bypass limits and delays x-enum-varnames: - - PriorityMinimum - - PriorityDefault - - PriorityBypassThreshold - - PriorityMaximum + - PriorityMinimum + - PriorityDefault + - PriorityBypassThreshold + - PriorityMaximum smsgateway.MessageState: properties: id: @@ -252,12 +274,12 @@ definitions: recipients: description: Recipients states items: - $ref: '#/definitions/smsgateway.RecipientState' + $ref: "#/definitions/smsgateway.RecipientState" minItems: 1 type: array state: allOf: - - $ref: '#/definitions/smsgateway.ProcessingState' + - $ref: "#/definitions/smsgateway.ProcessingState" description: State example: Pending states: @@ -266,8 +288,8 @@ definitions: description: History of states type: object required: - - recipients - - state + - recipients + - state type: object smsgateway.MessagesExportRequest: properties: @@ -285,9 +307,9 @@ definitions: example: "2024-01-01T23:59:59Z" type: string required: - - deviceId - - since - - until + - deviceId + - since + - until type: object smsgateway.MobileChangePasswordRequest: properties: @@ -301,15 +323,16 @@ definitions: minLength: 14 type: string required: - - currentPassword - - newPassword + - currentPassword + - newPassword type: object smsgateway.MobileDeviceResponse: properties: device: allOf: - - $ref: '#/definitions/smsgateway.Device' - description: Device information, empty if device is not registered on the + - $ref: "#/definitions/smsgateway.Device" + description: + Device information, empty if device is not registered on the server externalIp: description: External IP @@ -338,7 +361,7 @@ definitions: phoneNumbers: description: Recipients (phone numbers) example: - - "79990001234" + - "79990001234" items: type: string maxItems: 100 @@ -346,9 +369,10 @@ definitions: type: array priority: allOf: - - $ref: '#/definitions/smsgateway.MessagePriority' + - $ref: "#/definitions/smsgateway.MessagePriority" default: 0 - description: Priority, messages with values greater than `99` will bypass + description: + Priority, messages with values greater than `99` will bypass limits and delays example: 0 maximum: 127 @@ -372,8 +396,8 @@ definitions: example: true type: boolean required: - - message - - phoneNumbers + - message + - phoneNumbers type: object smsgateway.MobileRegisterRequest: properties: @@ -432,11 +456,11 @@ definitions: type: object smsgateway.ProcessingState: enum: - - Pending - - Processed - - Sent - - Delivered - - Failed + - Pending + - Processed + - Sent + - Delivered + - Failed type: string x-enum-comments: ProcessingStateDelivered: Delivered @@ -445,23 +469,23 @@ definitions: ProcessingStateProcessed: Processed (received by device) ProcessingStateSent: Sent x-enum-varnames: - - ProcessingStatePending - - ProcessingStateProcessed - - ProcessingStateSent - - ProcessingStateDelivered - - ProcessingStateFailed + - ProcessingStatePending + - ProcessingStateProcessed + - ProcessingStateSent + - ProcessingStateDelivered + - ProcessingStateFailed smsgateway.PushEventType: enum: - - MessageEnqueued - - WebhooksUpdated - - MessagesExportRequested - - SettingsUpdated + - MessageEnqueued + - WebhooksUpdated + - MessagesExportRequested + - SettingsUpdated type: string x-enum-varnames: - - PushMessageEnqueued - - PushWebhooksUpdated - - PushMessagesExportRequested - - PushSettingsUpdated + - PushMessageEnqueued + - PushWebhooksUpdated + - PushMessagesExportRequested + - PushSettingsUpdated smsgateway.PushNotification: properties: data: @@ -471,21 +495,21 @@ definitions: type: object event: allOf: - - $ref: '#/definitions/smsgateway.PushEventType' + - $ref: "#/definitions/smsgateway.PushEventType" default: MessageEnqueued description: The type of event. enum: - - MessageEnqueued - - WebhooksUpdated - - MessagesExportRequested - - SettingsUpdated + - MessageEnqueued + - WebhooksUpdated + - MessagesExportRequested + - SettingsUpdated example: MessageEnqueued token: description: The token of the device that receives the notification. example: PyDmBQZZXYmyxMwED8Fzy type: string required: - - token + - token type: object smsgateway.RecipientState: properties: @@ -501,17 +525,18 @@ definitions: type: string state: allOf: - - $ref: '#/definitions/smsgateway.ProcessingState' + - $ref: "#/definitions/smsgateway.ProcessingState" description: State example: Pending required: - - phoneNumber - - state + - phoneNumber + - state type: object smsgateway.SettingsEncryption: properties: passphrase: - description: Passphrase is the encryption passphrase. If nil or empty, encryption + description: + Passphrase is the encryption passphrase. If nil or empty, encryption is disabled. type: string type: object @@ -528,15 +553,15 @@ definitions: properties: limit_period: allOf: - - $ref: '#/definitions/smsgateway.LimitPeriod' + - $ref: "#/definitions/smsgateway.LimitPeriod" description: |- LimitPeriod defines the period for message sending limits. Valid values are "Disabled", "PerMinute", "PerHour", or "PerDay". enum: - - Disabled - - PerMinute - - PerHour - - PerDay + - Disabled + - PerMinute + - PerHour + - PerDay limit_value: description: |- LimitValue is the maximum number of messages allowed per limit period. @@ -563,14 +588,14 @@ definitions: type: integer sim_selection_mode: allOf: - - $ref: '#/definitions/smsgateway.SimSelectionMode' + - $ref: "#/definitions/smsgateway.SimSelectionMode" description: |- SimSelectionMode defines how SIM cards are selected for sending messages. Valid values are "OSDefault", "RoundRobin", or "Random". enum: - - OSDefault - - RoundRobin - - Random + - OSDefault + - RoundRobin + - Random type: object smsgateway.SettingsPing: properties: @@ -584,7 +609,8 @@ definitions: smsgateway.SettingsWebhooks: properties: internet_required: - description: InternetRequired indicates whether internet access is required + description: + InternetRequired indicates whether internet access is required for webhooks. type: boolean retry_count: @@ -599,25 +625,37 @@ definitions: type: object smsgateway.SimSelectionMode: enum: - - OSDefault - - RoundRobin - - Random + - OSDefault + - RoundRobin + - Random type: string x-enum-varnames: - - OSDefault - - RoundRobin - - Random + - OSDefault + - RoundRobin + - Random + smsgateway.TextMessage: + properties: + text: + description: Message text + example: Hello World! + maxLength: 65535 + minLength: 1 + type: string + required: + - text + type: object smsgateway.Webhook: properties: deviceId: - description: The unique identifier of the device the webhook is associated + description: + The unique identifier of the device the webhook is associated with. example: PyDmBQZZXYmyxMwED8Fzy maxLength: 21 type: string event: allOf: - - $ref: '#/definitions/smsgateway.WebhookEvent' + - $ref: "#/definitions/smsgateway.WebhookEvent" description: The type of event the webhook is triggered for. example: sms:received id: @@ -630,133 +668,135 @@ definitions: example: https://example.com/webhook type: string required: - - event - - url + - event + - url type: object smsgateway.WebhookEvent: enum: - - sms:received - - sms:sent - - sms:delivered - - sms:failed - - system:ping + - sms:received + - sms:sent + - sms:delivered + - sms:failed + - system:ping type: string x-enum-varnames: - - WebhookEventSmsReceived - - WebhookEventSmsSent - - WebhookEventSmsDelivered - - WebhookEventSmsFailed - - WebhookEventSystemPing + - WebhookEventSmsReceived + - WebhookEventSmsSent + - WebhookEventSmsDelivered + - WebhookEventSmsFailed + - WebhookEventSystemPing host: api.sms-gate.app info: contact: email: support@sms-gate.app name: SMSGate Support - description: This API provides programmatic access to sending SMS messages on Android + description: + This API provides programmatic access to sending SMS messages on Android devices. Features include sending SMS, checking message status, device management, webhook configuration, and system health checks. title: SMS Gateway for Androidâ„¢ API - version: '{APP_VERSION}' + version: "{APP_VERSION}" paths: /3rdparty/v1/devices: get: description: Returns list of registered devices produces: - - application/json + - application/json responses: "200": description: Device list schema: items: - $ref: '#/definitions/smsgateway.Device' + $ref: "#/definitions/smsgateway.Device" type: array "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: List devices tags: - - User - - Devices + - User + - Devices /3rdparty/v1/devices/{id}: delete: description: Removes device parameters: - - description: Device ID - in: path - name: id - required: true - type: string + - description: Device ID + in: path + name: id + required: true + type: string produces: - - application/json + - application/json responses: "204": description: Successfully removed "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "404": description: Device not found schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Remove device tags: - - User - - Devices + - User + - Devices /3rdparty/v1/health: get: description: Checks if service is healthy produces: - - application/json + - application/json responses: "200": description: Health check result schema: - $ref: '#/definitions/smsgateway.HealthResponse' + $ref: "#/definitions/smsgateway.HealthResponse" "500": description: Service is unhealthy schema: - $ref: '#/definitions/smsgateway.HealthResponse' + $ref: "#/definitions/smsgateway.HealthResponse" summary: Health check tags: - - System + - System /3rdparty/v1/inbox/export: post: consumes: - - application/json - description: Initiates process of inbox messages export via webhooks. For each + - application/json + description: + Initiates process of inbox messages export via webhooks. For each message the `sms:received` webhook will be triggered. The webhooks will be triggered without specific order. parameters: - - description: Export inbox request - in: body - name: request - required: true - schema: - $ref: '#/definitions/smsgateway.MessagesExportRequest' + - description: Export inbox request + in: body + name: request + required: true + schema: + $ref: "#/definitions/smsgateway.MessagesExportRequest" produces: - - application/json + - application/json responses: "202": description: Inbox export request accepted @@ -765,83 +805,86 @@ paths: "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Request inbox messages export tags: - - User - - Messages + - User + - Messages /3rdparty/v1/logs: get: description: Retrieve a list of log entries within a specified time range. parameters: - - description: The start of the time range for the logs to retrieve. Logs created - after this timestamp will be included. - format: date-time - in: query - name: from - type: string - - description: The end of the time range for the logs to retrieve. Logs created - before this timestamp will be included. - format: date-time - in: query - name: to - type: string + - description: + The start of the time range for the logs to retrieve. Logs created + after this timestamp will be included. + format: date-time + in: query + name: from + type: string + - description: + The end of the time range for the logs to retrieve. Logs created + before this timestamp will be included. + format: date-time + in: query + name: to + type: string produces: - - application/json + - application/json responses: "200": description: Log entries schema: items: - $ref: '#/definitions/smsgateway.LogEntry' + $ref: "#/definitions/smsgateway.LogEntry" type: array "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "501": description: Not implemented schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Get logs tags: - - System - - Logs + - System + - Logs /3rdparty/v1/messages: post: consumes: - - application/json - description: Enqueues message for sending. If multiple devices are registered, + - application/json + description: + Enqueues message for sending. If multiple devices are registered, it will be sent via a random one parameters: - - description: Skip phone validation - in: query - name: skipPhoneValidation - type: boolean - - description: Send message request - in: body - name: request - required: true - schema: - $ref: '#/definitions/smsgateway.Message' + - description: Skip phone validation + in: query + name: skipPhoneValidation + type: boolean + - description: Send message request + in: body + name: request + required: true + schema: + $ref: "#/definitions/smsgateway.Message" produces: - - application/json + - application/json responses: "202": description: Message enqueued @@ -850,100 +893,100 @@ paths: description: Get message state URL type: string schema: - $ref: '#/definitions/smsgateway.MessageState' + $ref: "#/definitions/smsgateway.MessageState" "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "409": description: Message with such ID already exists schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Enqueue message tags: - - User - - Messages + - User + - Messages /3rdparty/v1/messages/{id}: get: description: Returns message state by ID parameters: - - description: Message ID - in: path - name: id - required: true - type: string + - description: Message ID + in: path + name: id + required: true + type: string produces: - - application/json + - application/json responses: "200": description: Message state schema: - $ref: '#/definitions/smsgateway.MessageState' + $ref: "#/definitions/smsgateway.MessageState" "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Get message state tags: - - User - - Messages + - User + - Messages /3rdparty/v1/settings: get: description: Returns settings for a specific user produces: - - application/json + - application/json responses: "200": description: Settings schema: - $ref: '#/definitions/smsgateway.DeviceSettings' + $ref: "#/definitions/smsgateway.DeviceSettings" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Get settings tags: - - User - - Settings + - User + - Settings patch: consumes: - - application/json + - application/json description: Partially updates settings for a specific user parameters: - - description: Settings - in: body - name: request - required: true - schema: - $ref: '#/definitions/smsgateway.DeviceSettings' + - description: Settings + in: body + name: request + required: true + schema: + $ref: "#/definitions/smsgateway.DeviceSettings" produces: - - application/json + - application/json responses: "200": description: Settings updated @@ -952,34 +995,34 @@ paths: "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Partially update settings tags: - - User - - Settings + - User + - Settings put: consumes: - - application/json + - application/json description: Replaces settings parameters: - - description: Settings - in: body - name: request - required: true - schema: - $ref: '#/definitions/smsgateway.DeviceSettings' + - description: Settings + in: body + name: request + required: true + schema: + $ref: "#/definitions/smsgateway.DeviceSettings" produces: - - application/json + - application/json responses: "200": description: Settings updated @@ -988,95 +1031,96 @@ paths: "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Replace settings tags: - - User - - Settings + - User + - Settings /3rdparty/v1/webhooks: get: description: Returns list of registered webhooks produces: - - application/json + - application/json responses: "200": description: Webhook list schema: items: - $ref: '#/definitions/smsgateway.Webhook' + $ref: "#/definitions/smsgateway.Webhook" type: array "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: List webhooks tags: - - User - - Webhooks + - User + - Webhooks post: consumes: - - application/json - description: Registers webhook. If webhook with same ID already exists, it will + - application/json + description: + Registers webhook. If webhook with same ID already exists, it will be replaced parameters: - - description: Webhook - in: body - name: request - required: true - schema: - $ref: '#/definitions/smsgateway.Webhook' + - description: Webhook + in: body + name: request + required: true + schema: + $ref: "#/definitions/smsgateway.Webhook" produces: - - application/json + - application/json responses: "201": description: Created schema: - $ref: '#/definitions/smsgateway.Webhook' + $ref: "#/definitions/smsgateway.Webhook" "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Register webhook tags: - - User - - Webhooks + - User + - Webhooks /3rdparty/v1/webhooks/{id}: delete: description: Deletes webhook parameters: - - description: Webhook ID - in: path - name: id - required: true - type: string + - description: Webhook ID + in: path + name: id + required: true + type: string produces: - - application/json + - application/json responses: "204": description: Webhook deleted @@ -1085,304 +1129,305 @@ paths: "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Delete webhook tags: - - User - - Webhooks + - User + - Webhooks /mobile/v1/device: get: description: Returns device information produces: - - application/json + - application/json responses: "200": description: Device information schema: - $ref: '#/definitions/smsgateway.MobileDeviceResponse' + $ref: "#/definitions/smsgateway.MobileDeviceResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" summary: Get device information tags: - - Device + - Device patch: consumes: - - application/json + - application/json description: Updates push token for device parameters: - - description: Device update request - in: body - name: request - required: true - schema: - $ref: '#/definitions/smsgateway.MobileUpdateRequest' + - description: Device update request + in: body + name: request + required: true + schema: + $ref: "#/definitions/smsgateway.MobileUpdateRequest" responses: "204": description: Successfully updated "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "403": description: Forbidden (wrong device ID) schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - MobileToken: [] + - MobileToken: [] summary: Update device tags: - - Device + - Device post: consumes: - - application/json - description: Registers new device for new or existing user. Returns user credentials + - application/json + description: + Registers new device for new or existing user. Returns user credentials only for new users parameters: - - description: Device registration request - in: body - name: request - required: true - schema: - $ref: '#/definitions/smsgateway.MobileRegisterRequest' + - description: Device registration request + in: body + name: request + required: true + schema: + $ref: "#/definitions/smsgateway.MobileRegisterRequest" produces: - - application/json + - application/json responses: "201": description: Device registered schema: - $ref: '#/definitions/smsgateway.MobileRegisterResponse' + $ref: "#/definitions/smsgateway.MobileRegisterResponse" "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized (private mode only) schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "429": description: Too many requests schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] - - UserCode: [] - - ServerKey: [] + - ApiAuth: [] + - UserCode: [] + - ServerKey: [] summary: Register device tags: - - Device + - Device /mobile/v1/message: get: consumes: - - application/json + - application/json description: Returns list of pending messages produces: - - application/json + - application/json responses: "200": description: List of pending messages schema: items: - $ref: '#/definitions/smsgateway.MobileMessage' + $ref: "#/definitions/smsgateway.MobileMessage" type: array "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - MobileToken: [] + - MobileToken: [] summary: Get messages for sending tags: - - Device - - Messages + - Device + - Messages patch: consumes: - - application/json + - application/json description: Updates message state parameters: - - description: New message state - in: body - name: request - required: true - schema: - items: - $ref: '#/definitions/smsgateway.MessageState' - type: array + - description: New message state + in: body + name: request + required: true + schema: + items: + $ref: "#/definitions/smsgateway.MessageState" + type: array produces: - - application/json + - application/json responses: "204": description: Successfully updated "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - MobileToken: [] + - MobileToken: [] summary: Update message state tags: - - Device - - Messages + - Device + - Messages /mobile/v1/settings: get: description: Returns settings for a device produces: - - application/json + - application/json responses: "200": description: Settings schema: - $ref: '#/definitions/smsgateway.DeviceSettings' + $ref: "#/definitions/smsgateway.DeviceSettings" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - MobileToken: [] + - MobileToken: [] summary: Get settings tags: - - Device - - Settings + - Device + - Settings /mobile/v1/user/code: get: consumes: - - application/json + - application/json description: Returns one-time code for device registration produces: - - application/json + - application/json responses: "200": description: User code schema: - $ref: '#/definitions/smsgateway.MobileUserCodeResponse' + $ref: "#/definitions/smsgateway.MobileUserCodeResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - ApiAuth: [] + - ApiAuth: [] summary: Get one-time code for device registration tags: - - Device + - Device /mobile/v1/user/password: patch: consumes: - - application/json + - application/json description: Changes the user's password parameters: - - description: Password change request - in: body - name: request - required: true - schema: - $ref: '#/definitions/smsgateway.MobileChangePasswordRequest' + - description: Password change request + in: body + name: request + required: true + schema: + $ref: "#/definitions/smsgateway.MobileChangePasswordRequest" produces: - - application/json + - application/json responses: "204": description: Password changed successfully "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - MobileToken: [] + - MobileToken: [] summary: Change password tags: - - Device + - Device /mobile/v1/webhooks: get: description: Returns list of registered webhooks for device produces: - - application/json + - application/json responses: "200": description: Webhook list schema: items: - $ref: '#/definitions/smsgateway.Webhook' + $ref: "#/definitions/smsgateway.Webhook" type: array "401": description: Unauthorized schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" security: - - MobileToken: [] + - MobileToken: [] summary: List webhooks tags: - - Device - - Webhooks + - Device + - Webhooks /upstream/v1/push: post: consumes: - - application/json + - application/json description: Enqueues notifications for sending to devices parameters: - - description: Push request - in: body - name: request - required: true - schema: - items: - $ref: '#/definitions/smsgateway.PushNotification' - type: array + - description: Push request + in: body + name: request + required: true + schema: + items: + $ref: "#/definitions/smsgateway.PushNotification" + type: array produces: - - application/json + - application/json responses: "202": description: Notification enqueued "400": description: Invalid request schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "429": description: Too many requests schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" "500": description: Internal server error schema: - $ref: '#/definitions/smsgateway.ErrorResponse' + $ref: "#/definitions/smsgateway.ErrorResponse" summary: Send push notifications tags: - - Upstream + - Upstream schemes: -- https + - https securityDefinitions: ApiAuth: type: basic