diff --git a/Makefile b/Makefile index 39091136..304af6eb 100644 --- a/Makefile +++ b/Makefile @@ -8,11 +8,11 @@ ifeq ($(OS),Windows_NT) endif .PHONY: \ - all fmt lint test coverage benchmark deps release clean help \ + all fmt lint test coverage benchmark deps gen release clean help \ init init-dev ngrok air db-upgrade db-upgrade-raw run test-e2e build install \ docker-build docker docker-dev docker-clean -all: fmt lint coverage ## Run all tests and checks +all: gen fmt lint coverage ## Run all tests and checks fmt: ## Format the code golangci-lint fmt @@ -33,6 +33,9 @@ benchmark: ## Run benchmarks deps: ## Install dependencies go mod download +gen: ## Generate code + go generate ./... + release: ## Create release DOCKER_REGISTRY=$(registry_name) RELEASE_ID=0 goreleaser release --snapshot --clean diff --git a/README.md b/README.md index 6ecdffa5..45ad68d4 100644 --- a/README.md +++ b/README.md @@ -162,19 +162,21 @@ Authorization: Bearer The following scopes are available for token generation: -- `messages:send` - Send SMS messages -- `messages:read` - Read individual messages -- `messages:list` - List messages -- `messages:export` - Export messages -- `devices:list` - List connected devices - `devices:delete` - Delete devices -- `webhooks:list` - List webhooks -- `webhooks:write` - Create and update webhooks -- `webhooks:delete` - Delete webhooks +- `devices:list` - List connected devices +- `inbox:list` - List incoming messages with filters +- `inbox:read` - Read incoming messages +- `logs:read` - Read server logs +- `messages:export` - Export messages +- `messages:list` - List messages +- `messages:read` - Read individual messages +- `messages:send` - Send SMS messages - `settings:read` - Read server settings - `settings:write` - Modify server settings -- `logs:read` - Read server logs - `tokens:manage` - Generate and revoke tokens +- `webhooks:delete` - Delete webhooks +- `webhooks:list` - List webhooks +- `webhooks:write` - Create and update webhooks ## Contributing diff --git a/api/local.http b/api/local.http index e2e70202..df48e7a5 100644 --- a/api/local.http +++ b/api/local.http @@ -193,4 +193,24 @@ Content-Type: application/json ### DELETE {{localUrl}}/auth/token/{{jti}} HTTP/1.1 -Authorization: Basic {{localCredentials}} \ No newline at end of file +Authorization: Basic {{localCredentials}} + +### +# @name getInbox +@inboxMessageId={{getInbox.response.body.$.0.id}} +GET {{localUrl}}/inbox HTTP/1.1 +Authorization: Basic {{localCredentials}} + +### +GET {{localUrl}}/inbox?type=SMS&limit=1&offset={{$randomInt 0 100}}&from=2026-01-01T00:00:00.000Z&to=2026-12-31T23:59:59Z HTTP/1.1 +Authorization: Basic {{localCredentials}} + +### +POST {{localUrl}}/inbox/refresh HTTP/1.1 +Authorization: Basic {{localCredentials}} +Content-Type: application/json + +{ + "since": "2026-01-01T00:00:00.000Z", + "until": "2026-12-31T23:59:59Z" +} \ No newline at end of file diff --git a/go.mod b/go.mod index c269bfb6..8cbd2000 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.0 require ( firebase.google.com/go/v4 v4.19.0 - github.com/android-sms-gateway/client-go v1.12.3 + github.com/android-sms-gateway/client-go v1.12.4 github.com/ansrivas/fiberprometheus/v2 v2.6.1 github.com/capcom6/go-helpers v0.3.0 github.com/capcom6/go-infra-fx v0.5.2 diff --git a/go.sum b/go.sum index 77a87970..810ef07b 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,10 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/android-sms-gateway/client-go v1.12.3 h1:ToyjxJ1JxP9oKXsAMoBFT8qNFK8f0s3EYrOyASks27g= -github.com/android-sms-gateway/client-go v1.12.3/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= +github.com/android-sms-gateway/client-go v1.12.4-0.20260411073238-b7c27fa612b2 h1:0UyeZIlv7VxQn6DHkwcWRmkvE34/Sj+9to0ZtizGTYY= +github.com/android-sms-gateway/client-go v1.12.4-0.20260411073238-b7c27fa612b2/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= +github.com/android-sms-gateway/client-go v1.12.4 h1:umYoIEFQQHFOC4RvqTGorzScicYwzuyRP5Ep6sGwSHs= +github.com/android-sms-gateway/client-go v1.12.4/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.6.1 h1:wac3pXaE6BYYTF04AC6K0ktk6vCD+MnDOJZ3SK66kXM= diff --git a/internal/sms-gateway/app.go b/internal/sms-gateway/app.go index 4a6a1cfa..8ac09f02 100644 --- a/internal/sms-gateway/app.go +++ b/internal/sms-gateway/app.go @@ -7,6 +7,7 @@ import ( appconfig "github.com/android-sms-gateway/server/internal/config" "github.com/android-sms-gateway/server/internal/sms-gateway/cache" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers" + "github.com/android-sms-gateway/server/internal/sms-gateway/inbox" "github.com/android-sms-gateway/server/internal/sms-gateway/jwt" "github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth" appdb "github.com/android-sms-gateway/server/internal/sms-gateway/modules/db" @@ -63,6 +64,7 @@ func Module() fx.Option { online.Module(), jwt.Module(), otp.Module(), + inbox.Module(), ) } diff --git a/internal/sms-gateway/handlers/3rdparty.go b/internal/sms-gateway/handlers/3rdparty.go index d6a04080..f918c7da 100644 --- a/internal/sms-gateway/handlers/3rdparty.go +++ b/internal/sms-gateway/handlers/3rdparty.go @@ -3,6 +3,7 @@ package handlers import ( "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/devices" + "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/inbox" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/logs" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/messages" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/jwtauth" @@ -28,6 +29,7 @@ type thirdPartyHandler struct { webhooksHandler *webhooks.ThirdPartyController devicesHandler *devices.ThirdPartyController settingsHandler *settings.ThirdPartyController + inboxHandler *inbox.ThirdPartyController logsHandler *logs.ThirdPartyController authHandler *thirdparty.AuthHandler } @@ -41,6 +43,7 @@ func newThirdPartyHandler( webhooksHandler *webhooks.ThirdPartyController, devicesHandler *devices.ThirdPartyController, settingsHandler *settings.ThirdPartyController, + inboxHandler *inbox.ThirdPartyController, logsHandler *logs.ThirdPartyController, authHandler *thirdparty.AuthHandler, @@ -61,6 +64,7 @@ func newThirdPartyHandler( webhooksHandler: webhooksHandler, devicesHandler: devicesHandler, settingsHandler: settingsHandler, + inboxHandler: inboxHandler, logsHandler: logsHandler, authHandler: authHandler, } @@ -86,6 +90,7 @@ func (h *thirdPartyHandler) Register(router fiber.Router) { h.devicesHandler.Register(router.Group("/devices")) h.settingsHandler.Register(router.Group("/settings")) + h.inboxHandler.Register(router.Group("/inbox")) h.webhooksHandler.Register(router.Group("/webhooks")) diff --git a/internal/sms-gateway/handlers/inbox/3rdparty.go b/internal/sms-gateway/handlers/inbox/3rdparty.go new file mode 100644 index 00000000..8cf5a9c1 --- /dev/null +++ b/internal/sms-gateway/handlers/inbox/3rdparty.go @@ -0,0 +1,84 @@ +package inbox + +import ( + "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base" + "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/permissions" + "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth" + "github.com/android-sms-gateway/server/internal/sms-gateway/inbox" + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +type ThirdPartyController struct { + base.Handler + + inboxSvc *inbox.Service +} + +func NewThirdPartyController( + inboxSvc *inbox.Service, + logger *zap.Logger, + validator *validator.Validate, +) *ThirdPartyController { + return &ThirdPartyController{ + Handler: base.Handler{ + Logger: logger, + Validator: validator, + }, + + inboxSvc: inboxSvc, + } +} + +func (h *ThirdPartyController) Register(router fiber.Router) { + router.Get("", permissions.RequireScope(ScopeList), userauth.WithUserID(h.list)) + router.Post("/refresh", permissions.RequireScope(ScopeRefresh), userauth.WithUserID(h.refresh)) +} + +// @Summary Get incoming messages +// @Description Retrieves incoming messages with filtering and pagination. +// @Security ApiAuth +// @Security JWTAuth +// @Tags User, Inbox +// @Produce json +// @Param type query string false "Filter incoming messages by type" Enums(SMS,DATA_SMS,MMS,MMS_DOWNLOADED) +// @Param limit query int false "Maximum number of messages to return" minimum(1) maximum(500) default(50) +// @Param offset query int false "Number of messages to skip" minimum(0) default(0) +// @Param from query string false "Start of date range (ISO 8601)" Format(date-time) +// @Param to query string false "End of date range (ISO 8601)" Format(date-time) +// @Param deviceId query string false "Device ID" +// @Success 200 {array} smsgateway.IncomingMessage "A list of incoming messages" +// @Header 200 {integer} X-Total-Count "Total number of items available" +// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request" +// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized" +// @Failure 403 {object} smsgateway.ErrorResponse "Forbidden" +// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error" +// @Failure 501 {object} smsgateway.ErrorResponse "Not implemented" +// @Router /3rdparty/v1/inbox [get] +// +// Get incoming messages. +func (h *ThirdPartyController) list(_ string, _ *fiber.Ctx) error { + return fiber.NewError(fiber.StatusNotImplemented, "Inbox API is not implemented yet") +} + +// @Summary Request inbox messages refresh +// @Description Refreshes inbox messages. Webhooks will not be triggered. +// @Security ApiAuth +// @Security JWTAuth +// @Tags User, Inbox +// @Accept json +// @Produce json +// @Param request body smsgateway.MessagesExportRequest true "Export inbox request" +// @Success 202 {object} object "Inbox refresh request accepted" +// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request" +// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized" +// @Failure 403 {object} smsgateway.ErrorResponse "Forbidden" +// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error" +// @Failure 501 {object} smsgateway.ErrorResponse "Not implemented" +// @Router /3rdparty/v1/inbox/refresh [post] +// +// Request inbox refresh. +func (h *ThirdPartyController) refresh(_ string, _ *fiber.Ctx) error { + return fiber.NewError(fiber.StatusNotImplemented, "Inbox API is not implemented yet") +} diff --git a/internal/sms-gateway/handlers/inbox/permissions.go b/internal/sms-gateway/handlers/inbox/permissions.go new file mode 100644 index 00000000..ea4932f7 --- /dev/null +++ b/internal/sms-gateway/handlers/inbox/permissions.go @@ -0,0 +1,6 @@ +package inbox + +const ( + ScopeList = "inbox:list" + ScopeRefresh = "inbox:refresh" +) diff --git a/internal/sms-gateway/handlers/messages/3rdparty.go b/internal/sms-gateway/handlers/messages/3rdparty.go index 0de1297d..d32a1347 100644 --- a/internal/sms-gateway/handlers/messages/3rdparty.go +++ b/internal/sms-gateway/handlers/messages/3rdparty.go @@ -11,6 +11,7 @@ import ( "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/converters" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/permissions" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth" + "github.com/android-sms-gateway/server/internal/sms-gateway/inbox" "github.com/android-sms-gateway/server/internal/sms-gateway/modules/devices" "github.com/android-sms-gateway/server/internal/sms-gateway/modules/messages" "github.com/capcom6/go-helpers/slices" @@ -29,6 +30,7 @@ type thirdPartyControllerParams struct { MessagesSvc *messages.Service DevicesSvc *devices.Service + InboxSvc *inbox.Service Validator *validator.Validate Logger *zap.Logger @@ -39,6 +41,7 @@ type ThirdPartyController struct { messagesSvc *messages.Service devicesSvc *devices.Service + inboxSvc *inbox.Service } func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyController { @@ -47,8 +50,10 @@ func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyContr Logger: params.Logger, Validator: params.Validator, }, + messagesSvc: params.MessagesSvc, devicesSvc: params.DevicesSvc, + inboxSvc: params.InboxSvc, } } @@ -179,6 +184,7 @@ func (h *ThirdPartyController) post(userID string, c *fiber.Ctx) error { // @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) // @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" // @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized" // @Failure 403 {object} smsgateway.ErrorResponse "Forbidden" @@ -257,19 +263,8 @@ func (h *ThirdPartyController) postInboxExport(userID string, c *fiber.Ctx) erro return fiber.NewError(fiber.StatusBadRequest, err.Error()) } - device, err := h.devicesSvc.Get(userID, devices.WithID(req.DeviceID)) - if err != nil { - if errors.Is(err, devices.ErrNotFound) { - return fiber.NewError(fiber.StatusBadRequest, "Invalid device ID") - } - - h.Logger.Error("failed to get device", zap.Error(err), zap.String("user_id", userID)) - return fiber.NewError(fiber.StatusInternalServerError, "failed to get device") - } - - if expErr := h.messagesSvc.ExportInbox(device, req.Since, req.Until); expErr != nil { - h.Logger.Error("failed to export inbox", zap.Error(expErr), zap.String("user_id", userID)) - return fiber.NewError(fiber.StatusInternalServerError, "failed to export inbox") + if err := h.inboxSvc.Refresh(userID, &req.DeviceID, req.Since, req.Until); err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } return c.SendStatus(fiber.StatusAccepted) diff --git a/internal/sms-gateway/handlers/module.go b/internal/sms-gateway/handlers/module.go index 4be3dcb9..6c3ca086 100644 --- a/internal/sms-gateway/handlers/module.go +++ b/internal/sms-gateway/handlers/module.go @@ -3,6 +3,7 @@ package handlers import ( "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/devices" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/events" + "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/inbox" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/logs" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/messages" "github.com/android-sms-gateway/server/internal/sms-gateway/handlers/settings" @@ -34,6 +35,7 @@ func Module() fx.Option { devices.NewThirdPartyController, settings.NewThirdPartyController, settings.NewMobileController, + inbox.NewThirdPartyController, logs.NewThirdPartyController, events.NewMobileController, fx.Private, diff --git a/internal/sms-gateway/inbox/module.go b/internal/sms-gateway/inbox/module.go new file mode 100644 index 00000000..1feefbb8 --- /dev/null +++ b/internal/sms-gateway/inbox/module.go @@ -0,0 +1,16 @@ +package inbox + +import ( + "github.com/go-core-fx/logger" + "go.uber.org/fx" +) + +func Module() fx.Option { + return fx.Module( + "inbox", + logger.WithNamedLogger("inbox"), + fx.Provide( + New, + ), + ) +} diff --git a/internal/sms-gateway/inbox/service.go b/internal/sms-gateway/inbox/service.go new file mode 100644 index 00000000..6f0ba9c6 --- /dev/null +++ b/internal/sms-gateway/inbox/service.go @@ -0,0 +1,33 @@ +package inbox + +import ( + "fmt" + "time" + + "github.com/android-sms-gateway/server/internal/sms-gateway/modules/events" + "go.uber.org/zap" +) + +type Service struct { + eventsSvc *events.Service + + logger *zap.Logger +} + +func New(eventsSvc *events.Service, logger *zap.Logger) *Service { + return &Service{ + eventsSvc: eventsSvc, + + logger: logger, + } +} + +func (s *Service) Refresh(userID string, deviceID *string, since, until time.Time) error { + event := events.NewMessagesExportRequestedEvent(since, until) + + if err := s.eventsSvc.Notify(userID, deviceID, event); err != nil { + return fmt.Errorf("failed to notify device: %w", err) + } + + return nil +} diff --git a/internal/sms-gateway/modules/messages/service.go b/internal/sms-gateway/modules/messages/service.go index 230d42a3..863baf83 100644 --- a/internal/sms-gateway/modules/messages/service.go +++ b/internal/sms-gateway/modules/messages/service.go @@ -292,16 +292,6 @@ func (s *Service) prepareMessage( return msg, nil } -func (s *Service) ExportInbox(device models.Device, since, until time.Time) error { - event := events.NewMessagesExportRequestedEvent(since, until) - - if err := s.eventsSvc.Notify(device.UserID, &device.ID, event); err != nil { - return fmt.Errorf("failed to notify device: %w", err) - } - - return nil -} - /////////////////////////////////////////////////////////////////////////////// func (s *Service) recipientsStateToModel(input []smsgateway.RecipientState, hash bool) []messageRecipientModel { diff --git a/internal/sms-gateway/openapi/docs.go b/internal/sms-gateway/openapi/docs.go index 40e57222..bb8f1f35 100644 --- a/internal/sms-gateway/openapi/docs.go +++ b/internal/sms-gateway/openapi/docs.go @@ -352,6 +352,198 @@ const docTemplate = `{ } } }, + "/3rdparty/v1/inbox": { + "get": { + "security": [ + { + "ApiAuth": [] + }, + { + "JWTAuth": [] + } + ], + "description": "Retrieves incoming messages with filtering and pagination.", + "produces": [ + "application/json" + ], + "tags": [ + "User", + "Inbox" + ], + "summary": "Get incoming messages", + "parameters": [ + { + "enum": [ + "SMS", + "DATA_SMS", + "MMS", + "MMS_DOWNLOADED" + ], + "type": "string", + "description": "Filter incoming messages by type", + "name": "type", + "in": "query" + }, + { + "maximum": 500, + "minimum": 1, + "type": "integer", + "default": 50, + "description": "Maximum number of messages to return", + "name": "limit", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "default": 0, + "description": "Number of messages to skip", + "name": "offset", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "Start of date range (ISO 8601)", + "name": "from", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "description": "End of date range (ISO 8601)", + "name": "to", + "in": "query" + }, + { + "type": "string", + "description": "Device ID", + "name": "deviceId", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A list of incoming messages", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/smsgateway.IncomingMessage" + } + }, + "headers": { + "X-Total-Count": { + "type": "integer", + "description": "Total number of items available" + } + } + }, + "400": { + "description": "Invalid request", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "501": { + "description": "Not implemented", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + } + } + } + }, + "/3rdparty/v1/inbox/refresh": { + "post": { + "security": [ + { + "ApiAuth": [] + }, + { + "JWTAuth": [] + } + ], + "description": "Refreshes inbox messages. Webhooks will not be triggered.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User", + "Inbox" + ], + "summary": "Request inbox messages refresh", + "parameters": [ + { + "description": "Export inbox request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/smsgateway.MessagesExportRequest" + } + } + ], + "responses": { + "202": { + "description": "Inbox refresh request accepted", + "schema": { + "type": "object" + } + }, + "400": { + "description": "Invalid request", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "501": { + "description": "Not implemented", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + } + } + } + }, "/3rdparty/v1/logs": { "get": { "security": [ @@ -500,6 +692,12 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/smsgateway.MessageState" } + }, + "headers": { + "X-Total-Count": { + "type": "integer", + "description": "Total number of items available" + } } }, "400": { @@ -1469,6 +1667,85 @@ const docTemplate = `{ "HealthStatusFail" ] }, + "smsgateway.IncomingMessage": { + "type": "object", + "required": [ + "contentPreview", + "createdAt", + "id", + "sender", + "type" + ], + "properties": { + "contentPreview": { + "description": "Message body preview or metadata", + "type": "string", + "example": "Hello World!" + }, + "createdAt": { + "description": "Message received timestamp", + "type": "string", + "format": "date-time", + "example": "2020-01-01T00:00:00Z" + }, + "id": { + "description": "Incoming message ID", + "type": "string", + "example": "PyDmBQZZXYmyxMwED8Fzy" + }, + "recipient": { + "description": "Recipient phone number on the device", + "type": "string", + "example": "+79990001234" + }, + "sender": { + "description": "Incoming sender phone number", + "type": "string", + "example": "+79990001234" + }, + "simNumber": { + "description": "SIM slot number", + "type": "integer", + "example": 1 + }, + "type": { + "description": "Message type", + "allOf": [ + { + "$ref": "#/definitions/smsgateway.IncomingMessageType" + } + ], + "example": "SMS" + } + } + }, + "smsgateway.IncomingMessageType": { + "type": "string", + "enum": [ + "SMS", + "DATA_SMS", + "MMS", + "MMS_DOWNLOADED" + ], + "x-enum-comments": { + "IncomingMessageTypeDataSMS": "Data SMS message", + "IncomingMessageTypeMMS": "MMS message", + "IncomingMessageTypeMmsDownloaded": "Downloaded MMS message", + "IncomingMessageTypeSMS": "SMS message" + }, + "x-enum-descriptions": [ + "SMS message", + "Data SMS message", + "MMS message", + "Downloaded MMS message" + ], + "x-enum-varnames": [ + "IncomingMessageTypeSMS", + "IncomingMessageTypeDataSMS", + "IncomingMessageTypeMMS", + "IncomingMessageTypeMmsDownloaded" + ] + }, "smsgateway.LimitPeriod": { "type": "string", "enum": [