mirror of
https://github.com/makayabou/asg-server.git
synced 2026-05-02 17:43:36 +02:00
[mobile] add order param for GET /message endpoint
This commit is contained in:
parent
6dac509305
commit
f50b85bdba
@ -60,7 +60,7 @@ type ThirdPartyController struct {
|
||||
//
|
||||
// Enqueue message
|
||||
func (h *ThirdPartyController) post(user models.User, c *fiber.Ctx) error {
|
||||
var params postQueryParams
|
||||
var params thirdPartyPostQueryParams
|
||||
if err := h.QueryParserValidator(c, ¶ms); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
@ -190,7 +190,7 @@ func (h *ThirdPartyController) post(user models.User, c *fiber.Ctx) error {
|
||||
//
|
||||
// Get message history
|
||||
func (h *ThirdPartyController) list(user models.User, c *fiber.Ctx) error {
|
||||
params := getQueryParams{}
|
||||
params := thirdPartyGetQueryParams{}
|
||||
if err := h.QueryParserValidator(c, ¶ms); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
122
internal/sms-gateway/handlers/messages/mobile.go
Normal file
122
internal/sms-gateway/handlers/messages/mobile.go
Normal file
@ -0,0 +1,122 @@
|
||||
package messages
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/android-sms-gateway/client-go/smsgateway"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/converters"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/deviceauth"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/messages"
|
||||
"github.com/capcom6/go-helpers/slices"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type mobileControllerParams struct {
|
||||
fx.In
|
||||
|
||||
MessagesSvc *messages.Service
|
||||
|
||||
Validator *validator.Validate
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
type MobileController struct {
|
||||
base.Handler
|
||||
|
||||
messagesSvc *messages.Service
|
||||
}
|
||||
|
||||
// @Summary Get messages for sending
|
||||
// @Description Returns list of pending messages
|
||||
// @Security MobileToken
|
||||
// @Tags Device, Messages
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param order query string false "Message processing order: lifo (default) or fifo" Enums(lifo,fifo) default(lifo)
|
||||
// @Success 200 {object} smsgateway.MobileGetMessagesResponse "List of pending messages"
|
||||
// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
|
||||
// @Router /mobile/v1/message [get]
|
||||
//
|
||||
// Get messages for sending
|
||||
func (h *MobileController) list(device models.Device, c *fiber.Ctx) error {
|
||||
// Get and validate order parameter
|
||||
params := mobileGetQueryParams{}
|
||||
if err := h.QueryParserValidator(c, ¶ms); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
msgs, err := h.messagesSvc.SelectPending(device.ID, params.OrderOrDefault())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get messages: %w", err)
|
||||
}
|
||||
|
||||
return c.JSON(
|
||||
smsgateway.MobileGetMessagesResponse(
|
||||
slices.Map(
|
||||
msgs,
|
||||
converters.MessageToMobileDTO,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// @Summary Update message state
|
||||
// @Description Updates message state
|
||||
// @Security MobileToken
|
||||
// @Tags Device, Messages
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body smsgateway.MobilePatchMessageRequest true "List of message state updates"
|
||||
// @Success 204 {object} nil "Successfully updated"
|
||||
// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
|
||||
// @Router /mobile/v1/message [patch]
|
||||
//
|
||||
// Update message state
|
||||
func (h *MobileController) patch(device models.Device, c *fiber.Ctx) error {
|
||||
var req smsgateway.MobilePatchMessageRequest
|
||||
if err := h.BodyParserValidator(c, &req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
for _, v := range req {
|
||||
messageState := messages.MessageStateIn{
|
||||
ID: v.ID,
|
||||
State: messages.ProcessingState(v.State),
|
||||
Recipients: v.Recipients,
|
||||
States: v.States,
|
||||
}
|
||||
|
||||
err := h.messagesSvc.UpdateState(device.ID, messageState)
|
||||
if err != nil && !errors.Is(err, messages.ErrMessageNotFound) {
|
||||
h.Logger.Error("Can't update message status",
|
||||
zap.String("message_id", v.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *MobileController) Register(router fiber.Router) {
|
||||
router.Get("", deviceauth.WithDevice(h.list))
|
||||
router.Patch("", deviceauth.WithDevice(h.patch))
|
||||
}
|
||||
|
||||
func NewMobileController(params mobileControllerParams) *MobileController {
|
||||
return &MobileController{
|
||||
Handler: base.Handler{
|
||||
Logger: params.Logger.Named("messages"),
|
||||
Validator: params.Validator,
|
||||
},
|
||||
messagesSvc: params.MessagesSvc,
|
||||
}
|
||||
}
|
||||
@ -7,12 +7,12 @@ import (
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/messages"
|
||||
)
|
||||
|
||||
type postQueryParams struct {
|
||||
type thirdPartyPostQueryParams struct {
|
||||
SkipPhoneValidation bool `query:"skipPhoneValidation"`
|
||||
DeviceActiveWithin uint `query:"deviceActiveWithin"`
|
||||
}
|
||||
|
||||
type getQueryParams struct {
|
||||
type thirdPartyGetQueryParams struct {
|
||||
StartDate string `query:"from" validate:"omitempty,datetime=2006-01-02T15:04:05Z07:00"`
|
||||
EndDate string `query:"to" validate:"omitempty,datetime=2006-01-02T15:04:05Z07:00"`
|
||||
State string `query:"state" validate:"omitempty,oneof=Pending Processed Sent Delivered Failed"`
|
||||
@ -21,7 +21,7 @@ type getQueryParams struct {
|
||||
Offset int `query:"offset" validate:"omitempty,min=0"`
|
||||
}
|
||||
|
||||
func (p *getQueryParams) Validate() error {
|
||||
func (p *thirdPartyGetQueryParams) Validate() error {
|
||||
if p.StartDate != "" && p.EndDate != "" && p.StartDate > p.EndDate {
|
||||
return fmt.Errorf("`from` date must be before `to` date")
|
||||
}
|
||||
@ -29,7 +29,7 @@ func (p *getQueryParams) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *getQueryParams) ToFilter() messages.MessagesSelectFilter {
|
||||
func (p *thirdPartyGetQueryParams) ToFilter() messages.MessagesSelectFilter {
|
||||
filter := messages.MessagesSelectFilter{}
|
||||
|
||||
if p.StartDate != "" {
|
||||
@ -55,7 +55,7 @@ func (p *getQueryParams) ToFilter() messages.MessagesSelectFilter {
|
||||
return filter
|
||||
}
|
||||
|
||||
func (p *getQueryParams) ToOptions() messages.MessagesSelectOptions {
|
||||
func (p *thirdPartyGetQueryParams) ToOptions() messages.MessagesSelectOptions {
|
||||
options := messages.MessagesSelectOptions{
|
||||
WithRecipients: true,
|
||||
WithStates: true,
|
||||
@ -73,3 +73,15 @@ func (p *getQueryParams) ToOptions() messages.MessagesSelectOptions {
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
type mobileGetQueryParams struct {
|
||||
Order messages.MessagesOrder `query:"order" validate:"omitempty,oneof=lifo fifo"`
|
||||
}
|
||||
|
||||
func (p *mobileGetQueryParams) OrderOrDefault() messages.MessagesOrder {
|
||||
if p.Order != "" {
|
||||
return p.Order
|
||||
}
|
||||
return messages.MessagesOrderLIFO
|
||||
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@ -9,6 +8,7 @@ import (
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/converters"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/events"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/messages"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/deviceauth"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/settings"
|
||||
@ -16,9 +16,7 @@ import (
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth"
|
||||
"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/anys"
|
||||
"github.com/capcom6/go-helpers/slices"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/keyauth"
|
||||
@ -27,13 +25,28 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type mobileHandlerParams struct {
|
||||
fx.In
|
||||
|
||||
Logger *zap.Logger
|
||||
Validator *validator.Validate
|
||||
|
||||
AuthSvc *auth.Service
|
||||
DevicesSvc *devices.Service
|
||||
|
||||
MessagesCtrl *messages.MobileController
|
||||
WebhooksCtrl *webhooks.MobileController
|
||||
SettingsCtrl *settings.MobileController
|
||||
EventsCtrl *events.MobileController
|
||||
}
|
||||
|
||||
type mobileHandler struct {
|
||||
base.Handler
|
||||
|
||||
authSvc *auth.Service
|
||||
devicesSvc *devices.Service
|
||||
messagesSvc *messages.Service
|
||||
authSvc *auth.Service
|
||||
devicesSvc *devices.Service
|
||||
|
||||
messagesCtrl *messages.MobileController
|
||||
webhooksCtrl *webhooks.MobileController
|
||||
settingsCtrl *settings.MobileController
|
||||
eventsCtrl *events.MobileController
|
||||
@ -151,69 +164,6 @@ func (h *mobileHandler) patchDevice(device models.Device, c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// @Summary Get messages for sending
|
||||
// @Description Returns list of pending messages
|
||||
// @Security MobileToken
|
||||
// @Tags Device, Messages
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} smsgateway.MobileGetMessagesResponse "List of pending messages"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
|
||||
// @Router /mobile/v1/message [get]
|
||||
//
|
||||
// Get messages for sending
|
||||
func (h *mobileHandler) getMessage(device models.Device, c *fiber.Ctx) error {
|
||||
msgs, err := h.messagesSvc.SelectPending(device.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get messages: %w", err)
|
||||
}
|
||||
|
||||
return c.JSON(
|
||||
smsgateway.MobileGetMessagesResponse(
|
||||
slices.Map(
|
||||
msgs,
|
||||
converters.MessageToMobileDTO,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// @Summary Update message state
|
||||
// @Description Updates message state
|
||||
// @Security MobileToken
|
||||
// @Tags Device, Messages
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body smsgateway.MobilePatchMessageRequest true "List of message state updates"
|
||||
// @Success 204 {object} nil "Successfully updated"
|
||||
// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
|
||||
// @Router /mobile/v1/message [patch]
|
||||
//
|
||||
// Update message state
|
||||
func (h *mobileHandler) patchMessage(device models.Device, c *fiber.Ctx) error {
|
||||
var req smsgateway.MobilePatchMessageRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
for _, v := range req {
|
||||
messageState := messages.MessageStateIn{
|
||||
ID: v.ID,
|
||||
State: messages.ProcessingState(v.State),
|
||||
Recipients: v.Recipients,
|
||||
States: v.States,
|
||||
}
|
||||
|
||||
err := h.messagesSvc.UpdateState(device.ID, messageState)
|
||||
if err != nil && !errors.Is(err, messages.ErrMessageNotFound) {
|
||||
h.Logger.Error("Can't update message status", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// @Summary Get one-time code for device registration
|
||||
// @Description Returns one-time code for device registration
|
||||
// @Security ApiAuth
|
||||
@ -303,43 +253,29 @@ func (h *mobileHandler) Register(router fiber.Router) {
|
||||
|
||||
router.Patch("/device", deviceauth.WithDevice(h.patchDevice))
|
||||
|
||||
router.Get("/message", deviceauth.WithDevice(h.getMessage))
|
||||
router.Patch("/message", deviceauth.WithDevice(h.patchMessage))
|
||||
|
||||
// Should be under `userauth.NewBasic` protection instead of `deviceauth`
|
||||
router.Patch("/user/password", deviceauth.WithDevice(h.changePassword))
|
||||
|
||||
h.messagesCtrl.Register(router.Group("/message"))
|
||||
h.messagesCtrl.Register(router.Group("/messages"))
|
||||
h.webhooksCtrl.Register(router.Group("/webhooks"))
|
||||
h.settingsCtrl.Register(router.Group("/settings"))
|
||||
h.eventsCtrl.Register(router.Group("/events"))
|
||||
}
|
||||
|
||||
type mobileHandlerParams struct {
|
||||
fx.In
|
||||
|
||||
Logger *zap.Logger
|
||||
Validator *validator.Validate
|
||||
|
||||
AuthSvc *auth.Service
|
||||
DevicesSvc *devices.Service
|
||||
MessagesSvc *messages.Service
|
||||
|
||||
WebhooksCtrl *webhooks.MobileController
|
||||
SettingsCtrl *settings.MobileController
|
||||
EventsCtrl *events.MobileController
|
||||
}
|
||||
|
||||
func newMobileHandler(params mobileHandlerParams) *mobileHandler {
|
||||
idGen, _ := nanoid.Standard(21)
|
||||
|
||||
return &mobileHandler{
|
||||
Handler: base.Handler{Logger: params.Logger, Validator: params.Validator},
|
||||
authSvc: params.AuthSvc,
|
||||
Handler: base.Handler{Logger: params.Logger, Validator: params.Validator},
|
||||
authSvc: params.AuthSvc,
|
||||
|
||||
messagesCtrl: params.MessagesCtrl,
|
||||
devicesSvc: params.DevicesSvc,
|
||||
messagesSvc: params.MessagesSvc,
|
||||
webhooksCtrl: params.WebhooksCtrl,
|
||||
settingsCtrl: params.SettingsCtrl,
|
||||
eventsCtrl: params.EventsCtrl,
|
||||
idGen: idGen,
|
||||
|
||||
idGen: idGen,
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ var Module = fx.Module(
|
||||
fx.Provide(
|
||||
newHealthHandler,
|
||||
messages.NewThirdPartyController,
|
||||
messages.NewMobileController,
|
||||
webhooks.NewThirdPartyController,
|
||||
webhooks.NewMobileController,
|
||||
devices.NewThirdPartyController,
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
const hashingLockName = "36444143-1ace-4dbf-891c-cc505911497e"
|
||||
const maxPendingBatch = 100
|
||||
|
||||
var ErrMessageNotFound = gorm.ErrRecordNotFound
|
||||
var ErrMessageAlreadyExists = errors.New("duplicate id")
|
||||
@ -70,7 +71,11 @@ func (r *repository) Select(filter MessagesSelectFilter, options MessagesSelectO
|
||||
}
|
||||
|
||||
// Apply ordering
|
||||
query = query.Order("messages.priority DESC, messages.id DESC")
|
||||
if options.OrderBy == MessagesOrderFIFO {
|
||||
query = query.Order("messages.priority DESC, messages.id ASC")
|
||||
} else {
|
||||
query = query.Order("messages.priority DESC, messages.id DESC")
|
||||
}
|
||||
|
||||
// Preload related data
|
||||
if options.WithRecipients {
|
||||
@ -91,13 +96,14 @@ func (r *repository) Select(filter MessagesSelectFilter, options MessagesSelectO
|
||||
return messages, total, nil
|
||||
}
|
||||
|
||||
func (r *repository) SelectPending(deviceID string) ([]Message, error) {
|
||||
func (r *repository) SelectPending(deviceID string, order MessagesOrder) ([]Message, error) {
|
||||
messages, _, err := r.Select(MessagesSelectFilter{
|
||||
DeviceID: deviceID,
|
||||
State: ProcessingStatePending,
|
||||
}, MessagesSelectOptions{
|
||||
WithRecipients: true,
|
||||
Limit: 100,
|
||||
Limit: maxPendingBatch,
|
||||
OrderBy: order,
|
||||
})
|
||||
|
||||
return messages, err
|
||||
|
||||
@ -2,6 +2,17 @@ package messages
|
||||
|
||||
import "time"
|
||||
|
||||
// MessagesOrder defines supported ordering for message selection.
|
||||
// Valid values: "lifo" (default), "fifo".
|
||||
type MessagesOrder string
|
||||
|
||||
const (
|
||||
// MessagesOrderLIFO orders messages newest-first within the same priority (default).
|
||||
MessagesOrderLIFO MessagesOrder = "lifo"
|
||||
// MessagesOrderFIFO orders messages oldest-first within the same priority.
|
||||
MessagesOrderFIFO MessagesOrder = "fifo"
|
||||
)
|
||||
|
||||
type MessagesSelectFilter struct {
|
||||
ExtID string
|
||||
UserID string
|
||||
@ -16,6 +27,10 @@ type MessagesSelectOptions struct {
|
||||
WithDevice bool
|
||||
WithStates bool
|
||||
|
||||
// OrderBy sets the retrieval order for pending messages.
|
||||
// Empty (zero) value defaults to "lifo".
|
||||
OrderBy MessagesOrder
|
||||
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
@ -92,8 +92,12 @@ func (s *Service) RunBackgroundTasks(ctx context.Context, wg *sync.WaitGroup) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Service) SelectPending(deviceID string) ([]MessageOut, error) {
|
||||
messages, err := s.messages.SelectPending(deviceID)
|
||||
func (s *Service) SelectPending(deviceID string, order MessagesOrder) ([]MessageOut, error) {
|
||||
if order == "" {
|
||||
order = MessagesOrderLIFO
|
||||
}
|
||||
|
||||
messages, err := s.messages.SelectPending(deviceID, order)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -982,6 +982,19 @@
|
||||
"Messages"
|
||||
],
|
||||
"summary": "Get messages for sending",
|
||||
"parameters": [
|
||||
{
|
||||
"enum": [
|
||||
"lifo",
|
||||
"fifo"
|
||||
],
|
||||
"type": "string",
|
||||
"default": "lifo",
|
||||
"description": "Message processing order: lifo (default) or fifo",
|
||||
"name": "order",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of pending messages",
|
||||
@ -992,6 +1005,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/smsgateway.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
|
||||
@ -1417,6 +1417,15 @@ paths:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns list of pending messages
|
||||
parameters:
|
||||
- default: lifo
|
||||
description: 'Message processing order: lifo (default) or fifo'
|
||||
enum:
|
||||
- lifo
|
||||
- fifo
|
||||
in: query
|
||||
name: order
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@ -1426,6 +1435,10 @@ paths:
|
||||
items:
|
||||
$ref: '#/definitions/smsgateway.MobileMessage'
|
||||
type: array
|
||||
"400":
|
||||
description: Invalid request
|
||||
schema:
|
||||
$ref: '#/definitions/smsgateway.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user