mirror of
https://github.com/makayabou/asg-server.git
synced 2026-05-02 17:43:36 +02:00
[settings] introduce settings module
This commit is contained in:
parent
dfe1341ec8
commit
d8f9864e52
2
go.mod
2
go.mod
@ -6,7 +6,7 @@ toolchain go1.23.2
|
||||
|
||||
require (
|
||||
firebase.google.com/go/v4 v4.12.1
|
||||
github.com/android-sms-gateway/client-go v1.5.8
|
||||
github.com/android-sms-gateway/client-go v1.6.0
|
||||
github.com/ansrivas/fiberprometheus/v2 v2.6.1
|
||||
github.com/capcom6/go-helpers v0.2.0
|
||||
github.com/capcom6/go-infra-fx v0.2.1
|
||||
|
||||
14
go.sum
14
go.sum
@ -26,12 +26,14 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/android-sms-gateway/client-go v1.5.7 h1:1L9Ot3yc+5DtGaDOCUj4/8DEECWyfo4IoPyL+oXnzyE=
|
||||
github.com/android-sms-gateway/client-go v1.5.7/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
|
||||
github.com/android-sms-gateway/client-go v1.5.8-0.20250516025314-5876d8deb355 h1:fctR5OH1c7g1zWEfp4K+fCZkY4+tZwTiKr/rN5N2yS8=
|
||||
github.com/android-sms-gateway/client-go v1.5.8-0.20250516025314-5876d8deb355/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
|
||||
github.com/android-sms-gateway/client-go v1.5.8 h1:t9630c1Hv8u/MjwQ8epJ0iDpt3VXurSNFC91CFEjM/M=
|
||||
github.com/android-sms-gateway/client-go v1.5.8/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
|
||||
github.com/android-sms-gateway/client-go v1.5.9-0.20250522134006-6e8b4dd3057a h1:TSmfm+KOsR1Ie10nZEjCVDepa1bEPin0NAgEUOSJiqw=
|
||||
github.com/android-sms-gateway/client-go v1.5.9-0.20250522134006-6e8b4dd3057a/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
|
||||
github.com/android-sms-gateway/client-go v1.5.9-0.20250522231449-9e0855eff19f h1:VYrL6YbkQ49pcyiXTYcR5LN1WpNy1Tc684XjeE1UCvw=
|
||||
github.com/android-sms-gateway/client-go v1.5.9-0.20250522231449-9e0855eff19f/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
|
||||
github.com/android-sms-gateway/client-go v1.5.9-0.20250524095300-2e41cae07049 h1:kdyVkqrgKDSI13JOKXVFz1al3IxfJPcbUaJvSXF6z+0=
|
||||
github.com/android-sms-gateway/client-go v1.5.9-0.20250524095300-2e41cae07049/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4=
|
||||
github.com/android-sms-gateway/client-go v1.6.0 h1:3hN0XEUnNrweBl5Xx3IfE5zyq5ihm7fB0dhuTZBKlns=
|
||||
github.com/android-sms-gateway/client-go v1.6.0/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=
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/messages"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/metrics"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/push"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/settings"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/webhooks"
|
||||
"github.com/capcom6/go-infra-fx/cli"
|
||||
"github.com/capcom6/go-infra-fx/db"
|
||||
@ -40,6 +41,7 @@ var Module = fx.Module(
|
||||
messages.Module,
|
||||
health.Module,
|
||||
webhooks.Module,
|
||||
settings.Module,
|
||||
devices.Module,
|
||||
metrics.Module,
|
||||
cleaner.Module,
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/logs"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/messages"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/settings"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/webhooks"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth"
|
||||
"github.com/go-playground/validator/v10"
|
||||
@ -21,6 +22,7 @@ type ThirdPartyHandlerParams struct {
|
||||
MessagesHandler *messages.ThirdPartyController
|
||||
WebhooksHandler *webhooks.ThirdPartyController
|
||||
DevicesHandler *devices.ThirdPartyController
|
||||
SettingsHandler *settings.ThirdPartyController
|
||||
LogsHandler *logs.ThirdPartyController
|
||||
|
||||
AuthSvc *auth.Service
|
||||
@ -36,6 +38,7 @@ type thirdPartyHandler struct {
|
||||
messagesHandler *messages.ThirdPartyController
|
||||
webhooksHandler *webhooks.ThirdPartyController
|
||||
devicesHandler *devices.ThirdPartyController
|
||||
settingsHandler *settings.ThirdPartyController
|
||||
logsHandler *logs.ThirdPartyController
|
||||
|
||||
authSvc *auth.Service
|
||||
@ -57,6 +60,8 @@ func (h *thirdPartyHandler) Register(router fiber.Router) {
|
||||
h.devicesHandler.Register(router.Group("/device")) // TODO: remove after 2025-07-11
|
||||
h.devicesHandler.Register(router.Group("/devices"))
|
||||
|
||||
h.settingsHandler.Register(router.Group("/settings"))
|
||||
|
||||
h.webhooksHandler.Register(router.Group("/webhooks"))
|
||||
|
||||
h.logsHandler.Register(router.Group("/logs"))
|
||||
@ -69,6 +74,7 @@ func newThirdPartyHandler(params ThirdPartyHandlerParams) *thirdPartyHandler {
|
||||
messagesHandler: params.MessagesHandler,
|
||||
webhooksHandler: params.WebhooksHandler,
|
||||
devicesHandler: params.DevicesHandler,
|
||||
settingsHandler: params.SettingsHandler,
|
||||
logsHandler: params.LogsHandler,
|
||||
authSvc: params.AuthSvc,
|
||||
}
|
||||
|
||||
@ -32,9 +32,11 @@ func TestDeviceToDTO(t *testing.T) {
|
||||
ID: "test-id",
|
||||
Name: anys.AsPointer("test-name"),
|
||||
LastSeen: lastSeenAt,
|
||||
TimedModel: models.TimedModel{
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
SoftDeletableModel: models.SoftDeletableModel{
|
||||
TimedModel: models.TimedModel{
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: smsgateway.Device{
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"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/handlers/middlewares/userauth"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/settings"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/webhooks"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/auth"
|
||||
@ -33,6 +34,7 @@ type mobileHandler struct {
|
||||
messagesSvc *messages.Service
|
||||
|
||||
webhooksCtrl *webhooks.MobileController
|
||||
settingsCtrl *settings.MobileController
|
||||
|
||||
idGen func() string
|
||||
}
|
||||
@ -303,6 +305,8 @@ func (h *mobileHandler) Register(router fiber.Router) {
|
||||
router.Patch("/user/password", deviceauth.WithDevice(h.changePassword))
|
||||
|
||||
h.webhooksCtrl.Register(router.Group("/webhooks"))
|
||||
|
||||
h.settingsCtrl.Register(router.Group("/settings"))
|
||||
}
|
||||
|
||||
type mobileHandlerParams struct {
|
||||
@ -316,6 +320,7 @@ type mobileHandlerParams struct {
|
||||
MessagesSvc *messages.Service
|
||||
|
||||
WebhooksCtrl *webhooks.MobileController
|
||||
SettingsCtrl *settings.MobileController
|
||||
}
|
||||
|
||||
func newMobileHandler(params mobileHandlerParams) *mobileHandler {
|
||||
@ -327,6 +332,7 @@ func newMobileHandler(params mobileHandlerParams) *mobileHandler {
|
||||
devicesSvc: params.DevicesSvc,
|
||||
messagesSvc: params.MessagesSvc,
|
||||
webhooksCtrl: params.WebhooksCtrl,
|
||||
settingsCtrl: params.SettingsCtrl,
|
||||
idGen: idGen,
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/devices"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/logs"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/messages"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/settings"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/webhooks"
|
||||
"github.com/capcom6/go-infra-fx/http"
|
||||
"go.uber.org/fx"
|
||||
@ -27,6 +28,8 @@ var Module = fx.Module(
|
||||
webhooks.NewThirdPartyController,
|
||||
webhooks.NewMobileController,
|
||||
devices.NewThirdPartyController,
|
||||
settings.NewThirdPartyController,
|
||||
settings.NewMobileController,
|
||||
logs.NewThirdPartyController,
|
||||
fx.Private,
|
||||
),
|
||||
|
||||
137
internal/sms-gateway/handlers/settings/3rdparty.go
Normal file
137
internal/sms-gateway/handlers/settings/3rdparty.go
Normal file
@ -0,0 +1,137 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"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/middlewares/userauth"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/devices"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/settings"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type thirdPartyControllerParams struct {
|
||||
fx.In
|
||||
|
||||
DevicesSvc *devices.Service
|
||||
SettingsSvc *settings.Service
|
||||
|
||||
Validator *validator.Validate
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
type ThirdPartyController struct {
|
||||
base.Handler
|
||||
|
||||
devicesSvc *devices.Service
|
||||
settingsSvc *settings.Service
|
||||
}
|
||||
|
||||
// @Summary Get settings
|
||||
// @Description Returns settings for a specific user
|
||||
// @Security ApiAuth
|
||||
// @Tags User, Settings
|
||||
// @Produce json
|
||||
// @Success 200 {object} smsgateway.DeviceSettings "Settings"
|
||||
// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
|
||||
// @Router /3rdparty/v1/settings [get]
|
||||
//
|
||||
// Get settings
|
||||
func (h *ThirdPartyController) get(user models.User, c *fiber.Ctx) error {
|
||||
settings, err := h.settingsSvc.GetSettings(user.ID, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get settings: %w", err)
|
||||
}
|
||||
|
||||
return c.JSON(settings)
|
||||
}
|
||||
|
||||
// @Summary Update settings
|
||||
// @Description Updates settings for a specific user
|
||||
// @Security ApiAuth
|
||||
// @Tags User, Settings
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body smsgateway.DeviceSettings true "Settings"
|
||||
// @Success 200 {object} object "Settings updated"
|
||||
// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request"
|
||||
// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
|
||||
// @Router /3rdparty/v1/settings [put]
|
||||
//
|
||||
// Update settings
|
||||
func (h *ThirdPartyController) put(user models.User, c *fiber.Ctx) error {
|
||||
if err := h.BodyParserValidator(c, &smsgateway.DeviceSettings{}); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid settings format: %v", err))
|
||||
}
|
||||
|
||||
settings := make(map[string]any, 8)
|
||||
|
||||
if err := c.BodyParser(&settings); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Failed to parse request body: %v", err))
|
||||
}
|
||||
|
||||
updated, err := h.settingsSvc.ReplaceSettings(user.ID, settings)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't update settings: %w", err)
|
||||
}
|
||||
|
||||
return c.JSON(updated)
|
||||
}
|
||||
|
||||
// @Summary Partially update settings
|
||||
// @Description Partially updates settings for a specific user
|
||||
// @Security ApiAuth
|
||||
// @Tags User, Settings
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body smsgateway.DeviceSettings true "Settings"
|
||||
// @Success 200 {object} object "Settings updated"
|
||||
// @Failure 400 {object} smsgateway.ErrorResponse "Invalid request"
|
||||
// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
|
||||
// @Router /3rdparty/v1/settings [patch]
|
||||
//
|
||||
// Partially update settings
|
||||
func (h *ThirdPartyController) patch(user models.User, c *fiber.Ctx) error {
|
||||
if err := h.BodyParserValidator(c, &smsgateway.DeviceSettings{}); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid settings format: %v", err))
|
||||
}
|
||||
|
||||
settings := make(map[string]any, 8)
|
||||
|
||||
if err := c.BodyParser(&settings); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Failed to parse request body: %v", err))
|
||||
}
|
||||
|
||||
updated, err := h.settingsSvc.UpdateSettings(user.ID, settings)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't update settings: %w", err)
|
||||
}
|
||||
|
||||
return c.JSON(updated)
|
||||
}
|
||||
|
||||
func (h *ThirdPartyController) Register(app fiber.Router) {
|
||||
app.Get("", userauth.WithUser(h.get))
|
||||
app.Patch("", userauth.WithUser(h.patch))
|
||||
app.Put("", userauth.WithUser(h.put))
|
||||
}
|
||||
|
||||
func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyController {
|
||||
return &ThirdPartyController{
|
||||
Handler: base.Handler{
|
||||
Logger: params.Logger.Named("settings"),
|
||||
Validator: params.Validator,
|
||||
},
|
||||
devicesSvc: params.DevicesSvc,
|
||||
settingsSvc: params.SettingsSvc,
|
||||
}
|
||||
}
|
||||
64
internal/sms-gateway/handlers/settings/mobile.go
Normal file
64
internal/sms-gateway/handlers/settings/mobile.go
Normal file
@ -0,0 +1,64 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
|
||||
"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/devices"
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/settings"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type mobileControllerParams struct {
|
||||
fx.In
|
||||
|
||||
DevicesSvc *devices.Service
|
||||
SettingsSvc *settings.Service
|
||||
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
type MobileController struct {
|
||||
base.Handler
|
||||
|
||||
devicesSvc *devices.Service
|
||||
settingsSvc *settings.Service
|
||||
}
|
||||
|
||||
// @Summary Get settings
|
||||
// @Description Returns settings for a device
|
||||
// @Security MobileToken
|
||||
// @Tags Device, Settings
|
||||
// @Produce json
|
||||
// @Success 200 {object} smsgateway.DeviceSettings "Settings"
|
||||
// @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized"
|
||||
// @Failure 500 {object} smsgateway.ErrorResponse "Internal server error"
|
||||
// @Router /mobile/v1/settings [get]
|
||||
//
|
||||
// Get settings
|
||||
func (h *MobileController) get(device models.Device, c *fiber.Ctx) error {
|
||||
settings, err := h.settingsSvc.GetSettings(device.UserID, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get settings for device %s (user ID: %s): %w", device.ID, device.UserID, err)
|
||||
}
|
||||
|
||||
return c.JSON(settings)
|
||||
}
|
||||
|
||||
func (h *MobileController) Register(router fiber.Router) {
|
||||
router.Get("", deviceauth.WithDevice(h.get))
|
||||
}
|
||||
|
||||
func NewMobileController(params mobileControllerParams) *MobileController {
|
||||
return &MobileController{
|
||||
Handler: base.Handler{
|
||||
Logger: params.Logger.Named("settings"),
|
||||
},
|
||||
devicesSvc: params.DevicesSvc,
|
||||
settingsSvc: params.SettingsSvc,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE `device_settings` (
|
||||
`user_id` varchar(32) NOT NULL,
|
||||
`settings` json NOT NULL,
|
||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (`user_id`),
|
||||
CONSTRAINT `fk_device_settings_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
---
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE `device_settings`;
|
||||
-- +goose StatementEnd
|
||||
@ -15,8 +15,12 @@ const (
|
||||
)
|
||||
|
||||
type TimedModel struct {
|
||||
CreatedAt time.Time `gorm:"->;not null;autocreatetime:false;default:CURRENT_TIMESTAMP(3)"`
|
||||
UpdatedAt time.Time `gorm:"->;not null;autoupdatetime:false;default:CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"`
|
||||
CreatedAt time.Time `gorm:"->;not null;autocreatetime:false;default:CURRENT_TIMESTAMP(3)"`
|
||||
UpdatedAt time.Time `gorm:"->;not null;autoupdatetime:false;default:CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"`
|
||||
}
|
||||
|
||||
type SoftDeletableModel struct {
|
||||
TimedModel
|
||||
DeletedAt *time.Time `gorm:"<-:update"`
|
||||
}
|
||||
|
||||
@ -25,7 +29,7 @@ type User struct {
|
||||
PasswordHash string `gorm:"not null;type:varchar(72)"`
|
||||
Devices []Device `gorm:"-,foreignKey:UserID;constraint:OnDelete:CASCADE"`
|
||||
|
||||
TimedModel
|
||||
SoftDeletableModel
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
@ -38,7 +42,7 @@ type Device struct {
|
||||
|
||||
UserID string `gorm:"not null;type:varchar(32)"`
|
||||
|
||||
TimedModel
|
||||
SoftDeletableModel
|
||||
}
|
||||
|
||||
func (d *Device) IsEmpty() bool {
|
||||
@ -67,7 +71,7 @@ type Message struct {
|
||||
Recipients []MessageRecipient `gorm:"foreignKey:MessageID;constraint:OnDelete:CASCADE"`
|
||||
States []MessageState `gorm:"foreignKey:MessageID;constraint:OnDelete:CASCADE"`
|
||||
|
||||
TimedModel
|
||||
SoftDeletableModel
|
||||
}
|
||||
|
||||
type MessageRecipient struct {
|
||||
|
||||
@ -168,6 +168,7 @@ func (s *Service) Notify(userID string, deviceID *string, event *domain.Event) e
|
||||
}
|
||||
|
||||
errs := make([]error, 0, len(devices))
|
||||
notifiedCount := 0
|
||||
for _, device := range devices {
|
||||
if device.PushToken == nil {
|
||||
s.logger.Info("Device has no push token", zap.String("user_id", userID), zap.String("device_id", device.ID))
|
||||
@ -177,10 +178,12 @@ func (s *Service) Notify(userID string, deviceID *string, event *domain.Event) e
|
||||
if err := s.Enqueue(*device.PushToken, event); err != nil {
|
||||
s.logger.Error("Failed to send push notification", zap.String("user_id", userID), zap.String("device_id", device.ID), zap.Error(err))
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
notifiedCount++
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("Notified devices", append(logFields, zap.Int("count", len(devices)))...)
|
||||
s.logger.Info("Notified devices", append(logFields, zap.Int("count", notifiedCount), zap.Int("total", len(devices)))...)
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
@ -47,3 +47,7 @@ func NewMessagesExportRequestedEvent(since, until time.Time) *domain.Event {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func NewSettingsUpdatedEvent() *domain.Event {
|
||||
return domain.NewEvent(smsgateway.PushSettingsUpdated, nil)
|
||||
}
|
||||
|
||||
24
internal/sms-gateway/modules/settings/models.go
Normal file
24
internal/sms-gateway/modules/settings/models.go
Normal file
@ -0,0 +1,24 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DeviceSettings struct {
|
||||
UserID string `gorm:"primaryKey;not null;type:varchar(32)"`
|
||||
Settings map[string]any `gorm:"not null;type:json;serializer:json"`
|
||||
|
||||
User models.User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
|
||||
|
||||
models.TimedModel
|
||||
}
|
||||
|
||||
func Migrate(db *gorm.DB) error {
|
||||
if err := db.AutoMigrate(&DeviceSettings{}); err != nil {
|
||||
return fmt.Errorf("device_settings migration failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
25
internal/sms-gateway/modules/settings/module.go
Normal file
25
internal/sms-gateway/modules/settings/module.go
Normal file
@ -0,0 +1,25 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"github.com/capcom6/go-infra-fx/db"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var Module = fx.Module(
|
||||
"settings",
|
||||
fx.Decorate(func(log *zap.Logger) *zap.Logger {
|
||||
return log.Named("settings")
|
||||
}),
|
||||
fx.Provide(
|
||||
newRepository,
|
||||
fx.Private,
|
||||
),
|
||||
fx.Provide(
|
||||
NewService,
|
||||
),
|
||||
)
|
||||
|
||||
func init() {
|
||||
db.RegisterMigration(Migrate)
|
||||
}
|
||||
67
internal/sms-gateway/modules/settings/repository.go
Normal file
67
internal/sms-gateway/modules/settings/repository.go
Normal file
@ -0,0 +1,67 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type repository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// GetSettings retrieves the device settings for a user by their userID.
|
||||
func (r *repository) GetSettings(userID string) (*DeviceSettings, error) {
|
||||
settings := &DeviceSettings{}
|
||||
err := r.db.Where("user_id = ?", userID).Limit(1).Find(settings).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// UpdateSettings updates the settings for a user.
|
||||
func (r *repository) UpdateSettings(settings *DeviceSettings) (*DeviceSettings, error) {
|
||||
var updatedSettings *DeviceSettings
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
source := &DeviceSettings{UserID: settings.UserID}
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Limit(1).Find(source).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if source.Settings == nil {
|
||||
source.Settings = map[string]any{}
|
||||
}
|
||||
|
||||
var err error
|
||||
settings.Settings, err = appendMap(source.Settings, settings.Settings, rules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Clauses(clause.OnConflict{UpdateAll: true}).Create(settings).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Return the updated settings
|
||||
updatedSettings = settings
|
||||
return nil
|
||||
})
|
||||
return updatedSettings, err
|
||||
}
|
||||
|
||||
// ReplaceSettings replaces the settings for a user.
|
||||
//
|
||||
// This function will overwrite all existing settings for the user.
|
||||
func (r *repository) ReplaceSettings(settings *DeviceSettings) (*DeviceSettings, error) {
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
return tx.Save(settings).Error
|
||||
})
|
||||
return settings, err
|
||||
}
|
||||
|
||||
func newRepository(db *gorm.DB) *repository {
|
||||
return &repository{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
93
internal/sms-gateway/modules/settings/service.go
Normal file
93
internal/sms-gateway/modules/settings/service.go
Normal file
@ -0,0 +1,93 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/push"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ServiceParams struct {
|
||||
fx.In
|
||||
|
||||
Repository *repository
|
||||
|
||||
Logger *zap.Logger
|
||||
|
||||
PushSvc *push.Service
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
settings *repository
|
||||
|
||||
logger *zap.Logger
|
||||
|
||||
pushSvc *push.Service
|
||||
}
|
||||
|
||||
func (s *Service) GetSettings(userID string, public bool) (map[string]any, error) {
|
||||
settings, err := s.settings.GetSettings(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !public {
|
||||
return settings.Settings, nil
|
||||
}
|
||||
|
||||
return filterMap(settings.Settings, rulesPublic)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateSettings(userID string, settings map[string]any) (map[string]any, error) {
|
||||
filtered, err := filterMap(settings, rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedSettings, err := s.settings.UpdateSettings(&DeviceSettings{
|
||||
UserID: userID,
|
||||
Settings: filtered,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.notifyDevices(userID)
|
||||
|
||||
return filterMap(updatedSettings.Settings, rulesPublic)
|
||||
}
|
||||
|
||||
func (s *Service) ReplaceSettings(userID string, settings map[string]any) (map[string]any, error) {
|
||||
filtered, err := filterMap(settings, rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updated, err := s.settings.ReplaceSettings(&DeviceSettings{
|
||||
UserID: userID,
|
||||
Settings: filtered,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.notifyDevices(userID)
|
||||
|
||||
return filterMap(updated.Settings, rulesPublic)
|
||||
}
|
||||
|
||||
// notifyDevices asynchronously notifies all the user's devices.
|
||||
func (s *Service) notifyDevices(userID string) {
|
||||
go func(userID string) {
|
||||
if err := s.pushSvc.Notify(userID, nil, push.NewSettingsUpdatedEvent()); err != nil {
|
||||
s.logger.Error("can't notify devices", zap.Error(err))
|
||||
}
|
||||
}(userID)
|
||||
}
|
||||
|
||||
func NewService(params ServiceParams) *Service {
|
||||
return &Service{
|
||||
settings: params.Repository,
|
||||
logger: params.Logger.Named("service"),
|
||||
pushSvc: params.PushSvc,
|
||||
}
|
||||
}
|
||||
112
internal/sms-gateway/modules/settings/utils.go
Normal file
112
internal/sms-gateway/modules/settings/utils.go
Normal file
@ -0,0 +1,112 @@
|
||||
package settings
|
||||
|
||||
import "fmt"
|
||||
|
||||
var rules = map[string]any{
|
||||
"encryption": map[string]any{
|
||||
"passphrase": "",
|
||||
},
|
||||
"messages": map[string]any{
|
||||
"send_interval_min": "",
|
||||
"send_interval_max": "",
|
||||
"limit_period": "",
|
||||
"limit_value": "",
|
||||
"sim_selection_mode": "",
|
||||
"log_lifetime_days": "",
|
||||
},
|
||||
"ping": map[string]any{
|
||||
"interval_seconds": "",
|
||||
},
|
||||
"logs": map[string]any{
|
||||
"lifetime_days": "",
|
||||
},
|
||||
"webhooks": map[string]any{
|
||||
"internet_required": "",
|
||||
"retry_count": "",
|
||||
"signing_key": "",
|
||||
},
|
||||
}
|
||||
|
||||
var rulesPublic = map[string]any{
|
||||
"encryption": map[string]any{},
|
||||
"messages": map[string]any{
|
||||
"send_interval_min": "",
|
||||
"send_interval_max": "",
|
||||
"limit_period": "",
|
||||
"limit_value": "",
|
||||
"sim_selection_mode": "",
|
||||
"log_lifetime_days": "",
|
||||
},
|
||||
"ping": map[string]any{
|
||||
"interval_seconds": "",
|
||||
},
|
||||
"logs": map[string]any{
|
||||
"lifetime_days": "",
|
||||
},
|
||||
"webhooks": map[string]any{
|
||||
"internet_required": "",
|
||||
"retry_count": "",
|
||||
},
|
||||
}
|
||||
|
||||
func filterMap(m map[string]any, r map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
|
||||
result := make(map[string]any)
|
||||
for field, rule := range r {
|
||||
if ruleObj, ok := rule.(map[string]any); ok {
|
||||
if dataObj, ok := m[field].(map[string]any); ok {
|
||||
result[field], err = filterMap(dataObj, ruleObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if m[field] == nil {
|
||||
continue
|
||||
} else {
|
||||
return nil, fmt.Errorf("the field: '%s' is not a map to dive", field)
|
||||
}
|
||||
} else if _, ok := rule.(string); ok {
|
||||
if _, ok := m[field]; !ok {
|
||||
continue
|
||||
}
|
||||
result[field] = m[field]
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func appendMap(m1, m2 map[string]any, rules map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
|
||||
for field, rule := range rules {
|
||||
if ruleObj, ok := rule.(map[string]any); ok {
|
||||
if dataObj, ok := m2[field].(map[string]any); ok {
|
||||
if m1Field, ok := m1[field].(map[string]any); ok {
|
||||
m1[field], err = appendMap(m1Field, dataObj, ruleObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Initialize if not present or not a map
|
||||
newMap := make(map[string]any)
|
||||
m1[field], err = appendMap(newMap, dataObj, ruleObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else if m2[field] == nil {
|
||||
continue
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected field '%s' to be a map, but got %T", field, m2[field])
|
||||
}
|
||||
} else if _, ok := rule.(string); ok {
|
||||
if _, ok := m2[field]; !ok {
|
||||
continue
|
||||
}
|
||||
m1[field] = m2[field]
|
||||
}
|
||||
}
|
||||
|
||||
return m1, nil
|
||||
}
|
||||
@ -19,7 +19,7 @@ type Webhook struct {
|
||||
User models.User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
|
||||
Device *models.Device `gorm:"foreignKey:DeviceID;constraint:OnDelete:CASCADE"`
|
||||
|
||||
models.TimedModel
|
||||
models.SoftDeletableModel
|
||||
}
|
||||
|
||||
func Migrate(db *gorm.DB) error {
|
||||
|
||||
@ -80,3 +80,56 @@ Authorization: Basic {{localCredentials}}
|
||||
###
|
||||
GET {{localUrl}}/logs?from=2025-02-05T20:39:46.190%2B07:00 HTTP/1.1
|
||||
Authorization: Basic {{localCredentials}}
|
||||
|
||||
###
|
||||
GET {{localUrl}}/settings HTTP/1.1
|
||||
Authorization: Basic {{localCredentials}}
|
||||
|
||||
###
|
||||
PATCH {{localUrl}}/settings HTTP/1.1
|
||||
Authorization: Basic {{localCredentials}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"encryption": {
|
||||
"passphrase": null
|
||||
},
|
||||
"gateway": {
|
||||
"cloud_url": "https://api.sms-gate.app/mobile/v1",
|
||||
"private_token": null
|
||||
},
|
||||
"messages": {
|
||||
"send_interval_min": null,
|
||||
"send_interval_max": null,
|
||||
"limit_period": "Disabled",
|
||||
"limit_value": null,
|
||||
"sim_selection_mode": "OSDefault",
|
||||
"log_lifetime_days": null
|
||||
},
|
||||
"localserver": {
|
||||
"PORT": 8080
|
||||
},
|
||||
"ping": {
|
||||
"interval_seconds": null
|
||||
},
|
||||
"logs": {
|
||||
"lifetime_days": 30
|
||||
},
|
||||
"webhooks": {
|
||||
"internet_required": true,
|
||||
"retry_count": 1,
|
||||
"signing_key": null
|
||||
}
|
||||
}
|
||||
|
||||
###
|
||||
PATCH {{localUrl}}/settings HTTP/1.1
|
||||
Authorization: Basic {{localCredentials}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"webhooks": {
|
||||
"internet_required": true,
|
||||
"retry_count": 1
|
||||
}
|
||||
}
|
||||
@ -71,3 +71,7 @@ Content-Type: application/json
|
||||
"currentPassword": "wsmgz1akhoo24o",
|
||||
"newPassword": "wsmgz1akhoo24o"
|
||||
}
|
||||
|
||||
###
|
||||
GET {{baseUrl}}/settings HTTP/1.1
|
||||
Authorization: Bearer {{mobileToken}}
|
||||
|
||||
@ -103,6 +103,52 @@ Authorization: Basic {{credentials}}
|
||||
GET {{baseUrl}}/api/3rdparty/v1/logs HTTP/1.1
|
||||
Authorization: Basic {{credentials}}
|
||||
|
||||
###
|
||||
GET {{baseUrl}}/3rdparty/v1/settings HTTP/1.1
|
||||
Authorization: Basic {{credentials}}
|
||||
|
||||
###
|
||||
PATCH {{baseUrl}}/3rdparty/v1/settings HTTP/1.1
|
||||
Authorization: Basic {{credentials}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"messages": {
|
||||
"send_interval_min": null,
|
||||
"send_interval_max": 1
|
||||
}
|
||||
}
|
||||
|
||||
###
|
||||
PUT {{baseUrl}}/3rdparty/v1/settings HTTP/1.1
|
||||
Authorization: Basic {{credentials}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"encryption": {
|
||||
"passphrase": "{{$guid}}"
|
||||
},
|
||||
"messages": {
|
||||
"send_interval_min": null,
|
||||
"send_interval_max": null,
|
||||
"limit_period": "Disabled",
|
||||
"limit_value": null,
|
||||
"sim_selection_mode": "OSDefault",
|
||||
"log_lifetime_days": null
|
||||
},
|
||||
"ping": {
|
||||
"interval_seconds": null
|
||||
},
|
||||
"logs": {
|
||||
"lifetime_days": 30
|
||||
},
|
||||
"webhooks": {
|
||||
"internet_required": true,
|
||||
"retry_count": 1,
|
||||
"signing_key": "{{$guid}}"
|
||||
}
|
||||
}
|
||||
|
||||
###
|
||||
GET http://localhost:3000/metrics HTTP/1.1
|
||||
|
||||
|
||||
@ -393,6 +393,156 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/3rdparty/v1/settings": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Returns settings for a specific user",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User",
|
||||
"Settings"
|
||||
],
|
||||
"summary": "Get settings",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Settings",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/smsgateway.DeviceSettings"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/smsgateway.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/smsgateway.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Updates settings for a specific user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User",
|
||||
"Settings"
|
||||
],
|
||||
"summary": "Update settings",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Settings",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/smsgateway.DeviceSettings"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Settings updated",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"ApiAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Partially updates settings for a specific user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User",
|
||||
"Settings"
|
||||
],
|
||||
"summary": "Partially update settings",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Settings",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/smsgateway.DeviceSettings"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Settings updated",
|
||||
"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/webhooks": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -767,6 +917,44 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mobile/v1/settings": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"MobileToken": []
|
||||
}
|
||||
],
|
||||
"description": "Returns settings for a device",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Device",
|
||||
"Settings"
|
||||
],
|
||||
"summary": "Get settings",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Settings",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/smsgateway.DeviceSettings"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/smsgateway.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/smsgateway.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mobile/v1/user/code": {
|
||||
"get": {
|
||||
"security": [
|
||||
@ -985,6 +1173,59 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"smsgateway.DeviceSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"encryption": {
|
||||
"description": "Encryption contains settings related to message encryption.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/smsgateway.SettingsEncryption"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gateway": {
|
||||
"description": "Gateway contains settings related to the Cloud/Private server configuration.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/smsgateway.SettingsGateway"
|
||||
}
|
||||
]
|
||||
},
|
||||
"logs": {
|
||||
"description": "Logs contains settings related to logging.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/smsgateway.SettingsLogs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"messages": {
|
||||
"description": "Messages contains settings related to message handling.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/smsgateway.SettingsMessages"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ping": {
|
||||
"description": "Ping contains settings related to ping functionality.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/smsgateway.SettingsPing"
|
||||
}
|
||||
]
|
||||
},
|
||||
"webhooks": {
|
||||
"description": "Webhooks contains settings related to webhook functionality.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/smsgateway.SettingsWebhooks"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"smsgateway.ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -1075,6 +1316,21 @@
|
||||
"HealthStatusFail"
|
||||
]
|
||||
},
|
||||
"smsgateway.LimitPeriod": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Disabled",
|
||||
"PerMinute",
|
||||
"PerHour",
|
||||
"PerDay"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"Disabled",
|
||||
"PerMinute",
|
||||
"PerHour",
|
||||
"PerDay"
|
||||
]
|
||||
},
|
||||
"smsgateway.LogEntry": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -1507,12 +1763,14 @@
|
||||
"enum": [
|
||||
"MessageEnqueued",
|
||||
"WebhooksUpdated",
|
||||
"MessagesExportRequested"
|
||||
"MessagesExportRequested",
|
||||
"SettingsUpdated"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PushMessageEnqueued",
|
||||
"PushWebhooksUpdated",
|
||||
"PushMessagesExportRequested"
|
||||
"PushMessagesExportRequested",
|
||||
"PushSettingsUpdated"
|
||||
]
|
||||
},
|
||||
"smsgateway.PushNotification": {
|
||||
@ -1534,7 +1792,8 @@
|
||||
"enum": [
|
||||
"MessageEnqueued",
|
||||
"WebhooksUpdated",
|
||||
"MessagesExportRequested"
|
||||
"MessagesExportRequested",
|
||||
"SettingsUpdated"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
@ -1580,6 +1839,131 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"smsgateway.SettingsEncryption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"passphrase": {
|
||||
"description": "Passphrase is the encryption passphrase. If nil or empty, encryption is disabled.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smsgateway.SettingsGateway": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cloud_url": {
|
||||
"description": "CloudURL is the URL of the server. If nil, the Cloud Public server is used.\nMust be a valid HTTPs URL when provided.",
|
||||
"type": "string"
|
||||
},
|
||||
"private_token": {
|
||||
"description": "PrivateToken is the private token for authenticating with the Private Server.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smsgateway.SettingsLogs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lifetime_days": {
|
||||
"description": "LifetimeDays is the number of days to retain logs.\nMust be at least 1 when provided.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"smsgateway.SettingsMessages": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit_period": {
|
||||
"description": "LimitPeriod defines the period for message sending limits.\nValid values are \"Disabled\", \"PerMinute\", \"PerHour\", or \"PerDay\".",
|
||||
"enum": [
|
||||
"Disabled",
|
||||
"PerMinute",
|
||||
"PerHour",
|
||||
"PerDay"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/smsgateway.LimitPeriod"
|
||||
}
|
||||
]
|
||||
},
|
||||
"limit_value": {
|
||||
"description": "LimitValue is the maximum number of messages allowed per limit period.\nMust be at least 1 when provided.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"log_lifetime_days": {
|
||||
"description": "LogLifetimeDays is the number of days to retain message logs.\nMust be at least 1 when provided.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"send_interval_max": {
|
||||
"description": "SendIntervalMax is the maximum interval between message sends (in seconds).\nMust be at least 1 when provided and greater than or equal to SendIntervalMin.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"send_interval_min": {
|
||||
"description": "SendIntervalMin is the minimum interval between message sends (in seconds).\nMust be at least 1 when provided.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"sim_selection_mode": {
|
||||
"description": "SimSelectionMode defines how SIM cards are selected for sending messages.\nValid values are \"OSDefault\", \"RoundRobin\", or \"Random\".",
|
||||
"enum": [
|
||||
"OSDefault",
|
||||
"RoundRobin",
|
||||
"Random"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/smsgateway.SimSelectionMode"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"smsgateway.SettingsPing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"interval_seconds": {
|
||||
"description": "IntervalSeconds is the interval between ping requests (in seconds).\nMust be at least 1 when provided.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"smsgateway.SettingsWebhooks": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"internet_required": {
|
||||
"description": "InternetRequired indicates whether internet access is required for webhooks.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"retry_count": {
|
||||
"description": "RetryCount is the number of times to retry failed webhook deliveries.\nMust be at least 1 when provided.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"signing_key": {
|
||||
"description": "SigningKey is the secret key used for signing webhook payloads.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"smsgateway.SimSelectionMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"OSDefault",
|
||||
"RoundRobin",
|
||||
"Random"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"OSDefault",
|
||||
"RoundRobin",
|
||||
"Random"
|
||||
]
|
||||
},
|
||||
"smsgateway.Webhook": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@ -26,6 +26,34 @@ definitions:
|
||||
example: "2020-01-01T00:00:00Z"
|
||||
type: string
|
||||
type: object
|
||||
smsgateway.DeviceSettings:
|
||||
properties:
|
||||
encryption:
|
||||
allOf:
|
||||
- $ref: '#/definitions/smsgateway.SettingsEncryption'
|
||||
description: Encryption contains settings related to message encryption.
|
||||
gateway:
|
||||
allOf:
|
||||
- $ref: '#/definitions/smsgateway.SettingsGateway'
|
||||
description: Gateway contains settings related to the Cloud/Private server
|
||||
configuration.
|
||||
logs:
|
||||
allOf:
|
||||
- $ref: '#/definitions/smsgateway.SettingsLogs'
|
||||
description: Logs contains settings related to logging.
|
||||
messages:
|
||||
allOf:
|
||||
- $ref: '#/definitions/smsgateway.SettingsMessages'
|
||||
description: Messages contains settings related to message handling.
|
||||
ping:
|
||||
allOf:
|
||||
- $ref: '#/definitions/smsgateway.SettingsPing'
|
||||
description: Ping contains settings related to ping functionality.
|
||||
webhooks:
|
||||
allOf:
|
||||
- $ref: '#/definitions/smsgateway.SettingsWebhooks'
|
||||
description: Webhooks contains settings related to webhook functionality.
|
||||
type: object
|
||||
smsgateway.ErrorResponse:
|
||||
properties:
|
||||
code:
|
||||
@ -91,6 +119,18 @@ definitions:
|
||||
- HealthStatusPass
|
||||
- HealthStatusWarn
|
||||
- HealthStatusFail
|
||||
smsgateway.LimitPeriod:
|
||||
enum:
|
||||
- Disabled
|
||||
- PerMinute
|
||||
- PerHour
|
||||
- PerDay
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- Disabled
|
||||
- PerMinute
|
||||
- PerHour
|
||||
- PerDay
|
||||
smsgateway.LogEntry:
|
||||
properties:
|
||||
context:
|
||||
@ -420,11 +460,13 @@ definitions:
|
||||
- MessageEnqueued
|
||||
- WebhooksUpdated
|
||||
- MessagesExportRequested
|
||||
- SettingsUpdated
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- PushMessageEnqueued
|
||||
- PushWebhooksUpdated
|
||||
- PushMessagesExportRequested
|
||||
- PushSettingsUpdated
|
||||
smsgateway.PushNotification:
|
||||
properties:
|
||||
data:
|
||||
@ -441,6 +483,7 @@ definitions:
|
||||
- MessageEnqueued
|
||||
- WebhooksUpdated
|
||||
- MessagesExportRequested
|
||||
- SettingsUpdated
|
||||
example: MessageEnqueued
|
||||
token:
|
||||
description: The token of the device that receives the notification.
|
||||
@ -470,6 +513,117 @@ definitions:
|
||||
- phoneNumber
|
||||
- state
|
||||
type: object
|
||||
smsgateway.SettingsEncryption:
|
||||
properties:
|
||||
passphrase:
|
||||
description: Passphrase is the encryption passphrase. If nil or empty, encryption
|
||||
is disabled.
|
||||
type: string
|
||||
type: object
|
||||
smsgateway.SettingsGateway:
|
||||
properties:
|
||||
cloud_url:
|
||||
description: |-
|
||||
CloudURL is the URL of the server. If nil, the Cloud Public server is used.
|
||||
Must be a valid HTTPs URL when provided.
|
||||
type: string
|
||||
private_token:
|
||||
description: PrivateToken is the private token for authenticating with the
|
||||
Private Server.
|
||||
type: string
|
||||
type: object
|
||||
smsgateway.SettingsLogs:
|
||||
properties:
|
||||
lifetime_days:
|
||||
description: |-
|
||||
LifetimeDays is the number of days to retain logs.
|
||||
Must be at least 1 when provided.
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
smsgateway.SettingsMessages:
|
||||
properties:
|
||||
limit_period:
|
||||
allOf:
|
||||
- $ref: '#/definitions/smsgateway.LimitPeriod'
|
||||
description: |-
|
||||
LimitPeriod defines the period for message sending limits.
|
||||
Valid values are "Disabled", "PerMinute", "PerHour", or "PerDay".
|
||||
enum:
|
||||
- Disabled
|
||||
- PerMinute
|
||||
- PerHour
|
||||
- PerDay
|
||||
limit_value:
|
||||
description: |-
|
||||
LimitValue is the maximum number of messages allowed per limit period.
|
||||
Must be at least 1 when provided.
|
||||
minimum: 1
|
||||
type: integer
|
||||
log_lifetime_days:
|
||||
description: |-
|
||||
LogLifetimeDays is the number of days to retain message logs.
|
||||
Must be at least 1 when provided.
|
||||
minimum: 1
|
||||
type: integer
|
||||
send_interval_max:
|
||||
description: |-
|
||||
SendIntervalMax is the maximum interval between message sends (in seconds).
|
||||
Must be at least 1 when provided and greater than or equal to SendIntervalMin.
|
||||
minimum: 1
|
||||
type: integer
|
||||
send_interval_min:
|
||||
description: |-
|
||||
SendIntervalMin is the minimum interval between message sends (in seconds).
|
||||
Must be at least 1 when provided.
|
||||
minimum: 1
|
||||
type: integer
|
||||
sim_selection_mode:
|
||||
allOf:
|
||||
- $ref: '#/definitions/smsgateway.SimSelectionMode'
|
||||
description: |-
|
||||
SimSelectionMode defines how SIM cards are selected for sending messages.
|
||||
Valid values are "OSDefault", "RoundRobin", or "Random".
|
||||
enum:
|
||||
- OSDefault
|
||||
- RoundRobin
|
||||
- Random
|
||||
type: object
|
||||
smsgateway.SettingsPing:
|
||||
properties:
|
||||
interval_seconds:
|
||||
description: |-
|
||||
IntervalSeconds is the interval between ping requests (in seconds).
|
||||
Must be at least 1 when provided.
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
smsgateway.SettingsWebhooks:
|
||||
properties:
|
||||
internet_required:
|
||||
description: InternetRequired indicates whether internet access is required
|
||||
for webhooks.
|
||||
type: boolean
|
||||
retry_count:
|
||||
description: |-
|
||||
RetryCount is the number of times to retry failed webhook deliveries.
|
||||
Must be at least 1 when provided.
|
||||
minimum: 1
|
||||
type: integer
|
||||
signing_key:
|
||||
description: SigningKey is the secret key used for signing webhook payloads.
|
||||
type: string
|
||||
type: object
|
||||
smsgateway.SimSelectionMode:
|
||||
enum:
|
||||
- OSDefault
|
||||
- RoundRobin
|
||||
- Random
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- OSDefault
|
||||
- RoundRobin
|
||||
- Random
|
||||
smsgateway.Webhook:
|
||||
properties:
|
||||
deviceId:
|
||||
@ -770,6 +924,102 @@ paths:
|
||||
tags:
|
||||
- User
|
||||
- Messages
|
||||
/3rdparty/v1/settings:
|
||||
get:
|
||||
description: Returns settings for a specific user
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Settings
|
||||
schema:
|
||||
$ref: '#/definitions/smsgateway.DeviceSettings'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/smsgateway.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/smsgateway.ErrorResponse'
|
||||
security:
|
||||
- ApiAuth: []
|
||||
summary: Get settings
|
||||
tags:
|
||||
- User
|
||||
- Settings
|
||||
patch:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Partially updates settings for a specific user
|
||||
parameters:
|
||||
- description: Settings
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/smsgateway.DeviceSettings'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Settings updated
|
||||
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: Partially update settings
|
||||
tags:
|
||||
- User
|
||||
- Settings
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Updates settings for a specific user
|
||||
parameters:
|
||||
- description: Settings
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/smsgateway.DeviceSettings'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Settings updated
|
||||
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: Update settings
|
||||
tags:
|
||||
- User
|
||||
- Settings
|
||||
/3rdparty/v1/webhooks:
|
||||
get:
|
||||
description: Returns list of registered webhooks
|
||||
@ -1009,6 +1259,30 @@ paths:
|
||||
tags:
|
||||
- Device
|
||||
- Messages
|
||||
/mobile/v1/settings:
|
||||
get:
|
||||
description: Returns settings for a device
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Settings
|
||||
schema:
|
||||
$ref: '#/definitions/smsgateway.DeviceSettings'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/smsgateway.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/smsgateway.ErrorResponse'
|
||||
security:
|
||||
- MobileToken: []
|
||||
summary: Get settings
|
||||
tags:
|
||||
- Device
|
||||
- Settings
|
||||
/mobile/v1/user/code:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user