From 70297a50071edc097125c2c34a471114109de7a7 Mon Sep 17 00:00:00 2001 From: Aleksandr Soloshenko Date: Tue, 31 Dec 2024 09:57:41 +0700 Subject: [PATCH] [feature] add inbox export endpoint --- go.mod | 2 +- go.sum | 4 + .../sms-gateway/handlers/messages/3rdparty.go | 38 ++++++++ .../sms-gateway/modules/messages/service.go | 11 +++ internal/sms-gateway/modules/push/service.go | 7 +- internal/sms-gateway/modules/push/types.go | 17 +++- pkg/swagger/docs/requests.http | 17 +++- pkg/swagger/docs/swagger.json | 97 ++++++++++++++++++- pkg/swagger/docs/swagger.yaml | 66 ++++++++++++- 9 files changed, 242 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 9a9af32..3b75b43 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.0 require ( firebase.google.com/go/v4 v4.12.1 - github.com/android-sms-gateway/client-go v1.2.0 + github.com/android-sms-gateway/client-go v1.2.1-0.20241231042455-ce468dd89fdb github.com/ansrivas/fiberprometheus/v2 v2.6.1 github.com/capcom6/go-helpers v0.0.0-20240521035631-865ee2879fa3 github.com/capcom6/go-infra-fx v0.2.0 diff --git a/go.sum b/go.sum index 075d2c0..84b411f 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,10 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/android-sms-gateway/client-go v1.2.0 h1:P02e/Nm2XY6gpxVQVZiaxh1ZfInVkwfOLzz8Mp/1dy0= github.com/android-sms-gateway/client-go v1.2.0/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= +github.com/android-sms-gateway/client-go v1.2.1-0.20241231005427-53ab5bf34e4f h1:BfLaSqzXTRwAiXafZO/kA7kK8uPEXNB/32iHfSJlOSE= +github.com/android-sms-gateway/client-go v1.2.1-0.20241231005427-53ab5bf34e4f/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= +github.com/android-sms-gateway/client-go v1.2.1-0.20241231042455-ce468dd89fdb h1:c3ll8h375G/oL4Qzexo35XBxHrw9HgGOqmxK6CPX5Bg= +github.com/android-sms-gateway/client-go v1.2.1-0.20241231042455-ce468dd89fdb/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= diff --git a/internal/sms-gateway/handlers/messages/3rdparty.go b/internal/sms-gateway/handlers/messages/3rdparty.go index fc35302..6b5dae2 100644 --- a/internal/sms-gateway/handlers/messages/3rdparty.go +++ b/internal/sms-gateway/handlers/messages/3rdparty.go @@ -126,9 +126,47 @@ func (h *ThirdPartyController) get(user models.User, c *fiber.Ctx) error { return c.JSON(state) } +// @Summary Request inbox messages export +// @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. +// @Security ApiAuth +// @Tags User, Messages +// @Accept json +// @Produce json +// @Param request body smsgateway.MessagesExportRequest true "Export inbox request" +// @Success 202 {object} object "Inbox export request accepted" +// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request" +// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized" +// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error" +// @Router /3rdparty/v1/inbox/export [post] +// +// Export inbox +func (h *ThirdPartyController) postInboxExport(user models.User, c *fiber.Ctx) error { + req := smsgateway.MessagesExportRequest{} + if err := h.BodyParserValidator(c, &req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + device, err := h.devicesSvc.Get(devices.WithUserID(user.ID), devices.WithID(req.DeviceID)) + if err != nil { + if errors.Is(err, devices.ErrNotFound) { + return fiber.NewError(fiber.StatusBadRequest, "Invalid device ID") + } + + return err + } + + if err := h.messagesSvc.ExportInbox(device, req.Since, req.Until); err != nil { + return err + } + + return c.SendStatus(fiber.StatusAccepted) +} + func (h *ThirdPartyController) Register(router fiber.Router) { router.Post("", auth.WithUser(h.post)) router.Get(":id", auth.WithUser(h.get)) + + router.Post("inbox/export", auth.WithUser(h.postInboxExport)) } func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyController { diff --git a/internal/sms-gateway/modules/messages/service.go b/internal/sms-gateway/modules/messages/service.go index bb33d2f..821dc72 100644 --- a/internal/sms-gateway/modules/messages/service.go +++ b/internal/sms-gateway/modules/messages/service.go @@ -3,6 +3,7 @@ package messages import ( "context" "crypto/sha256" + "errors" "fmt" "sync" "time" @@ -244,6 +245,16 @@ func (s *Service) Enqeue(device models.Device, message smsgateway.Message, opts return state, nil } +func (s *Service) ExportInbox(device models.Device, since, until time.Time) error { + if device.PushToken == nil { + return errors.New("no push token") + } + + event := push.NewMessagesExportRequestedEvent(since, until) + + return s.pushSvc.Enqueue(*device.PushToken, event) +} + func (s *Service) Clean(ctx context.Context) error { //TODO: use delete queue to optimize deletion n, err := s.messages.removeProcessed(ctx, time.Now().Add(-s.config.ProcessedLifetime)) diff --git a/internal/sms-gateway/modules/push/service.go b/internal/sms-gateway/modules/push/service.go index 14a8308..3adbf90 100644 --- a/internal/sms-gateway/modules/push/service.go +++ b/internal/sms-gateway/modules/push/service.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/android-sms-gateway/server/internal/sms-gateway/modules/push/domain" "github.com/android-sms-gateway/server/pkg/types/cache" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -36,7 +37,7 @@ type Service struct { client client - cache *cache.Cache[Event] + cache *cache.Cache[domain.Event] enqueuedCounter *prometheus.CounterVec @@ -61,7 +62,7 @@ func New(params Params) *Service { return &Service{ config: params.Config, client: params.Client, - cache: cache.New[Event](cache.Config{}), + cache: cache.New[domain.Event](cache.Config{}), enqueuedCounter: enqueuedCounter, logger: params.Logger, } @@ -83,7 +84,7 @@ func (s *Service) Run(ctx context.Context) { } // Enqueue adds the data to the cache and immediately sends all messages if the debounce is 0. -func (s *Service) Enqueue(token string, event *Event) error { +func (s *Service) Enqueue(token string, event *domain.Event) error { if err := s.cache.Set(token, *event); err != nil { return fmt.Errorf("can't add message to cache: %w", err) } diff --git a/internal/sms-gateway/modules/push/types.go b/internal/sms-gateway/modules/push/types.go index 55b24ac..8b85e3f 100644 --- a/internal/sms-gateway/modules/push/types.go +++ b/internal/sms-gateway/modules/push/types.go @@ -2,6 +2,7 @@ package push import ( "context" + "time" "github.com/android-sms-gateway/client-go/smsgateway" "github.com/android-sms-gateway/server/internal/sms-gateway/modules/push/domain" @@ -17,14 +18,24 @@ const ( type client interface { Open(ctx context.Context) error - Send(ctx context.Context, messages map[string]Event) error + Send(ctx context.Context, messages map[string]domain.Event) error Close(ctx context.Context) error } -func NewMessageEnqueuedEvent() *Event { +func NewMessageEnqueuedEvent() *domain.Event { return domain.NewEvent(smsgateway.PushMessageEnqueued, nil) } -func NewWebhooksUpdatedEvent() *Event { +func NewWebhooksUpdatedEvent() *domain.Event { return domain.NewEvent(smsgateway.PushWebhooksUpdated, nil) } + +func NewMessagesExportRequestedEvent(since, until time.Time) *domain.Event { + return domain.NewEvent( + smsgateway.PushMessagesExportRequested, + map[string]string{ + "since": since.Format(time.RFC3339), + "until": until.Format(time.RFC3339), + }, + ) +} diff --git a/pkg/swagger/docs/requests.http b/pkg/swagger/docs/requests.http index 03a1566..1638fae 100644 --- a/pkg/swagger/docs/requests.http +++ b/pkg/swagger/docs/requests.http @@ -10,7 +10,7 @@ GET {{baseUrl}}/health HTTP/1.1 GET {{baseUrl}}/api/3rdparty/v1/health HTTP/1.1 ### -POST {{baseUrl}}/api/3rdparty/v1/message?skipPhoneValidation=false HTTP/1.1 +POST {{baseUrl}}/api/3rdparty/v1/messages?skipPhoneValidation=false HTTP/1.1 Content-Type: application/json Authorization: Basic {{credentials}} @@ -25,7 +25,7 @@ Authorization: Basic {{credentials}} } ### -POST {{baseUrl}}/api/3rdparty/v1/message HTTP/1.1 +POST {{baseUrl}}/api/3rdparty/v1/messages HTTP/1.1 Content-Type: application/json Authorization: Basic {{credentials}} @@ -41,9 +41,20 @@ Authorization: Basic {{credentials}} } ### -GET {{baseUrl}}/api/3rdparty/v1/message/K56aIsVsQ2rECdv_ajzTd HTTP/1.1 +GET {{baseUrl}}/api/3rdparty/v1/messages/K56aIsVsQ2rECdv_ajzTd HTTP/1.1 Authorization: Basic {{credentials}} +### +POST {{baseUrl}}/api/3rdparty/v1/messages/inbox/export HTTP/1.1 +Authorization: Basic {{credentials}} +Content-Type: application/json + +{ + "since": "2024-12-01T00:00:00.000Z", + "until": "2024-12-31T23:59:59.999Z", + "deviceId": "MxKw03Q2ZVoomrLeDLlMO" +} + ### GET {{baseUrl}}/api/3rdparty/v1/devices HTTP/1.1 Authorization: Basic {{credentials}} diff --git a/pkg/swagger/docs/swagger.json b/pkg/swagger/docs/swagger.json index 8689651..6ec3463 100644 --- a/pkg/swagger/docs/swagger.json +++ b/pkg/swagger/docs/swagger.json @@ -86,6 +86,64 @@ } } }, + "/3rdparty/v1/inbox/export": { + "post": { + "security": [ + { + "ApiAuth": [] + } + ], + "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.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "User", + "Messages" + ], + "summary": "Request inbox messages export", + "parameters": [ + { + "description": "Export inbox request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/smsgateway.MessagesExportRequest" + } + } + ], + "responses": { + "202": { + "description": "Inbox export request accepted", + "schema": { + "type": "object" + } + }, + "400": { + "description": "Invalid request", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/smsgateway.ErrorResponse" + } + } + } + } + }, "/3rdparty/v1/logs": { "get": { "security": [ @@ -149,7 +207,7 @@ } } }, - "/3rdparty/v1/message": { + "/3rdparty/v1/messages": { "post": { "security": [ { @@ -225,7 +283,7 @@ } } }, - "/3rdparty/v1/message/{id}": { + "/3rdparty/v1/messages/{id}": { "get": { "security": [ { @@ -1075,6 +1133,32 @@ } } }, + "smsgateway.MessagesExportRequest": { + "type": "object", + "required": [ + "deviceId", + "since", + "until" + ], + "properties": { + "deviceId": { + "description": "DeviceID is the ID of the device to export messages for.", + "type": "string", + "maxLength": 21, + "example": "PyDmBQZZXYmyxMwED8Fzy" + }, + "since": { + "description": "Since is the start of the time range to export.", + "type": "string", + "example": "2024-01-01T00:00:00Z" + }, + "until": { + "description": "Until is the end of the time range to export.", + "type": "string", + "example": "2024-01-01T23:59:59Z" + } + } + }, "smsgateway.MobileChangePasswordRequest": { "type": "object", "required": [ @@ -1198,11 +1282,13 @@ "type": "string", "enum": [ "MessageEnqueued", - "WebhooksUpdated" + "WebhooksUpdated", + "MessagesExportRequested" ], "x-enum-varnames": [ "PushMessageEnqueued", - "PushWebhooksUpdated" + "PushWebhooksUpdated", + "PushMessagesExportRequested" ] }, "smsgateway.PushNotification": { @@ -1223,7 +1309,8 @@ "default": "MessageEnqueued", "enum": [ "MessageEnqueued", - "WebhooksUpdated" + "WebhooksUpdated", + "MessagesExportRequested" ], "allOf": [ { diff --git a/pkg/swagger/docs/swagger.yaml b/pkg/swagger/docs/swagger.yaml index 51b29cd..025c23c 100644 --- a/pkg/swagger/docs/swagger.yaml +++ b/pkg/swagger/docs/swagger.yaml @@ -211,6 +211,26 @@ definitions: - recipients - state type: object + smsgateway.MessagesExportRequest: + properties: + deviceId: + description: DeviceID is the ID of the device to export messages for. + example: PyDmBQZZXYmyxMwED8Fzy + maxLength: 21 + type: string + since: + description: Since is the start of the time range to export. + example: "2024-01-01T00:00:00Z" + type: string + until: + description: Until is the end of the time range to export. + example: "2024-01-01T23:59:59Z" + type: string + required: + - deviceId + - since + - until + type: object smsgateway.MobileChangePasswordRequest: properties: currentPassword: @@ -305,10 +325,12 @@ definitions: enum: - MessageEnqueued - WebhooksUpdated + - MessagesExportRequested type: string x-enum-varnames: - PushMessageEnqueued - PushWebhooksUpdated + - PushMessagesExportRequested smsgateway.PushNotification: properties: data: @@ -324,6 +346,7 @@ definitions: enum: - MessageEnqueued - WebhooksUpdated + - MessagesExportRequested example: MessageEnqueued token: description: The token of the device that receives the notification. @@ -444,6 +467,45 @@ paths: summary: Health check tags: - System + /3rdparty/v1/inbox/export: + post: + consumes: + - 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' + produces: + - application/json + responses: + "202": + description: Inbox export request accepted + schema: + type: object + "400": + description: Invalid request + schema: + $ref: '#/definitions/smsgateway.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/smsgateway.ErrorResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/smsgateway.ErrorResponse' + security: + - ApiAuth: [] + summary: Request inbox messages export + tags: + - User + - Messages /3rdparty/v1/logs: get: description: Retrieve a list of log entries within a specified time range. @@ -487,7 +549,7 @@ paths: tags: - System - Logs - /3rdparty/v1/message: + /3rdparty/v1/messages: post: consumes: - application/json @@ -537,7 +599,7 @@ paths: tags: - User - Messages - /3rdparty/v1/message/{id}: + /3rdparty/v1/messages/{id}: get: description: Returns message state by ID parameters: