From 79682075ff27e991baeda2d34f5d2a0441cf5193 Mon Sep 17 00:00:00 2001 From: Aleksandr Soloshenko Date: Tue, 11 Jun 2024 09:09:37 +0700 Subject: [PATCH] [webhooks] notify devices on change --- api/swagger.json | 115 ++++++++++++------ api/swagger.yaml | 81 +++++++----- go.mod | 2 +- go.sum | 2 + internal/sms-gateway/app.go | 4 +- internal/sms-gateway/handlers/3rdparty.go | 10 +- internal/sms-gateway/handlers/mobile.go | 2 +- internal/sms-gateway/handlers/upstream.go | 2 +- .../sms-gateway/handlers/webhooks/3rdparty.go | 6 +- .../sms-gateway/handlers/webhooks/mobile.go | 2 +- internal/sms-gateway/modules/auth/service.go | 32 +++-- .../{services => modules/devices}/module.go | 12 +- .../sms-gateway/modules/devices/repository.go | 65 ++++++++++ .../modules/devices/repository_filter.go | 54 ++++++++ .../sms-gateway/modules/devices/service.go | 58 +++++++++ .../sms-gateway/modules/messages/service.go | 5 +- internal/sms-gateway/modules/push/service.go | 2 +- .../sms-gateway/modules/webhooks/service.go | 69 ++++++++++- internal/sms-gateway/repositories/devices.go | 56 --------- internal/sms-gateway/repositories/module.go | 1 - internal/sms-gateway/services/devices.go | 32 ----- 21 files changed, 410 insertions(+), 202 deletions(-) rename internal/sms-gateway/{services => modules/devices}/module.go (53%) create mode 100644 internal/sms-gateway/modules/devices/repository.go create mode 100644 internal/sms-gateway/modules/devices/repository_filter.go create mode 100644 internal/sms-gateway/modules/devices/service.go delete mode 100644 internal/sms-gateway/repositories/devices.go delete mode 100644 internal/sms-gateway/services/devices.go diff --git a/api/swagger.json b/api/swagger.json index ba373b5..a885ea9 100644 --- a/api/swagger.json +++ b/api/swagger.json @@ -230,7 +230,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO" + "$ref": "#/definitions/smsgateway.Webhook" } } }, @@ -273,7 +273,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO" + "$ref": "#/definitions/smsgateway.Webhook" } } ], @@ -281,7 +281,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO" + "$ref": "#/definitions/smsgateway.Webhook" } }, "400": { @@ -595,7 +595,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO" + "$ref": "#/definitions/smsgateway.Webhook" } } }, @@ -668,41 +668,6 @@ } }, "definitions": { - "github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO": { - "type": "object", - "required": [ - "event", - "url" - ], - "properties": { - "event": { - "allOf": [ - { - "$ref": "#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookEvent" - } - ], - "example": "sms:received" - }, - "id": { - "type": "string", - "maxLength": 36, - "example": "123e4567-e89b-12d3-a456-426614174000" - }, - "url": { - "type": "string", - "example": "https://example.com/webhook" - } - } - }, - "github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookEvent": { - "type": "string", - "enum": [ - "sms:received" - ], - "x-enum-varnames": [ - "WebhookEventSmsReceived" - ] - }, "smsgateway.Device": { "type": "object", "properties": { @@ -1019,14 +984,46 @@ "ProcessingStateFailed" ] }, + "smsgateway.PushEventType": { + "type": "string", + "enum": [ + "MessageEnqueued", + "WebhooksUpdated" + ], + "x-enum-varnames": [ + "PushMessageEnqueued", + "PushWebhooksUpdated" + ] + }, "smsgateway.PushNotification": { "type": "object", "required": [ "token" ], "properties": { + "data": { + "description": "The additional data associated with the event.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "event": { + "description": "The type of event.", + "default": "MessageEnqueued", + "enum": [ + "MessageEnqueued", + "WebhooksUpdated" + ], + "allOf": [ + { + "$ref": "#/definitions/smsgateway.PushEventType" + } + ], + "example": "MessageEnqueued" + }, "token": { - "description": "Device FCM token", + "description": "The token of the device that receives the notification.", "type": "string", "example": "PyDmBQZZXYmyxMwED8Fzy" } @@ -1061,6 +1058,44 @@ "example": "Pending" } } + }, + "smsgateway.Webhook": { + "type": "object", + "required": [ + "event", + "url" + ], + "properties": { + "event": { + "description": "The type of event the webhook is triggered for.", + "allOf": [ + { + "$ref": "#/definitions/smsgateway.WebhookEvent" + } + ], + "example": "sms:received" + }, + "id": { + "description": "The unique identifier of the webhook.", + "type": "string", + "maxLength": 36, + "example": "123e4567-e89b-12d3-a456-426614174000" + }, + "url": { + "description": "The URL the webhook will be sent to.", + "type": "string", + "example": "https://example.com/webhook" + } + } + }, + "smsgateway.WebhookEvent": { + "type": "string", + "enum": [ + "sms:received" + ], + "x-enum-varnames": [ + "WebhookEventSmsReceived" + ] } }, "securityDefinitions": { diff --git a/api/swagger.yaml b/api/swagger.yaml index d698ea9..b2de5e7 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -1,28 +1,5 @@ basePath: /api definitions: - github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO: - properties: - event: - allOf: - - $ref: '#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookEvent' - example: sms:received - id: - example: 123e4567-e89b-12d3-a456-426614174000 - maxLength: 36 - type: string - url: - example: https://example.com/webhook - type: string - required: - - event - - url - type: object - github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookEvent: - enum: - - sms:received - type: string - x-enum-varnames: - - WebhookEventSmsReceived smsgateway.Device: properties: createdAt: @@ -261,10 +238,32 @@ definitions: - ProcessingStateSent - ProcessingStateDelivered - ProcessingStateFailed + smsgateway.PushEventType: + enum: + - MessageEnqueued + - WebhooksUpdated + type: string + x-enum-varnames: + - PushMessageEnqueued + - PushWebhooksUpdated smsgateway.PushNotification: properties: + data: + additionalProperties: + type: string + description: The additional data associated with the event. + type: object + event: + allOf: + - $ref: '#/definitions/smsgateway.PushEventType' + default: MessageEnqueued + description: The type of event. + enum: + - MessageEnqueued + - WebhooksUpdated + example: MessageEnqueued token: - description: Device FCM token + description: The token of the device that receives the notification. example: PyDmBQZZXYmyxMwED8Fzy type: string required: @@ -291,6 +290,32 @@ definitions: - phoneNumber - state type: object + smsgateway.Webhook: + properties: + event: + allOf: + - $ref: '#/definitions/smsgateway.WebhookEvent' + description: The type of event the webhook is triggered for. + example: sms:received + id: + description: The unique identifier of the webhook. + example: 123e4567-e89b-12d3-a456-426614174000 + maxLength: 36 + type: string + url: + description: The URL the webhook will be sent to. + example: https://example.com/webhook + type: string + required: + - event + - url + type: object + smsgateway.WebhookEvent: + enum: + - sms:received + type: string + x-enum-varnames: + - WebhookEventSmsReceived host: sms.capcom.me info: contact: @@ -435,7 +460,7 @@ paths: description: Webhook list schema: items: - $ref: '#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO' + $ref: '#/definitions/smsgateway.Webhook' type: array "401": description: Unauthorized @@ -462,14 +487,14 @@ paths: name: request required: true schema: - $ref: '#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO' + $ref: '#/definitions/smsgateway.Webhook' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO' + $ref: '#/definitions/smsgateway.Webhook' "400": description: Invalid request schema: @@ -670,7 +695,7 @@ paths: description: Webhook list schema: items: - $ref: '#/definitions/github_com_capcom6_sms-gateway_pkg_smsgateway.WebhookDTO' + $ref: '#/definitions/smsgateway.Webhook' type: array "401": description: Unauthorized diff --git a/go.mod b/go.mod index 442cde9..13abfdd 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.0 require ( firebase.google.com/go/v4 v4.12.1 - github.com/android-sms-gateway/client-go v1.0.1-0.20240610220902-94dc5641aa00 + github.com/android-sms-gateway/client-go v1.0.1-0.20240610222412-894fc9370287 github.com/capcom6/go-helpers v0.0.0-20240521035030-5f57bddeecee github.com/capcom6/go-infra-fx v0.0.2 github.com/go-playground/validator/v10 v10.16.0 diff --git a/go.sum b/go.sum index f07a5a0..a23a60e 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/android-sms-gateway/client-go v1.0.0 h1:TPRNHlgcEW6jThsx0y4AG1J7wH5Ir github.com/android-sms-gateway/client-go v1.0.0/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= github.com/android-sms-gateway/client-go v1.0.1-0.20240610220902-94dc5641aa00 h1:GUtH5Pw57cxhpQ3y8EYQFTqbpjQ2dZR6G7BjcHK6lAM= github.com/android-sms-gateway/client-go v1.0.1-0.20240610220902-94dc5641aa00/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= +github.com/android-sms-gateway/client-go v1.0.1-0.20240610222412-894fc9370287 h1:4Q6TuWQcTrKb+nyMrdBTBIV0b4R/xQgmOJhOygHWIkg= +github.com/android-sms-gateway/client-go v1.0.1-0.20240610222412-894fc9370287/go.mod h1:DQsReciU1xcaVW3T5Z2bqslNdsAwCFCtghawmA6g6L4= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= diff --git a/internal/sms-gateway/app.go b/internal/sms-gateway/app.go index af2cb2a..5337d1d 100644 --- a/internal/sms-gateway/app.go +++ b/internal/sms-gateway/app.go @@ -13,12 +13,12 @@ import ( "github.com/capcom6/sms-gateway/internal/sms-gateway/handlers" "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/auth" appdb "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/db" + "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/devices" "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/health" "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/messages" "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/push" "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/webhooks" "github.com/capcom6/sms-gateway/internal/sms-gateway/repositories" - "github.com/capcom6/sms-gateway/internal/sms-gateway/services" "go.uber.org/fx" "go.uber.org/fx/fxevent" "go.uber.org/zap" @@ -33,7 +33,6 @@ var Module = fx.Module( http.Module, validator.Module, handlers.Module, - services.Module, auth.Module, push.Module, repositories.Module, @@ -41,6 +40,7 @@ var Module = fx.Module( messages.Module, health.Module, webhooks.Module, + devices.Module, ) func Run() { diff --git a/internal/sms-gateway/handlers/3rdparty.go b/internal/sms-gateway/handlers/3rdparty.go index 5701d33..3038d82 100644 --- a/internal/sms-gateway/handlers/3rdparty.go +++ b/internal/sms-gateway/handlers/3rdparty.go @@ -9,9 +9,9 @@ import ( "github.com/capcom6/sms-gateway/internal/sms-gateway/handlers/webhooks" "github.com/capcom6/sms-gateway/internal/sms-gateway/models" "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/auth" + "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/devices" "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/messages" "github.com/capcom6/sms-gateway/internal/sms-gateway/repositories" - "github.com/capcom6/sms-gateway/internal/sms-gateway/services" "github.com/capcom6/sms-gateway/pkg/types" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" @@ -32,7 +32,7 @@ type ThirdPartyHandlerParams struct { AuthSvc *auth.Service MessagesSvc *messages.Service - DevicesSvc *services.DevicesService + DevicesSvc *devices.Service Logger *zap.Logger Validator *validator.Validate @@ -46,7 +46,7 @@ type thirdPartyHandler struct { authSvc *auth.Service messagesSvc *messages.Service - devicesSvc *services.DevicesService + devicesSvc *devices.Service } // @Summary List devices @@ -62,7 +62,7 @@ type thirdPartyHandler struct { // // List devices func (h *thirdPartyHandler) getDevice(user models.User, c *fiber.Ctx) error { - devices, err := h.devicesSvc.Select(user) + devices, err := h.devicesSvc.Select(devices.WithUserID(user.ID)) if err != nil { return fmt.Errorf("can't select devices: %w", err) } @@ -110,7 +110,7 @@ func (h *thirdPartyHandler) postMessage(user models.User, c *fiber.Ctx) error { skipPhoneValidation := c.QueryBool("skipPhoneValidation", false) - devices, err := h.devicesSvc.Select(user) + devices, err := h.devicesSvc.Select(devices.WithUserID(user.ID)) if err != nil { return fmt.Errorf("can't select devices: %w", err) } diff --git a/internal/sms-gateway/handlers/mobile.go b/internal/sms-gateway/handlers/mobile.go index 1e50b19..ff49f76 100644 --- a/internal/sms-gateway/handlers/mobile.go +++ b/internal/sms-gateway/handlers/mobile.go @@ -62,7 +62,7 @@ func (h *mobileHandler) postDevice(c *fiber.Ctx) error { return fmt.Errorf("can't create user: %w", err) } - device, err := h.authSvc.RegisterDevice(user.ID, req.Name, req.PushToken) + device, err := h.authSvc.RegisterDevice(user, req.Name, req.PushToken) if err != nil { return fmt.Errorf("can't register device: %w", err) } diff --git a/internal/sms-gateway/handlers/upstream.go b/internal/sms-gateway/handlers/upstream.go index b3823d6..1eb2c2b 100644 --- a/internal/sms-gateway/handlers/upstream.go +++ b/internal/sms-gateway/handlers/upstream.go @@ -73,7 +73,7 @@ func (h *upstreamHandler) postPush(c *fiber.Ctx) error { Data: v.Data, } - if err := h.pushSvc.Enqueue(c.Context(), v.Token, &event); err != nil { + if err := h.pushSvc.Enqueue(v.Token, &event); err != nil { h.Logger.Error("Can't push message", zap.Error(err)) } } diff --git a/internal/sms-gateway/handlers/webhooks/3rdparty.go b/internal/sms-gateway/handlers/webhooks/3rdparty.go index 329540f..6e96fcb 100644 --- a/internal/sms-gateway/handlers/webhooks/3rdparty.go +++ b/internal/sms-gateway/handlers/webhooks/3rdparty.go @@ -34,7 +34,7 @@ type ThirdPartyController struct { // @Security ApiAuth // @Tags User, Webhooks // @Produce json -// @Success 200 {object} []smsgateway.WebhookDTO "Webhook list" +// @Success 200 {object} []smsgateway.Webhook "Webhook list" // @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized" // @Failure 500 {object} smsgateway.ErrorResponse "Internal server error" // @Router /api/v1/3rdparty/webhooks [get] @@ -55,8 +55,8 @@ func (h *ThirdPartyController) get(user models.User, c *fiber.Ctx) error { // @Tags User, Webhooks // @Accept json // @Produce json -// @Param request body smsgateway.WebhookDTO true "Webhook" -// @Success 201 {object} smsgateway.WebhookDTO "Created" +// @Param request body smsgateway.Webhook true "Webhook" +// @Success 201 {object} smsgateway.Webhook "Created" // @Failure 400 {object} smsgateway.ErrorResponse "Invalid request" // @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized" // @Failure 500 {object} smsgateway.ErrorResponse "Internal server error" diff --git a/internal/sms-gateway/handlers/webhooks/mobile.go b/internal/sms-gateway/handlers/webhooks/mobile.go index 8484798..c2239bd 100644 --- a/internal/sms-gateway/handlers/webhooks/mobile.go +++ b/internal/sms-gateway/handlers/webhooks/mobile.go @@ -31,7 +31,7 @@ type MobileController struct { // @Security MobileToken // @Tags Device, Webhooks // @Produce json -// @Success 200 {object} []smsgateway.WebhookDTO "Webhook list" +// @Success 200 {object} []smsgateway.Webhook "Webhook list" // @Failure 401 {object} smsgateway.ErrorResponse "Unauthorized" // @Failure 500 {object} smsgateway.ErrorResponse "Internal server error" // @Router /mobile/v1/webhooks [get] diff --git a/internal/sms-gateway/modules/auth/service.go b/internal/sms-gateway/modules/auth/service.go index 0cbf61a..75991b3 100644 --- a/internal/sms-gateway/modules/auth/service.go +++ b/internal/sms-gateway/modules/auth/service.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/capcom6/sms-gateway/internal/sms-gateway/models" + "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/devices" "github.com/capcom6/sms-gateway/internal/sms-gateway/repositories" "github.com/capcom6/sms-gateway/pkg/crypto" "github.com/jaevor/go-nanoid" @@ -21,8 +22,8 @@ type Params struct { Config Config - Users *repositories.UsersRepository - Devices *repositories.DevicesRepository + Users *repositories.UsersRepository + DevicesSvc *devices.Service Logger *zap.Logger } @@ -30,8 +31,8 @@ type Params struct { type Service struct { config Config - users *repositories.UsersRepository - devices *repositories.DevicesRepository + users *repositories.UsersRepository + devicesSvc *devices.Service logger *zap.Logger @@ -42,11 +43,11 @@ func New(params Params) *Service { idgen, _ := nanoid.Standard(21) return &Service{ - config: params.Config, - users: params.Users, - devices: params.Devices, - logger: params.Logger.Named("Service"), - idgen: idgen, + config: params.Config, + users: params.Users, + devicesSvc: params.DevicesSvc, + logger: params.Logger.Named("Service"), + idgen: idgen, } } @@ -67,20 +68,17 @@ func (s *Service) RegisterUser(login, password string) (models.User, error) { return user, nil } -func (s *Service) RegisterDevice(userID string, name, pushToken *string) (models.Device, error) { +func (s *Service) RegisterDevice(user models.User, name, pushToken *string) (models.Device, error) { device := models.Device{ - ID: s.idgen(), Name: name, - AuthToken: s.idgen(), PushToken: pushToken, - UserID: userID, } - return device, s.devices.Insert(&device) + return device, s.devicesSvc.Insert(user, &device) } func (s *Service) UpdateDevice(id, pushToken string) error { - return s.devices.UpdateToken(id, pushToken) + return s.devicesSvc.UpdateToken(id, pushToken) } func (s *Service) IsPublic() bool { @@ -100,12 +98,12 @@ func (s *Service) AuthorizeRegistration(token string) error { } func (s *Service) AuthorizeDevice(token string) (models.Device, error) { - device, err := s.devices.GetByToken(token) + device, err := s.devicesSvc.Get(devices.WithToken(token)) if err != nil { return device, err } - if err := s.devices.UpdateLastSeen(device.ID); err != nil { + if err := s.devicesSvc.UpdateLastSeen(device.ID); err != nil { s.logger.Error("can't update last seen", zap.Error(err)) } diff --git a/internal/sms-gateway/services/module.go b/internal/sms-gateway/modules/devices/module.go similarity index 53% rename from internal/sms-gateway/services/module.go rename to internal/sms-gateway/modules/devices/module.go index e8b5ea3..e15b3ff 100644 --- a/internal/sms-gateway/services/module.go +++ b/internal/sms-gateway/modules/devices/module.go @@ -1,4 +1,4 @@ -package services +package devices import ( "go.uber.org/fx" @@ -6,11 +6,15 @@ import ( ) var Module = fx.Module( - "services", + "devices", fx.Decorate(func(log *zap.Logger) *zap.Logger { - return log.Named("services") + return log.Named("devices") }), fx.Provide( - NewDevicesService, + newDevicesRepository, + fx.Private, + ), + fx.Provide( + NewService, ), ) diff --git a/internal/sms-gateway/modules/devices/repository.go b/internal/sms-gateway/modules/devices/repository.go new file mode 100644 index 0000000..55f6b79 --- /dev/null +++ b/internal/sms-gateway/modules/devices/repository.go @@ -0,0 +1,65 @@ +package devices + +import ( + "errors" + "time" + + "github.com/capcom6/sms-gateway/internal/sms-gateway/models" + "gorm.io/gorm" +) + +var ( + ErrNotFound = gorm.ErrRecordNotFound + ErrInvalidFilter = errors.New("invalid filter") + ErrMoreThanOne = errors.New("more than one record") +) + +type repository struct { + db *gorm.DB +} + +func (r *repository) Select(filter ...SelectFilter) ([]models.Device, error) { + if len(filter) == 0 { + return nil, ErrInvalidFilter + } + + f := newFilter(filter...) + devices := []models.Device{} + + return devices, f.apply(r.db).Find(&devices).Error +} + +func (r *repository) Get(filter ...SelectFilter) (models.Device, error) { + devices, err := r.Select(filter...) + if err != nil { + return models.Device{}, err + } + + if len(devices) == 0 { + return models.Device{}, ErrNotFound + } + + if len(devices) > 1 { + return models.Device{}, ErrMoreThanOne + } + + return devices[0], nil +} + +func (r *repository) Insert(device *models.Device) error { + return r.db.Create(device).Error +} + +func (r *repository) UpdateToken(id, token string) error { + return r.db.Model(&models.Device{}).Where("id", id).Update("push_token", token).Error +} + +func (r *repository) UpdateLastSeen(id string) error { + return r.db.Model(&models.Device{}).Where("id", id).Update("last_seen", time.Now()).Error +} + +func newDevicesRepository(db *gorm.DB) *repository { + return &repository{ + db: db, + } +} diff --git a/internal/sms-gateway/modules/devices/repository_filter.go b/internal/sms-gateway/modules/devices/repository_filter.go new file mode 100644 index 0000000..7128138 --- /dev/null +++ b/internal/sms-gateway/modules/devices/repository_filter.go @@ -0,0 +1,54 @@ +package devices + +import "gorm.io/gorm" + +type SelectFilter func(*selectFilter) + +func WithID(id string) SelectFilter { + return func(f *selectFilter) { + f.id = &id + } +} + +func WithToken(token string) SelectFilter { + return func(f *selectFilter) { + f.token = &token + } +} + +func WithUserID(userID string) SelectFilter { + return func(f *selectFilter) { + f.userID = &userID + } +} + +type selectFilter struct { + id *string + userID *string + token *string +} + +func newFilter(filters ...SelectFilter) *selectFilter { + f := &selectFilter{} + f.merge(filters...) + return f +} + +func (f *selectFilter) merge(filters ...SelectFilter) { + for _, filter := range filters { + filter(f) + } +} + +func (f *selectFilter) apply(query *gorm.DB) *gorm.DB { + if f.id != nil { + query = query.Where("id = ?", *f.id) + } + if f.token != nil { + query = query.Where("auth_token = ?", *f.token) + } + if f.userID != nil { + query = query.Where("user_id = ?", *f.userID) + } + return query +} diff --git a/internal/sms-gateway/modules/devices/service.go b/internal/sms-gateway/modules/devices/service.go new file mode 100644 index 0000000..dceac96 --- /dev/null +++ b/internal/sms-gateway/modules/devices/service.go @@ -0,0 +1,58 @@ +package devices + +import ( + "github.com/capcom6/sms-gateway/internal/sms-gateway/models" + "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/db" + "go.uber.org/fx" + "go.uber.org/zap" +) + +type ServiceParams struct { + fx.In + + Devices *repository + + IDGen db.IDGen + + Logger *zap.Logger +} + +type Service struct { + devices *repository + + idGen db.IDGen + + logger *zap.Logger +} + +func (s *Service) Insert(user models.User, device *models.Device) error { + device.ID = s.idGen() + device.AuthToken = s.idGen() + device.UserID = user.ID + + return s.devices.Insert(device) +} + +func (s *Service) Select(filter ...SelectFilter) ([]models.Device, error) { + return s.devices.Select(filter...) +} + +func (s *Service) Get(filter ...SelectFilter) (models.Device, error) { + return s.devices.Get(filter...) +} + +func (s *Service) UpdateToken(deviceId string, token string) error { + return s.devices.UpdateToken(deviceId, token) +} + +func (s *Service) UpdateLastSeen(deviceId string) error { + return s.devices.UpdateLastSeen(deviceId) +} + +func NewService(params ServiceParams) *Service { + return &Service{ + devices: params.Devices, + idGen: params.IDGen, + logger: params.Logger.Named("service"), + } +} diff --git a/internal/sms-gateway/modules/messages/service.go b/internal/sms-gateway/modules/messages/service.go index 6c26378..ae14098 100644 --- a/internal/sms-gateway/modules/messages/service.go +++ b/internal/sms-gateway/modules/messages/service.go @@ -214,10 +214,7 @@ func (s *Service) Enqeue(device models.Device, message smsgateway.Message, opts } go func(token string) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := s.PushSvc.Enqueue(ctx, token, push.NewMessageEnqueuedEvent()); err != nil { + if err := s.PushSvc.Enqueue(token, push.NewMessageEnqueuedEvent()); err != nil { s.Logger.Error("Can't enqueue message", zap.String("token", token), zap.Error(err)) } }(*device.PushToken) diff --git a/internal/sms-gateway/modules/push/service.go b/internal/sms-gateway/modules/push/service.go index fad5f4e..16d4955 100644 --- a/internal/sms-gateway/modules/push/service.go +++ b/internal/sms-gateway/modules/push/service.go @@ -70,7 +70,7 @@ func (s *Service) Run(ctx context.Context) { } // Enqueue adds the data to the cache and immediately sends all messages if the debounce is 0. -func (s *Service) Enqueue(ctx context.Context, token string, event *Event) error { +func (s *Service) Enqueue(token string, event *Event) error { s.cache.Set(token, event.Map()) return nil diff --git a/internal/sms-gateway/modules/webhooks/service.go b/internal/sms-gateway/modules/webhooks/service.go index 089df4f..96150da 100644 --- a/internal/sms-gateway/modules/webhooks/service.go +++ b/internal/sms-gateway/modules/webhooks/service.go @@ -6,18 +6,43 @@ import ( "github.com/android-sms-gateway/client-go/smsgateway" "github.com/capcom6/go-helpers/slices" "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/db" + "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/devices" + "github.com/capcom6/sms-gateway/internal/sms-gateway/modules/push" + "go.uber.org/fx" + "go.uber.org/zap" ) +type ServiceParams struct { + fx.In + + idgen db.IDGen + + webhooks *Repository + + devicesSvc *devices.Service + pushSvc *push.Service + + logger *zap.Logger +} + type Service struct { idgen db.IDGen webhooks *Repository + + devicesSvc *devices.Service + pushSvc *push.Service + + logger *zap.Logger } -func NewService(idgen db.IDGen, webhooks *Repository) *Service { +func NewService(params ServiceParams) *Service { return &Service{ - idgen: idgen, - webhooks: webhooks, + idgen: params.idgen, + webhooks: params.webhooks, + devicesSvc: params.devicesSvc, + pushSvc: params.pushSvc, + logger: params.logger, } } @@ -44,10 +69,44 @@ func (s *Service) Replace(userID string, webhook *smsgateway.Webhook) error { Event: webhook.Event, } - return s.webhooks.Replace(&model) + if err := s.webhooks.Replace(&model); err != nil { + return fmt.Errorf("can't replace webhook: %w", err) + } + + go s.notifyDevices(userID) + + return nil } func (s *Service) Delete(userID string, filters ...SelectFilter) error { filters = append(filters, WithUserID(userID)) - return s.webhooks.Delete(filters...) + if err := s.webhooks.Delete(filters...); err != nil { + return fmt.Errorf("can't delete webhooks: %w", err) + } + + go s.notifyDevices(userID) + + return nil +} + +func (s *Service) notifyDevices(userID string) { + s.logger.Info("Notifying devices", zap.String("user_id", userID)) + + devices, err := s.devicesSvc.Select(devices.WithUserID(userID)) + if err != nil { + s.logger.Error("Failed to select devices", zap.String("user_id", userID), zap.Error(err)) + return + } + + for _, device := range devices { + if device.PushToken == nil { + continue + } + + if err := s.pushSvc.Enqueue(*device.PushToken, push.NewWebhooksUpdatedEvent()); err != nil { + s.logger.Error("Failed to send push notification", zap.String("user_id", userID), zap.Error(err)) + } + } + + s.logger.Info("Notified devices", zap.String("user_id", userID), zap.Int("count", len(devices))) } diff --git a/internal/sms-gateway/repositories/devices.go b/internal/sms-gateway/repositories/devices.go deleted file mode 100644 index 2e798c0..0000000 --- a/internal/sms-gateway/repositories/devices.go +++ /dev/null @@ -1,56 +0,0 @@ -package repositories - -import ( - "time" - - "github.com/capcom6/sms-gateway/internal/sms-gateway/models" - "gorm.io/gorm" -) - -var ( - ErrDeviceNotFound = gorm.ErrRecordNotFound -) - -type SelectDevicesFilter struct { - UserId *string -} - -type DevicesRepository struct { - db *gorm.DB -} - -func (r *DevicesRepository) Select(filter SelectDevicesFilter) ([]models.Device, error) { - devices := []models.Device{} - - return devices, r.db.Where(filter).Find(&devices).Error -} - -func (r *DevicesRepository) Get(id string) (models.Device, error) { - device := models.Device{} - - return device, r.db.Where("id = ?", id).Take(&device).Error -} - -func (r *DevicesRepository) GetByToken(token string) (models.Device, error) { - device := models.Device{} - - return device, r.db.Where("auth_token = ?", token).Take(&device).Error -} - -func (r *DevicesRepository) Insert(device *models.Device) error { - return r.db.Create(device).Error -} - -func (r *DevicesRepository) UpdateToken(id, token string) error { - return r.db.Model(&models.Device{}).Where("id", id).Update("push_token", token).Error -} - -func (r *DevicesRepository) UpdateLastSeen(id string) error { - return r.db.Model(&models.Device{}).Where("id", id).Update("last_seen", time.Now()).Error -} - -func NewDevicesRepository(db *gorm.DB) *DevicesRepository { - return &DevicesRepository{ - db: db, - } -} diff --git a/internal/sms-gateway/repositories/module.go b/internal/sms-gateway/repositories/module.go index 89b1343..e97bbe4 100644 --- a/internal/sms-gateway/repositories/module.go +++ b/internal/sms-gateway/repositories/module.go @@ -11,7 +11,6 @@ var Module = fx.Module( return log.Named("repositories") }), fx.Provide( - NewDevicesRepository, NewMessagesRepository, NewUsersRepository, ), diff --git a/internal/sms-gateway/services/devices.go b/internal/sms-gateway/services/devices.go deleted file mode 100644 index 5b689dc..0000000 --- a/internal/sms-gateway/services/devices.go +++ /dev/null @@ -1,32 +0,0 @@ -package services - -import ( - "github.com/capcom6/sms-gateway/internal/sms-gateway/models" - "github.com/capcom6/sms-gateway/internal/sms-gateway/repositories" - "go.uber.org/fx" - "go.uber.org/zap" -) - -type DevicesServiceParams struct { - fx.In - - Devices *repositories.DevicesRepository - Logger *zap.Logger -} - -type DevicesService struct { - Devices *repositories.DevicesRepository - - Logger *zap.Logger -} - -func (s *DevicesService) Select(user models.User) ([]models.Device, error) { - return s.Devices.Select(repositories.SelectDevicesFilter{UserId: &user.ID}) -} - -func NewDevicesService(params DevicesServiceParams) *DevicesService { - return &DevicesService{ - Devices: params.Devices, - Logger: params.Logger.Named("DevicesService"), - } -}