mirror of
https://github.com/makayabou/asg-server.git
synced 2026-05-02 17:43:36 +02:00
Added: ttl support
This commit is contained in:
parent
a9a92b15bb
commit
e473400bdf
@ -16,6 +16,7 @@ Authorization: Basic IUGFXE:v4ejpbeydvjo1h
|
||||
|
||||
{
|
||||
"message": "Test",
|
||||
"ttl": {{$randomInt 0 86400}},
|
||||
"phoneNumbers": [
|
||||
"79990001234"
|
||||
]
|
||||
@ -24,3 +25,7 @@ Authorization: Basic IUGFXE:v4ejpbeydvjo1h
|
||||
###
|
||||
GET {{baseUrl}}/api/3rdparty/v1/message/pRl1wf_yez5TikJb_xemU HTTP/1.1
|
||||
Authorization: Basic IUGFXE:v4ejpbeydvjo1h
|
||||
|
||||
###
|
||||
GET {{baseUrl}}/api/mobile/v1/message HTTP/1.1
|
||||
Authorization: Bearer KuvE4LBXzvy8QO2ZXDDMP
|
||||
@ -357,6 +357,12 @@
|
||||
"example": [
|
||||
"79990001234"
|
||||
]
|
||||
},
|
||||
"ttl": {
|
||||
"description": "Время жизни сообщения в секундах",
|
||||
"type": "integer",
|
||||
"minimum": 5,
|
||||
"example": 86400
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -33,6 +33,11 @@ definitions:
|
||||
maxItems: 100
|
||||
minItems: 1
|
||||
type: array
|
||||
ttl:
|
||||
description: Время жизни сообщения в секундах
|
||||
example: 86400
|
||||
minimum: 5
|
||||
type: integer
|
||||
required:
|
||||
- message
|
||||
- phoneNumbers
|
||||
|
||||
@ -26,6 +26,8 @@ import (
|
||||
// @host localhost:3000
|
||||
// @schemes http
|
||||
// @BasePath /api
|
||||
//
|
||||
// SMS-шлюз
|
||||
func main() {
|
||||
cfg := config.GetConfig()
|
||||
|
||||
|
||||
10
db/migrations/20231020230401_ttl_support.sql
Normal file
10
db/migrations/20231020230401_ttl_support.sql
Normal file
@ -0,0 +1,10 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE `messages`
|
||||
ADD `valid_until` datetime;
|
||||
-- +goose StatementEnd
|
||||
---
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE `messages` DROP `valid_until`;
|
||||
-- +goose StatementEnd
|
||||
4
go.mod
4
go.mod
@ -4,12 +4,11 @@ go 1.20
|
||||
|
||||
require (
|
||||
bitbucket.org/soft-c/gohelpers v1.0.3-0.20221007032455-694a304f5909
|
||||
bitbucket.org/soft-c/gomicrobase v1.1.2-0.20221006080527-7eeddcd13770
|
||||
bitbucket.org/soft-c/gomicrobase v1.3.1-0.20231020165939-64940c19df05
|
||||
firebase.google.com/go/v4 v4.12.0
|
||||
github.com/go-playground/validator/v10 v10.11.0
|
||||
github.com/gofiber/fiber/v2 v2.38.1
|
||||
github.com/jaevor/go-nanoid v1.3.0
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/nyaruka/phonenumbers v1.1.8
|
||||
google.golang.org/api v0.114.0
|
||||
gorm.io/gorm v1.23.8
|
||||
@ -41,6 +40,7 @@ require (
|
||||
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@ -1,7 +1,7 @@
|
||||
bitbucket.org/soft-c/gohelpers v1.0.3-0.20221007032455-694a304f5909 h1:SwG69J+r+YOkcHnOUYFmsdvRyeEnvMs2KHTf+4L+4f4=
|
||||
bitbucket.org/soft-c/gohelpers v1.0.3-0.20221007032455-694a304f5909/go.mod h1:AlZzQ1xftfS8rWSwjwTj3/MPMFFfgfNCP3b7xLoEeKM=
|
||||
bitbucket.org/soft-c/gomicrobase v1.1.2-0.20221006080527-7eeddcd13770 h1:IAEOe5Uv9+zQEaOuT8eHJPUfS64Nc0I9ufq7afFXKUk=
|
||||
bitbucket.org/soft-c/gomicrobase v1.1.2-0.20221006080527-7eeddcd13770/go.mod h1:Pvv07ulIXfDG5us/ZG5Of7vvjY+tq3fkcrY/MmJUEiE=
|
||||
bitbucket.org/soft-c/gomicrobase v1.3.1-0.20231020165939-64940c19df05 h1:FHJKNG4sy0LYof5LlF8MIUYCcF0/d57Pw+GSXrBPiQc=
|
||||
bitbucket.org/soft-c/gomicrobase v1.3.1-0.20231020165939-64940c19df05/go.mod h1:/b3RdE4wI1BxzZ/FrJ4c8X3g2h/dL8XRyiXovD9oXzE=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
@ -196,8 +196,8 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
microbase "bitbucket.org/soft-c/gomicrobase"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@ -20,21 +16,10 @@ var instance *Config
|
||||
var once = sync.Once{}
|
||||
|
||||
func newConfig() *Config {
|
||||
if err := godotenv.Load(".env"); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
errorLog.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
path := os.Getenv("CONFIG_PATH")
|
||||
if path == "" {
|
||||
path = "config.yml"
|
||||
}
|
||||
|
||||
config := Config{}
|
||||
|
||||
if err := microbase.LoadConfig(path, &config); err != nil {
|
||||
errorLog.Fatalf("Can't load config from %s: %s", path, err.Error())
|
||||
if err := microbase.LoadConfig(&config); err != nil {
|
||||
errorLog.Fatalf("Can't load config from %s", err.Error())
|
||||
}
|
||||
|
||||
return &config
|
||||
|
||||
@ -3,6 +3,7 @@ package smsgateway
|
||||
import (
|
||||
"bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/handlers"
|
||||
"bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/models"
|
||||
_ "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/tasks"
|
||||
microbase "bitbucket.org/soft-c/gomicrobase"
|
||||
)
|
||||
|
||||
|
||||
@ -32,6 +32,8 @@ type thirdPartyHandler struct {
|
||||
// @Failure 400 {object} smsgateway.ErrorResponse "Некорректный запрос"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Внутренняя ошибка сервера"
|
||||
// @Router /3rdparty/v1/message [post]
|
||||
//
|
||||
// Поставить сообщение в очередь
|
||||
func (h *thirdPartyHandler) postMessage(user models.User, c *fiber.Ctx) error {
|
||||
req := smsgateway.Message{}
|
||||
if err := h.BodyParserValidator(c, &req); err != nil {
|
||||
@ -39,7 +41,7 @@ func (h *thirdPartyHandler) postMessage(user models.User, c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
if len(user.Devices) < 1 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Нет ни одного устройтсва в учетной записи")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Нет ни одного устройства в учетной записи")
|
||||
}
|
||||
|
||||
device := user.Devices[0]
|
||||
@ -66,6 +68,8 @@ func (h *thirdPartyHandler) postMessage(user models.User, c *fiber.Ctx) error {
|
||||
// @Failure 400 {object} smsgateway.ErrorResponse "Некорректный запрос"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Внутренняя ошибка сервера"
|
||||
// @Router /3rdparty/v1/message [get]
|
||||
//
|
||||
// Получить состояние сообщения
|
||||
func (h *thirdPartyHandler) getMessage(user models.User, c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
|
||||
|
||||
@ -35,6 +35,8 @@ type mobileHandler struct {
|
||||
// @Failure 400 {object} smsgateway.ErrorResponse "Некорректный запрос"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Внутренняя ошибка сервера"
|
||||
// @Router /mobile/v1/device [post]
|
||||
//
|
||||
// Регистрация устройства
|
||||
func (h *mobileHandler) postDevice(c *fiber.Ctx) error {
|
||||
req := smsgateway.MobileRegisterRequest{}
|
||||
|
||||
@ -75,6 +77,8 @@ func (h *mobileHandler) postDevice(c *fiber.Ctx) error {
|
||||
// @Failure 403 {object} smsgateway.ErrorResponse "Операция запрещена"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Внутренняя ошибка сервера"
|
||||
// @Router /mobile/v1/device [patch]
|
||||
//
|
||||
// Обновление устройства
|
||||
func (h *mobileHandler) patchDevice(device models.Device, c *fiber.Ctx) error {
|
||||
req := smsgateway.MobileUpdateRequest{}
|
||||
|
||||
@ -102,6 +106,8 @@ func (h *mobileHandler) patchDevice(device models.Device, c *fiber.Ctx) error {
|
||||
// @Success 200 {array} smsgateway.Message "Список сообщений"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Внутренняя ошибка сервера"
|
||||
// @Router /mobile/v1/message [get]
|
||||
//
|
||||
// Получить сообщения для отправки
|
||||
func (h *mobileHandler) getMessage(device models.Device, c *fiber.Ctx) error {
|
||||
messages, err := h.messagesSvc.SelectPending(device.ID)
|
||||
if err != nil {
|
||||
@ -122,6 +128,8 @@ func (h *mobileHandler) getMessage(device models.Device, c *fiber.Ctx) error {
|
||||
// @Failure 400 {object} smsgateway.ErrorResponse "Некорректный запрос"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Внутренняя ошибка сервера"
|
||||
// @Router /mobile/v1/message [patch]
|
||||
//
|
||||
// Обновить состояние сообщений
|
||||
func (h *mobileHandler) patchMessage(device models.Device, c *fiber.Ctx) error {
|
||||
req := []smsgateway.MessageState{}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
|
||||
@ -42,11 +42,12 @@ type Device struct {
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
ID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED;autoIncrement"`
|
||||
DeviceID string `gorm:"not null;type:char(21);uniqueIndex:unq_messages_id_device,priority:2;index:idx_messages_device_state"`
|
||||
ExtID string `gorm:"not null;type:varchar(36);uniqueIndex:unq_messages_id_device,priority:1"`
|
||||
Message string `gorm:"not null;type:tinytext"`
|
||||
State MessageState `gorm:"not null;type:enum('Pending','Sent','Processed','Delivered','Failed');default:Pending;index:idx_messages_device_state"`
|
||||
ID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED;autoIncrement"`
|
||||
DeviceID string `gorm:"not null;type:char(21);uniqueIndex:unq_messages_id_device,priority:2;index:idx_messages_device_state"`
|
||||
ExtID string `gorm:"not null;type:varchar(36);uniqueIndex:unq_messages_id_device,priority:1"`
|
||||
Message string `gorm:"not null;type:tinytext"`
|
||||
State MessageState `gorm:"not null;type:enum('Pending','Sent','Processed','Delivered','Failed');default:Pending;index:idx_messages_device_state"`
|
||||
ValidUntil time.Time `gorm:"type:datetime"`
|
||||
|
||||
Device Device `gorm:"foreignKey:DeviceID;constraint:OnDelete:CASCADE"`
|
||||
Recipients []MessageRecipient `gorm:"foreignKey:MessageID;constraint:OnDelete:CASCADE"`
|
||||
|
||||
@ -40,11 +40,26 @@ func (s *MessagesService) SelectPending(deviceID string) ([]smsgateway.Message,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
messages = s.filterTimeouted(messages)
|
||||
|
||||
result := make([]smsgateway.Message, len(messages))
|
||||
for i, v := range messages {
|
||||
var ttl *uint64 = nil
|
||||
if !v.ValidUntil.IsZero() {
|
||||
delta := time.Until(v.ValidUntil).Seconds()
|
||||
if delta > 0 {
|
||||
deltaInt := uint64(delta)
|
||||
ttl = &deltaInt
|
||||
} else {
|
||||
deltaInt := uint64(0)
|
||||
ttl = &deltaInt
|
||||
}
|
||||
}
|
||||
|
||||
result[i] = smsgateway.Message{
|
||||
ID: v.ExtID,
|
||||
Message: v.Message,
|
||||
TTL: ttl,
|
||||
PhoneNumbers: s.recipientsToDomain(v.Recipients),
|
||||
}
|
||||
}
|
||||
@ -98,10 +113,16 @@ func (s *MessagesService) Enqeue(device models.Device, message smsgateway.Messag
|
||||
}
|
||||
}
|
||||
|
||||
validUntil := time.Time{}
|
||||
if message.TTL != nil && *message.TTL > 0 {
|
||||
validUntil = time.Now().Add(time.Duration(*message.TTL) * time.Second)
|
||||
}
|
||||
|
||||
msg := models.Message{
|
||||
DeviceID: device.ID,
|
||||
ExtID: message.ID,
|
||||
Message: message.Message,
|
||||
ValidUntil: validUntil,
|
||||
Recipients: s.recipientsToModel(message.PhoneNumbers),
|
||||
}
|
||||
if msg.ExtID == "" {
|
||||
@ -129,6 +150,22 @@ func (s *MessagesService) Enqeue(device models.Device, message smsgateway.Messag
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (s *MessagesService) filterTimeouted(messages []models.Message) []models.Message {
|
||||
result := make([]models.Message, 0, len(messages))
|
||||
for _, v := range messages {
|
||||
if v.ValidUntil.IsZero() || time.Now().Before(v.ValidUntil) {
|
||||
result = append(result, v)
|
||||
} else if v.State == models.MessageStatePending {
|
||||
v.State = models.MessageStateFailed
|
||||
for i, _ := range v.Recipients {
|
||||
v.Recipients[i].State = models.MessageStateFailed
|
||||
}
|
||||
s.Messages.UpdateState(&v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *MessagesService) recipientsToDomain(input []models.MessageRecipient) []string {
|
||||
output := make([]string, len(input))
|
||||
|
||||
|
||||
34
internal/smsgateway/tasks/module.go
Normal file
34
internal/smsgateway/tasks/module.go
Normal file
@ -0,0 +1,34 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
microbase "bitbucket.org/soft-c/gomicrobase"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
microbase.RegisterOnStartedListener(task)
|
||||
}
|
||||
|
||||
func task(ctx context.Context, c chan error, d *gorm.DB) error {
|
||||
ticker := time.NewTicker(60 * time.Second)
|
||||
go func() {
|
||||
defer func() {
|
||||
c <- nil
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
fmt.Println("tick")
|
||||
case <-ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@ -14,6 +14,7 @@ const (
|
||||
type Message struct {
|
||||
ID string `json:"id,omitempty" validate:"omitempty,max=36" example:"PyDmBQZZXYmyxMwED8Fzy"` // Идентификатор
|
||||
Message string `json:"message" validate:"required,max=256" example:"Hello World!"` // Текст сообщения
|
||||
TTL *uint64 `json:"ttl" validate:"omitempty,min=5" example:"86400"` // Время жизни сообщения в секундах
|
||||
PhoneNumbers []string `json:"phoneNumbers" validate:"required,min=1,max=100,dive,required,min=10" example:"79990001234"` // Получатели
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user