[feature] add inbox export endpoint

This commit is contained in:
Aleksandr Soloshenko 2024-12-31 09:57:41 +07:00 committed by Aleksandr
parent 35c3ad3718
commit 70297a5007
9 changed files with 242 additions and 17 deletions

2
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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 {

View File

@ -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))

View File

@ -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)
}

View File

@ -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),
},
)
}

View File

@ -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}}

View File

@ -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": [
{

View File

@ -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: