mirror of
https://github.com/makayabou/asg-server.git
synced 2026-05-02 17:43:36 +02:00
[api/mobile] write message states log to db
This commit is contained in:
parent
a6c23ec7c4
commit
63f0cb5960
@ -63,14 +63,18 @@ Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"id": "GKBw_tkVnN8NJz3hse9ue",
|
||||
"id": "RqqnKoakAc82f6e4SwoMe",
|
||||
"state": "Failed",
|
||||
"recipients": [
|
||||
{
|
||||
"phoneNumber": "{{phone}}",
|
||||
"state": "Failed"
|
||||
}
|
||||
]
|
||||
],
|
||||
"states": {
|
||||
"Processed": "2024-05-13T16:49:17.357Z",
|
||||
"Failed": "2024-05-13T16:49:17.357+04:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -141,11 +141,11 @@ func (h *mobileHandler) patchMessage(device models.Device, c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
if err := h.Validator.Var(req, "required,dive"); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
for _, v := range req {
|
||||
if err := h.validateStruct(v); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
err := h.messagesSvc.UpdateState(device.ID, v)
|
||||
if err != nil && !errors.Is(err, repositories.ErrMessageNotFound) {
|
||||
h.Logger.Error("Can't update message status", zap.Error(err))
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE `message_states` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT,
|
||||
`message_id` BIGINT UNSIGNED NOT NULL,
|
||||
`state` enum(
|
||||
'Pending',
|
||||
'Sent',
|
||||
'Processed',
|
||||
'Delivered',
|
||||
'Failed'
|
||||
) NOT NULL,
|
||||
`updated_at` datetime(3) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `unq_message_states_message_id_state` (`message_id`, `state`),
|
||||
CONSTRAINT `fk_messages_states` FOREIGN KEY (`message_id`) REFERENCES `messages`(`id`) ON DELETE CASCADE
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
---
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE `message_states`;
|
||||
-- +goose StatementEnd
|
||||
@ -17,6 +17,7 @@ import (
|
||||
"github.com/nyaruka/phonenumbers"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -116,11 +117,18 @@ func (s *Service) UpdateState(deviceID string, message smsgateway.MessageState)
|
||||
return err
|
||||
}
|
||||
|
||||
if message.State == smsgateway.MessageStatePending {
|
||||
message.State = smsgateway.MessageStateProcessed
|
||||
if message.State == smsgateway.ProcessingStatePending {
|
||||
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{
|
||||
MessageID: existing.ID,
|
||||
State: models.ProcessingState(key),
|
||||
UpdatedAt: message.States[key],
|
||||
}
|
||||
})
|
||||
existing.Recipients = s.recipientsStateToModel(message.Recipients, existing.IsHashed)
|
||||
|
||||
if err := s.Messages.UpdateState(&existing); err != nil {
|
||||
@ -148,7 +156,7 @@ func (s *Service) GetState(user models.User, ID string) (smsgateway.MessageState
|
||||
func (s *Service) Enqeue(device models.Device, message smsgateway.Message, opts EnqueueOptions) (smsgateway.MessageState, error) {
|
||||
state := smsgateway.MessageState{
|
||||
ID: "",
|
||||
State: smsgateway.MessageStatePending,
|
||||
State: smsgateway.ProcessingStatePending,
|
||||
Recipients: make([]smsgateway.RecipientState, len(message.PhoneNumbers)),
|
||||
}
|
||||
|
||||
@ -167,7 +175,7 @@ func (s *Service) Enqeue(device models.Device, message smsgateway.Message, opts
|
||||
|
||||
state.Recipients[i] = smsgateway.RecipientState{
|
||||
PhoneNumber: phone,
|
||||
State: smsgateway.MessageStatePending,
|
||||
State: smsgateway.ProcessingStatePending,
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,8 +253,8 @@ func (s *Service) recipientsStateToModel(input []smsgateway.RecipientState, hash
|
||||
phoneNumber = "+" + phoneNumber
|
||||
}
|
||||
|
||||
if v.State == smsgateway.MessageStatePending {
|
||||
v.State = smsgateway.MessageStateProcessed
|
||||
if v.State == smsgateway.ProcessingStatePending {
|
||||
v.State = smsgateway.ProcessingStateProcessed
|
||||
}
|
||||
|
||||
if hash {
|
||||
@ -266,7 +274,7 @@ func (s *Service) recipientsStateToModel(input []smsgateway.RecipientState, hash
|
||||
func modelToMessageState(input models.Message) smsgateway.MessageState {
|
||||
return smsgateway.MessageState{
|
||||
ID: input.ExtID,
|
||||
State: smsgateway.ProcessState(input.State),
|
||||
State: smsgateway.ProcessingState(input.State),
|
||||
IsHashed: input.IsHashed,
|
||||
IsEncrypted: input.IsEncrypted,
|
||||
Recipients: slices.Map(input.Recipients, modelToRecipientState),
|
||||
@ -276,7 +284,7 @@ func modelToMessageState(input models.Message) smsgateway.MessageState {
|
||||
func modelToRecipientState(input models.MessageRecipient) smsgateway.RecipientState {
|
||||
return smsgateway.RecipientState{
|
||||
PhoneNumber: input.PhoneNumber,
|
||||
State: smsgateway.ProcessState(input.State),
|
||||
State: smsgateway.ProcessingState(input.State),
|
||||
Error: input.Error,
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/capcom6/sms-gateway/internal/sms-gateway/models"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
const HashingLockName = "36444143-1ace-4dbf-891c-cc505911497e"
|
||||
@ -59,6 +60,15 @@ func (r *MessagesRepository) UpdateState(message *models.Message) error {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range message.States {
|
||||
v.MessageID = message.ID
|
||||
if err := tx.Model(&v).Clauses(clause.OnConflict{
|
||||
DoNothing: true,
|
||||
}).Create(&v).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range message.Recipients {
|
||||
if err := tx.Model(&v).Where("message_id = ?", message.ID).Select("State", "Error").Updates(&v).Error; err != nil {
|
||||
return err
|
||||
|
||||
@ -6,13 +6,21 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
MessageStatePending ProcessState = "Pending" // Pending
|
||||
MessageStateProcessed ProcessState = "Processed" // Processed (received by device)
|
||||
MessageStateSent ProcessState = "Sent" // Sent
|
||||
MessageStateDelivered ProcessState = "Delivered" // Delivered
|
||||
MessageStateFailed ProcessState = "Failed" // Failed
|
||||
ProcessingStatePending ProcessingState = "Pending" // Pending
|
||||
ProcessingStateProcessed ProcessingState = "Processed" // Processed (received by device)
|
||||
ProcessingStateSent ProcessingState = "Sent" // Sent
|
||||
ProcessingStateDelivered ProcessingState = "Delivered" // Delivered
|
||||
ProcessingStateFailed ProcessingState = "Failed" // Failed
|
||||
)
|
||||
|
||||
var allProcessStates = map[ProcessingState]struct{}{
|
||||
ProcessingStatePending: {},
|
||||
ProcessingStateProcessed: {},
|
||||
ProcessingStateSent: {},
|
||||
ProcessingStateDelivered: {},
|
||||
ProcessingStateFailed: {},
|
||||
}
|
||||
|
||||
// Device
|
||||
type Device struct {
|
||||
ID string `json:"id" example:"PyDmBQZZXYmyxMwED8Fzy"` // ID
|
||||
@ -47,18 +55,29 @@ func (m Message) Validate() error {
|
||||
|
||||
// Message state
|
||||
type MessageState struct {
|
||||
ID string `json:"id,omitempty" validate:"omitempty,max=36" example:"PyDmBQZZXYmyxMwED8Fzy"` // Message ID
|
||||
State ProcessState `json:"state" validate:"required" example:"Pending"` // State
|
||||
IsHashed bool `json:"isHashed" example:"false"` // Hashed
|
||||
IsEncrypted bool `json:"isEncrypted" example:"false"` // Encrypted
|
||||
Recipients []RecipientState `json:"recipients" validate:"required,min=1,dive"` // Recipients states
|
||||
ID string `json:"id,omitempty" validate:"omitempty,max=36" example:"PyDmBQZZXYmyxMwED8Fzy"` // Message ID
|
||||
State ProcessingState `json:"state" validate:"required" example:"Pending"` // State
|
||||
IsHashed bool `json:"isHashed" example:"false"` // Hashed
|
||||
IsEncrypted bool `json:"isEncrypted" example:"false"` // Encrypted
|
||||
Recipients []RecipientState `json:"recipients" validate:"required,min=1,dive"` // Recipients states
|
||||
States map[string]time.Time `json:"states"` // History of states
|
||||
}
|
||||
|
||||
func (m MessageState) Validate() error {
|
||||
for k := range m.States {
|
||||
if _, ok := allProcessStates[ProcessingState(k)]; !ok {
|
||||
return fmt.Errorf("invalid state value: %s", k)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recipient state
|
||||
type RecipientState struct {
|
||||
PhoneNumber string `json:"phoneNumber" validate:"required,min=10,max=128" example:"79990001234"` // Phone number or first 16 symbols of SHA256 hash
|
||||
State ProcessState `json:"state" validate:"required" example:"Pending"` // State
|
||||
Error *string `json:"error,omitempty" example:"timeout"` // Error (for `Failed` state)
|
||||
PhoneNumber string `json:"phoneNumber" validate:"required,min=10,max=128" example:"79990001234"` // Phone number or first 16 symbols of SHA256 hash
|
||||
State ProcessingState `json:"state" validate:"required" example:"Pending"` // State
|
||||
Error *string `json:"error,omitempty" example:"timeout"` // Error (for `Failed` state)
|
||||
}
|
||||
|
||||
// Push notification
|
||||
|
||||
@ -2,6 +2,6 @@ package smsgateway
|
||||
|
||||
import "errors"
|
||||
|
||||
type ProcessState string
|
||||
type ProcessingState string
|
||||
|
||||
var ErrConflictFields = errors.New("conflict fields")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user