Skip to content

Add device settings management with new API endpoints and database updates #145

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 27, 2025

Conversation

capcom6
Copy link
Member

@capcom6 capcom6 commented May 22, 2025

Summary by CodeRabbit

  • New Features

    • Introduced new API endpoints for retrieving and updating device settings via both user and mobile authentication.
    • Added support for GET, PUT, and PATCH methods on device settings, enabling full or partial updates.
    • Device settings now include categories such as encryption, gateway, messages, logs, ping, and webhooks.
    • Added push notification event for settings updates.
  • Improvements

    • Enhanced API documentation with detailed examples and schema definitions for device settings.
    • Added database migration to support device settings storage.
  • Refactor

    • Updated data model inheritance for improved timestamp and soft deletion handling.
  • Bug Fixes

    • Synchronized device notification logic for improved consistency.
  • Chores

    • Integrated new settings module into application lifecycle and dependency management.

@coderabbitai coderabbitai bot changed the title @coderabbitai Add device settings management with new API endpoints and database updates May 22, 2025
Copy link

coderabbitai bot commented May 22, 2025

Walkthrough

This update introduces a comprehensive device settings management feature, including database schema, repository, service, and HTTP API endpoints for both third-party and mobile clients. It adds new database models and migrations, implements filtering/merging utilities for settings, and integrates the settings module into the application's dependency injection. The push notification and webhooks modules are refactored to support settings updates, and API documentation is extended to cover the new endpoints.

Changes

File(s) Change Summary
go.mod Updated github.com/android-sms-gateway/client-go dependency to a newer pseudo-version.
internal/sms-gateway/app.go Added the settings module to the main application module list.
internal/sms-gateway/handlers/3rdparty.go, internal/sms-gateway/handlers/mobile.go Integrated new settings controllers into third-party and mobile handlers; updated constructors and registration logic to include settings endpoints.
internal/sms-gateway/handlers/module.go Registered new settings controllers in the dependency injection module.
internal/sms-gateway/handlers/settings/3rdparty.go, internal/sms-gateway/handlers/settings/mobile.go Added new controllers for device settings management, exposing HTTP endpoints for retrieving, updating, and replacing settings for users and devices.
internal/sms-gateway/models/migrations/mysql/20250521225803_add_device_settings.sql Added a migration script to create the device_settings table with appropriate columns and constraints.
internal/sms-gateway/models/models.go, internal/sms-gateway/modules/webhooks/models.go, internal/sms-gateway/handlers/converters/devices_test.go Refactored model struct hierarchy: introduced SoftDeletableModel, updated models to embed it instead of TimedModel, and adjusted tests accordingly.
internal/sms-gateway/modules/push/service.go, internal/sms-gateway/modules/push/types.go Added Notify method to push service for device notifications; added a constructor for "settings updated" events; updated service to inject devices service.
internal/sms-gateway/modules/webhooks/service.go Refactored device notification logic to use the new push notification service and simplified error handling.
internal/sms-gateway/modules/settings/models.go, internal/sms-gateway/modules/settings/repository.go, internal/sms-gateway/modules/settings/service.go, internal/sms-gateway/modules/settings/module.go, internal/sms-gateway/modules/settings/utils.go Introduced the settings module with data model, repository, service, dependency injection module, migration registration, and utility functions for filtering and merging settings.
pkg/swagger/docs/local.http, pkg/swagger/docs/mobile.http, pkg/swagger/docs/requests.http Added HTTP request examples for new settings endpoints in documentation.
pkg/swagger/docs/swagger.json, pkg/swagger/docs/swagger.yaml Extended API specification with new settings endpoints, schemas, enums, and updated push notification event types.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ThirdPartyAPI
    participant ThirdPartyController
    participant SettingsService
    participant SettingsRepository
    participant DB

    User->>ThirdPartyAPI: Authenticated HTTP request (GET/PUT/PATCH /3rdparty/v1/settings)
    ThirdPartyAPI->>ThirdPartyController: Route to controller
    ThirdPartyController->>SettingsService: Get/Update/ReplaceSettings(userID, settings)
    SettingsService->>SettingsRepository: Query/update device_settings
    SettingsRepository->>DB: Read/write device_settings table
    SettingsRepository-->>SettingsService: Result/Error
    SettingsService-->>ThirdPartyController: Filtered settings/Error
    ThirdPartyController-->>ThirdPartyAPI: JSON response/Error
    ThirdPartyAPI-->>User: Response

    Note over ThirdPartyController,SettingsService: On settings update, may trigger push notification
Loading
sequenceDiagram
    participant Device
    participant MobileAPI
    participant MobileController
    participant SettingsService
    participant SettingsRepository
    participant DB

    Device->>MobileAPI: Authenticated HTTP GET /mobile/v1/settings
    MobileAPI->>MobileController: Route to controller
    MobileController->>SettingsService: GetSettings(userID)
    SettingsService->>SettingsRepository: Query device_settings
    SettingsRepository->>DB: Read device_settings table
    SettingsRepository-->>SettingsService: Settings/Error
    SettingsService-->>MobileController: Filtered settings/Error
    MobileController-->>MobileAPI: JSON response/Error
    MobileAPI-->>Device: Response
Loading
sequenceDiagram
    participant Service
    participant DevicesService
    participant PushService

    Service->>DevicesService: Query devices for user (optionally by deviceID)
    DevicesService-->>Service: List of devices
    Service->>PushService: Enqueue push event for each device with push token
    PushService-->>Service: Success/Error
    Service-->>Caller: Combined result
Loading

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 960f032 and a181aab.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (23)
  • go.mod (1 hunks)
  • internal/sms-gateway/app.go (2 hunks)
  • internal/sms-gateway/handlers/3rdparty.go (5 hunks)
  • internal/sms-gateway/handlers/converters/devices_test.go (1 hunks)
  • internal/sms-gateway/handlers/mobile.go (5 hunks)
  • internal/sms-gateway/handlers/module.go (2 hunks)
  • internal/sms-gateway/handlers/settings/3rdparty.go (1 hunks)
  • internal/sms-gateway/handlers/settings/mobile.go (1 hunks)
  • internal/sms-gateway/models/migrations/mysql/20250521225803_add_device_settings.sql (1 hunks)
  • internal/sms-gateway/models/models.go (4 hunks)
  • internal/sms-gateway/modules/push/service.go (5 hunks)
  • internal/sms-gateway/modules/push/types.go (1 hunks)
  • internal/sms-gateway/modules/settings/models.go (1 hunks)
  • internal/sms-gateway/modules/settings/module.go (1 hunks)
  • internal/sms-gateway/modules/settings/repository.go (1 hunks)
  • internal/sms-gateway/modules/settings/service.go (1 hunks)
  • internal/sms-gateway/modules/settings/utils.go (1 hunks)
  • internal/sms-gateway/modules/webhooks/models.go (1 hunks)
  • pkg/swagger/docs/local.http (1 hunks)
  • pkg/swagger/docs/mobile.http (1 hunks)
  • pkg/swagger/docs/requests.http (1 hunks)
  • pkg/swagger/docs/swagger.json (7 hunks)
  • pkg/swagger/docs/swagger.yaml (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (23)
  • go.mod
  • pkg/swagger/docs/mobile.http
  • internal/sms-gateway/app.go
  • internal/sms-gateway/modules/push/types.go
  • internal/sms-gateway/handlers/module.go
  • internal/sms-gateway/handlers/converters/devices_test.go
  • internal/sms-gateway/modules/webhooks/models.go
  • internal/sms-gateway/models/migrations/mysql/20250521225803_add_device_settings.sql
  • pkg/swagger/docs/requests.http
  • internal/sms-gateway/handlers/mobile.go
  • internal/sms-gateway/models/models.go
  • pkg/swagger/docs/local.http
  • internal/sms-gateway/handlers/3rdparty.go
  • internal/sms-gateway/modules/push/service.go
  • internal/sms-gateway/modules/settings/models.go
  • internal/sms-gateway/handlers/settings/3rdparty.go
  • internal/sms-gateway/modules/settings/module.go
  • pkg/swagger/docs/swagger.yaml
  • internal/sms-gateway/handlers/settings/mobile.go
  • internal/sms-gateway/modules/settings/repository.go
  • pkg/swagger/docs/swagger.json
  • internal/sms-gateway/modules/settings/utils.go
  • internal/sms-gateway/modules/settings/service.go
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build / Docker image (linux/arm64)
  • GitHub Check: lint
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (14)
internal/sms-gateway/models/migrations/mysql/20250521225803_add_device_settings.sql (2)

3-8: Consider adding auditing fields and ON UPDATE cascade

  1. A settings table is often updated frequently. Adding created_at / updated_at (or embedding them later via TimedModel) makes troubleshooting and eventual clean-up much easier.
  2. If the users.id value can ever be regenerated (unlikely but not impossible in some domains), an ON UPDATE CASCADE keeps the FK consistent.
    `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    CONSTRAINT `fk_device_settings_user`
        FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
        ON UPDATE CASCADE ON DELETE CASCADE

Please confirm whether you require temporal auditing; if so, I can supply the full migration diff.


11-14: Safe down-migration check

Dropping the table is fine, but goose will run this on every environment.
If any other table references device_settings in future, DROP TABLE will fail. You may wish to add IF EXISTS to make rollback idempotent:

DROP TABLE IF EXISTS `device_settings`;
internal/sms-gateway/modules/devices/models.go (1)

15-17: Propagate migrate errors upstream

AutoMigrate hides useful context (SQL, driver, etc.). Wrapping the error improves debuggability:

-func Migrate(db *gorm.DB) error {
-    return db.AutoMigrate(&DeviceSettings{})
-}
+func Migrate(db *gorm.DB) error {
+    if err := db.AutoMigrate(&DeviceSettings{}); err != nil {
+        return fmt.Errorf("device_settings migration failed: %w", err)
+    }
+    return nil
+}
internal/sms-gateway/handlers/mobile.go (1)

37-39: Nil-safety & route-scope clarification for settings controller

  1. h.settingsCtrl is dereferenced unconditionally in Register. If the DI graph fails to provide the controller (e.g. it was disabled behind a build tag), the app will panic.
    if h.settingsCtrl == nil {
        return fmt.Errorf("settings controller not initialised")
    }
  2. The /settings routes inherit the deviceauth.New middleware registered above. ✔️ Just confirm that the settings endpoints indeed require device auth; Swagger examples show both “user” & “device” contexts.
  3. FX param struct already enforces presence, but you may want fx.Annotate() with optional:"true" if you foresee running without the feature.

Also applies to: 309-310, 323-336

internal/sms-gateway/modules/devices/repository.go (3)

97-105: Consider explicit handling of "not found" case.

The GetSettings method returns successfully even when no settings are found for the user. Consider either returning ErrNotFound or documenting that an empty settings object is returned when no record exists.

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
	}

+	// If the settings object is empty (no record found), return explicit error
+	if settings.UserID == "" {
+		return nil, ErrNotFound
+	}

	return settings, nil
}

119-129: Remove commented-out code.

Consider removing the commented-out alternative implementation using JSON_MERGE_PATCH. If this is kept for future reference, add a comment explaining why the current implementation was chosen over this approach.

-	// return r.db.
-	// 	Clauses(clause.OnConflict{
-	// 		DoUpdates: clause.Assignments(
-	// 			map[string]interface{}{
-	// 				"settings": gorm.Expr("JSON_MERGE_PATCH(settings, VALUES(settings))"),
-	// 			},
-	// 		),
-	// 	}).
-	// 	Create(settings).
-	// 	Error

132-134: Consider adding transaction handling to ReplaceSettings.

For consistency with UpdateSettings, consider adding transaction handling to ReplaceSettings, especially since it could potentially replace a concurrently modified record.

func (r *repository) ReplaceSettings(settings *DeviceSettings) (*DeviceSettings, error) {
-	return settings, r.db.Save(settings).Error
+	err := r.db.Transaction(func(tx *gorm.DB) error {
+		return tx.Save(settings).Error
+	})
+	return settings, err
}
internal/sms-gateway/modules/push/service.go (1)

144-186: Consider optimizing error handling and logging

The implementation of the Notify method is solid, but there are a few small opportunities for improvement:

  1. The error slice allocation could be optimized since not all devices will have push tokens
  2. The log message counts all devices, not just those that were actually notified
- errs := make([]error, 0, len(devices))
+ errs := make([]error, 0)

// Later in the code:
+ 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))
          continue
      }

      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)))...)
internal/sms-gateway/modules/devices/utils.go (1)

65-90: Improve error message formatting

The error message uses string concatenation which is less efficient than using fmt.Errorf.

- return nil, errors.New("The field: '" + field + "' is not a map to dive")
+ return nil, fmt.Errorf("the field: '%s' is not a map to dive", field)

This requires adding fmt to the imports.

internal/sms-gateway/handlers/settings/3rdparty.go (3)

67-75: Consider adding error details to the return error

The body parser error handling could be improved by including the error details in the returned error message, making debugging easier.

-	if err := h.BodyParserValidator(c, &smsgateway.DeviceSettings{}); err != nil {
-		return fiber.NewError(fiber.StatusBadRequest, err.Error())
-	}
+	if err := h.BodyParserValidator(c, &smsgateway.DeviceSettings{}); err != nil {
+		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid settings format: %v", err))
+	}

71-75: Consider error handling inconsistency

You're making two body parse attempts, but only returning a generic error on the second parse. Consider consistent error handling.

-	if err := c.BodyParser(&settings); err != nil {
-		return err
-	}
+	if err := c.BodyParser(&settings); err != nil {
+		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Failed to parse request body: %v", err))
+	}

87-95: Same validation inconsistency as in put method

Consider the same error handling improvement as suggested for the put method.

-	if err := h.BodyParserValidator(c, &smsgateway.DeviceSettings{}); err != nil {
-		return fiber.NewError(fiber.StatusBadRequest, err.Error())
-	}
+	if err := h.BodyParserValidator(c, &smsgateway.DeviceSettings{}); err != nil {
+		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid settings format: %v", err))
+	}

-	if err := c.BodyParser(&settings); err != nil {
-		return err
-	}
+	if err := c.BodyParser(&settings); err != nil {
+		return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Failed to parse request body: %v", err))
+	}
internal/sms-gateway/handlers/settings/mobile.go (2)

40-47: Consider adding context to the error message

Adding more context to the error message would help with debugging.

-	if err != nil {
-		return fmt.Errorf("can't get settings: %w", err)
-	}
+	if err != nil {
+		return fmt.Errorf("can't get settings for device %s (user ID: %s): %w", device.ID, device.UserID, err)
+	}

54-60: Missing Validator initialization in MobileController

Unlike the ThirdPartyController, the MobileController doesn't initialize the Validator field in the base.Handler struct, which could lead to nil pointer dereferences if validation is attempted.

type mobileControllerParams struct {
	fx.In

	DevicesSvc *devices.Service

	Logger *zap.Logger
+	Validator *validator.Validate
}

func NewMobileController(params mobileControllerParams) *MobileController {
	return &MobileController{
		Handler: base.Handler{
			Logger: params.Logger.Named("settings"),
+			Validator: params.Validator,
		},
		devicesSvc: params.DevicesSvc,
	}
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfa12a4 and c9cb324.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (19)
  • go.mod (1 hunks)
  • internal/sms-gateway/handlers/3rdparty.go (5 hunks)
  • internal/sms-gateway/handlers/mobile.go (5 hunks)
  • internal/sms-gateway/handlers/module.go (2 hunks)
  • internal/sms-gateway/handlers/settings/3rdparty.go (1 hunks)
  • internal/sms-gateway/handlers/settings/mobile.go (1 hunks)
  • internal/sms-gateway/models/migrations/mysql/20250521225803_add_device_settings.sql (1 hunks)
  • internal/sms-gateway/modules/devices/models.go (1 hunks)
  • internal/sms-gateway/modules/devices/module.go (2 hunks)
  • internal/sms-gateway/modules/devices/repository.go (2 hunks)
  • internal/sms-gateway/modules/devices/service.go (1 hunks)
  • internal/sms-gateway/modules/devices/utils.go (1 hunks)
  • internal/sms-gateway/modules/push/service.go (5 hunks)
  • internal/sms-gateway/modules/webhooks/service.go (2 hunks)
  • pkg/swagger/docs/local.http (1 hunks)
  • pkg/swagger/docs/mobile.http (1 hunks)
  • pkg/swagger/docs/requests.http (1 hunks)
  • pkg/swagger/docs/swagger.json (6 hunks)
  • pkg/swagger/docs/swagger.yaml (6 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
internal/sms-gateway/handlers/module.go (2)
internal/sms-gateway/handlers/settings/3rdparty.go (1)
  • NewThirdPartyController (111-119)
internal/sms-gateway/handlers/settings/mobile.go (1)
  • NewMobileController (53-60)
internal/sms-gateway/modules/devices/module.go (1)
internal/sms-gateway/modules/devices/models.go (1)
  • Migrate (15-17)
internal/sms-gateway/modules/devices/repository.go (1)
internal/sms-gateway/modules/devices/models.go (1)
  • DeviceSettings (8-13)
internal/sms-gateway/handlers/mobile.go (1)
internal/sms-gateway/handlers/settings/mobile.go (1)
  • MobileController (23-27)
internal/sms-gateway/handlers/3rdparty.go (1)
internal/sms-gateway/handlers/settings/3rdparty.go (1)
  • ThirdPartyController (26-30)
internal/sms-gateway/modules/devices/models.go (1)
internal/sms-gateway/models/models.go (1)
  • User (23-29)
internal/sms-gateway/modules/devices/service.go (3)
internal/sms-gateway/modules/webhooks/service.go (1)
  • Service (28-37)
internal/sms-gateway/modules/auth/service.go (1)
  • Service (37-49)
internal/sms-gateway/modules/devices/models.go (1)
  • DeviceSettings (8-13)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build / Docker image (linux/arm64)
🔇 Additional comments (33)
pkg/swagger/docs/mobile.http (1)

75-77: Endpoint definition looks good.

The new GET endpoint for retrieving device settings follows the established pattern in the file and correctly includes the Bearer token authentication header.

internal/sms-gateway/modules/devices/module.go (1)

35-37: Migration registration looks good.

Proper initialization of the database migration for device settings. This ensures the migration runs automatically during application startup.

go.mod (1)

9-9: Consider using a stable release version.

You're using a pseudo-version (v1.5.9-0.20250522134006-6e8b4dd3057a) rather than a stable release. This could potentially introduce unexpected behavior if there are breaking changes.

Is this intended to be a temporary solution until a stable v1.5.9 release is available? Consider pinning to a stable release version when it becomes available.

internal/sms-gateway/handlers/module.go (2)

7-7: Settings handler import is correctly added.

The import for the new settings package is properly included.


31-32: Controller registration looks good.

The registration of both the ThirdPartyController and MobileController for settings follows the existing pattern in the codebase and ensures they're properly integrated into the dependency injection framework.

pkg/swagger/docs/local.http (3)

84-86: LGTM: New GET endpoint for settings retrieval looks good.

The added GET endpoint for retrieving device settings is properly defined with the correct authorization header.


89-123: LGTM: Full PATCH endpoint for settings updates is well-structured.

This example demonstrates a comprehensive settings update with all available configuration categories. The JSON structure is clear and shows the expected format for each settings category.


126-135: LGTM: Partial PATCH endpoint demonstrates focused updates.

This example nicely demonstrates how clients can update only specific settings (webhooks in this case) without affecting other configuration categories, following REST API best practices.

pkg/swagger/docs/requests.http (3)

107-108: LGTM: GET endpoint for 3rdparty settings retrieval looks good.

The added GET endpoint for retrieving device settings via the 3rdparty API is properly defined with the correct authorization header.


126-160: LGTM: PUT endpoint for full settings replacement.

The PUT endpoint example provides a comprehensive template for replacing all device settings. Using {{$guid}} for security-sensitive fields like passphrases and signing keys is a good practice in example templates.


111-123:

❓ Verification inconclusive

Verify negative value validity for send_interval_max.

The example shows "send_interval_max": -1 - is this a valid value or a special case? Negative intervals are typically not valid for time-based settings.


🏁 Script executed:

#!/bin/bash
# Search for validation or documentation of send_interval_max to check if negative values are handled

# Look for validation rules
echo "Searching for validation rules for send_interval_max..."
rg -A 3 -B 3 "send_interval_max" --type go

# Look for comments or documentation about this field
echo "Searching for documentation about send_interval_max..."
rg "send_interval_max" --type md --type txt

Length of output: 1319


Validate negative send_interval_max values

There’s no evidence in the codebase that negative intervals are handled specially (e.g., -1 as “unlimited”), nor any server-side validation for send_interval_max. Please confirm with the API contract whether negative values are allowed, and if not:

  • Add or update validation logic to reject negative send_interval_max.
  • Document the permitted range (or sentinel values) in your Swagger spec.

Locations to review:

  • pkg/swagger/docs/requests.http (lines 111–123)
  • Internal handler/model for /3rdparty/v1/settings (search for where messages.send_interval_max is parsed)
internal/sms-gateway/handlers/3rdparty.go (5)

9-9: LGTM: Import for settings package.

The settings package is properly imported to support the new settings handler.


25-25: LGTM: SettingsHandler field added to params.

The SettingsHandler field is correctly added to the ThirdPartyHandlerParams struct and marked for dependency injection with fx.In.


41-42: LGTM: settingsHandler field added to handler struct.

The settingsHandler field is properly added to the thirdPartyHandler struct.


63-64: LGTM: Settings handler registered with router.

The settings handler is properly registered with a dedicated route group at "/settings" and will inherit the authentication middleware applied at lines 52-55.


77-78: LGTM: Settings handler initialized in constructor.

The settings handler is properly initialized in the newThirdPartyHandler constructor.

internal/sms-gateway/modules/devices/repository.go (2)

10-10: LGTM: Import for GORM clause package.

The clause package is correctly imported to support locking and conflict resolution in the settings update method.


107-118: LGTM: Transaction-based settings update with locking.

The UpdateSettings method uses proper transaction handling and row-level locking to ensure consistency during the update process. The appendMap function with rules ensures proper merging of settings maps.

internal/sms-gateway/modules/push/service.go (4)

3-9: LGTM: Import of required packages

The added imports for errors and devices package support the new notification functionality.


29-39: LGTM: Updated Params struct

Proper injection of the DevicesSvc dependency through the fx.In struct.


41-56: LGTM: Updated Service struct

The Service struct now includes the devicesSvc field to access device information.


87-103: LGTM: Updated constructor

The constructor properly initializes the devicesSvc field from the params.

internal/sms-gateway/modules/devices/service.go (1)

131-138: LGTM: GetSettings implementation

The GetSettings method properly retrieves and filters settings according to public rules.

internal/sms-gateway/modules/devices/utils.go (2)

5-35: LGTM: Rules definition

The rules map properly defines the schema for device settings with appropriate nesting.


37-63: LGTM: Public rules definition

The rulesPublic map correctly defines a subset of rules for public access, omitting sensitive fields.

pkg/swagger/docs/swagger.yaml (5)

29-44: LGTM: DeviceSettings definition

Well-structured schema definition for device settings with appropriate references to nested objects.


434-444: LGTM: Updated PushEventType enum

Added SettingsUpdated event type to notify devices when settings change.


491-552: LGTM: Settings component schemas

Well-defined schemas for each settings component with appropriate property types and validations.


853-913: LGTM: 3rdparty settings endpoints

The API endpoints for third-party settings management are well-defined with appropriate request/response schemas and security.


1152-1175: LGTM: Mobile settings endpoints

The API endpoint for mobile device settings retrieval is well-defined with appropriate security and response schema.

internal/sms-gateway/handlers/settings/3rdparty.go (1)

105-109: Consistent API pattern implemented correctly

The Register method properly sets up routes with authentication middleware. Good approach using the WithUser middleware consistently across all endpoints.

pkg/swagger/docs/swagger.json (2)

1120-1142: DeviceSettings schema looks good

The DeviceSettings schema properly organizes different setting categories into logical groupings, making the structure clean and maintainable.


1665-1666: Good addition of SettingsUpdated event type

Adding the SettingsUpdated event type to the PushEventType enum ensures that mobile devices can be notified when settings are updated, which is essential for the new feature.

Also applies to: 1694-1695

Comment on lines 8 to 13
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"`
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

GORM JSON mapping & missing timestamps

  1. map[string]any + serializer:json compiles, but GORM’s built-in JSON serializer currently supports map[string]interface{} or datatypes.JSONMap.
    Using any relies on the Go 1.18 alias and has caused scan errors with MySQL drivers in the past.
  2. The model has no CreatedAt / UpdatedAt fields while every other entity embeds TimedModel. This makes it impossible to know when a user last changed settings.

Suggested refactor:

-import (
-    "github.com/android-sms-gateway/server/internal/sms-gateway/models"
-    "gorm.io/gorm"
-)
+import (
+    "github.com/android-sms-gateway/server/internal/sms-gateway/models"
+    "gorm.io/datatypes"
+    "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"`
+    Settings datatypes.JSONMap `gorm:"type:json"`
+
+    models.TimedModel          // gives CreatedAt / UpdatedAt

     User models.User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
 }

Happy to provide the full patch if you’d like.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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"`
}
import (
"github.com/android-sms-gateway/server/internal/sms-gateway/models"
"gorm.io/datatypes"
"gorm.io/gorm"
)
type DeviceSettings struct {
UserID string `gorm:"primaryKey;not null;type:varchar(32)"`
Settings datatypes.JSONMap `gorm:"type:json"`
models.TimedModel // adds CreatedAt / UpdatedAt
User models.User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
}
🤖 Prompt for AI Agents
In internal/sms-gateway/modules/devices/models.go around lines 8 to 13, replace
the type of the Settings field from map[string]any to map[string]interface{} to
ensure compatibility with GORM's JSON serializer and avoid scan errors with
MySQL drivers. Additionally, embed the TimedModel struct or add CreatedAt and
UpdatedAt timestamp fields to the DeviceSettings struct to track when settings
are created or updated, maintaining consistency with other entities.

Comment on lines +119 to 126
// notifyDevices asynchronously notifies all the user's devices.
func (s *Service) notifyDevices(userID string, deviceID *string) {
logFields := []zap.Field{
zap.String("user_id", userID),
}
if deviceID != nil {
logFields = append(logFields, zap.String("device_id", *deviceID))
}

s.logger.Info("Notifying devices", logFields...)

var filters []devices.SelectFilter
if deviceID != nil {
filters = []devices.SelectFilter{devices.WithID(*deviceID)}
}

devices, err := s.devicesSvc.Select(userID, filters...)
if err != nil {
s.logger.Error("Failed to select devices", append(logFields, zap.Error(err))...)
return
}

if len(devices) == 0 {
s.logger.Info("No devices found", logFields...)
return
}

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))
continue
go func(userID string, deviceID *string) {
if err := s.pushSvc.Notify(userID, deviceID, push.NewWebhooksUpdatedEvent()); err != nil {
s.logger.Error("can't notify devices", zap.Error(err))
}

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.String("device_id", device.ID), zap.Error(err))
}
}

s.logger.Info("Notified devices", append(logFields, zap.Int("count", len(devices)))...)
}(userID, deviceID)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential goroutine flood – consider a worker or context propagation

notifyDevices spawns a new goroutine on every webhook change. Under heavy load (bulk imports or script loops) this may create thousands of short-lived goroutines and hammer the DB via pushSvc.Notify.

Suggestions:

-func (s *Service) notifyDevices(userID string, deviceID *string) {
-    go func(userID string, deviceID *string) {
-        if err := s.pushSvc.Notify(userID, deviceID, push.NewWebhooksUpdatedEvent()); err != nil {
-            s.logger.Error("can't notify devices", zap.Error(err))
-        }
-    }(userID, deviceID)
+func (s *Service) notifyDevices(userID string, deviceID *string) {
+    // pushSvc.Notify is already I/O bound; run synchronously or
+    // hand off to an internal worker pool to limit concurrency.
+    if err := s.pushSvc.Notify(userID, deviceID, push.NewWebhooksUpdatedEvent()); err != nil {
+        s.logger.Error("can't notify devices", zap.Error(err))
+    }
 }

This keeps the call depth predictable and lets pushSvc decide whether to fan-out.

I can draft a simple worker-pool implementation if required.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// notifyDevices asynchronously notifies all the user's devices.
func (s *Service) notifyDevices(userID string, deviceID *string) {
logFields := []zap.Field{
zap.String("user_id", userID),
}
if deviceID != nil {
logFields = append(logFields, zap.String("device_id", *deviceID))
}
s.logger.Info("Notifying devices", logFields...)
var filters []devices.SelectFilter
if deviceID != nil {
filters = []devices.SelectFilter{devices.WithID(*deviceID)}
}
devices, err := s.devicesSvc.Select(userID, filters...)
if err != nil {
s.logger.Error("Failed to select devices", append(logFields, zap.Error(err))...)
return
}
if len(devices) == 0 {
s.logger.Info("No devices found", logFields...)
return
}
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))
continue
go func(userID string, deviceID *string) {
if err := s.pushSvc.Notify(userID, deviceID, push.NewWebhooksUpdatedEvent()); err != nil {
s.logger.Error("can't notify devices", zap.Error(err))
}
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.String("device_id", device.ID), zap.Error(err))
}
}
s.logger.Info("Notified devices", append(logFields, zap.Int("count", len(devices)))...)
}(userID, deviceID)
}
func (s *Service) notifyDevices(userID string, deviceID *string) {
// pushSvc.Notify is already I/O bound; run synchronously or
// hand off to an internal worker pool to limit concurrency.
if err := s.pushSvc.Notify(userID, deviceID, push.NewWebhooksUpdatedEvent()); err != nil {
s.logger.Error("can't notify devices", zap.Error(err))
}
}
🤖 Prompt for AI Agents
In internal/sms-gateway/modules/webhooks/service.go around lines 119 to 126, the
notifyDevices method spawns a new goroutine for every call, which can lead to
excessive goroutines and overload the pushSvc.Notify under heavy load. Refactor
notifyDevices to use a worker pool or a bounded queue to limit concurrent
goroutines. Implement a context propagation mechanism to manage cancellation and
timeouts properly. This approach will control concurrency, prevent goroutine
flooding, and allow pushSvc to handle fan-out more efficiently.

Comment on lines 157 to 172
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.devices.ReplaceSettings(&DeviceSettings{
UserID: userID,
Settings: filtered,
})
if err != nil {
return nil, err
}

return filterMap(updated.Settings, rulesPublic)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider notifying devices after settings replacement

Similar to UpdateSettings, the ReplaceSettings method should notify devices of changes.

Consider adding notification logic after successful settings replacement:

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.devices.ReplaceSettings(&DeviceSettings{
        UserID:   userID,
        Settings: filtered,
    })
    if err != nil {
        return nil, err
    }

+   // Consider adding code to notify devices about settings changes
+   // This would require injecting the push service or using an event system
+   // Example: pushSvc.Notify(userID, nil, &domain.SettingsUpdatedEvent{})

    return filterMap(updated.Settings, rulesPublic)
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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.devices.ReplaceSettings(&DeviceSettings{
UserID: userID,
Settings: filtered,
})
if err != nil {
return nil, err
}
return filterMap(updated.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.devices.ReplaceSettings(&DeviceSettings{
UserID: userID,
Settings: filtered,
})
if err != nil {
return nil, err
}
// Consider adding code to notify devices about settings changes
// This would require injecting the push service or using an event system
// Example: pushSvc.Notify(userID, nil, &domain.SettingsUpdatedEvent{})
return filterMap(updated.Settings, rulesPublic)
}
🤖 Prompt for AI Agents
In internal/sms-gateway/modules/devices/service.go around lines 157 to 172, the
ReplaceSettings method updates device settings but does not notify devices of
the changes. To fix this, add logic to notify devices after a successful
settings replacement, similar to the notification implemented in the
UpdateSettings method. Ensure the notification occurs only if the settings
update succeeds and handle any errors appropriately.

Comment on lines 140 to 155
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
}

err = s.devices.UpdateSettings(&DeviceSettings{
UserID: userID,
Settings: filtered,
})
if err != nil {
return nil, err
}

return s.GetSettings(userID)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider notifying devices after settings update

The UpdateSettings method correctly updates settings, but doesn't notify devices of the changes.

Since a new PushSettingsUpdated event type has been added (as seen in the Swagger docs), consider sending a notification to affected devices:

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
    }

    err = s.devices.UpdateSettings(&DeviceSettings{
        UserID:   userID,
        Settings: filtered,
    })
    if err != nil {
        return nil, err
    }

+   // Consider adding code to notify devices about settings changes
+   // This would require injecting the push service or using an event system
+   // Example: pushSvc.Notify(userID, nil, &domain.SettingsUpdatedEvent{})

    return s.GetSettings(userID)
}

This would require dependency injection of the push service, or implementing an event system.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
}
err = s.devices.UpdateSettings(&DeviceSettings{
UserID: userID,
Settings: filtered,
})
if err != nil {
return nil, err
}
return s.GetSettings(userID)
}
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
}
err = s.devices.UpdateSettings(&DeviceSettings{
UserID: userID,
Settings: filtered,
})
if err != nil {
return nil, err
}
// Consider adding code to notify devices about settings changes
// This would require injecting the push service or using an event system
// Example: pushSvc.Notify(userID, nil, &domain.SettingsUpdatedEvent{})
return s.GetSettings(userID)
}
🤖 Prompt for AI Agents
In internal/sms-gateway/modules/devices/service.go around lines 140 to 155,
after successfully updating device settings in UpdateSettings, add logic to
notify devices of the changes using the new PushSettingsUpdated event type. To
do this, inject the push notification service into the Service struct or
implement an event system, then call the appropriate method to send the
PushSettingsUpdated notification to affected devices before returning the
updated settings.

coderabbitai bot added a commit that referenced this pull request May 22, 2025
Docstrings generation was requested by @capcom6.

* #145 (comment)

The following files were modified:

* `internal/sms-gateway/handlers/3rdparty.go`
* `internal/sms-gateway/handlers/mobile.go`
* `internal/sms-gateway/handlers/settings/3rdparty.go`
* `internal/sms-gateway/handlers/settings/mobile.go`
* `internal/sms-gateway/modules/devices/models.go`
* `internal/sms-gateway/modules/devices/module.go`
* `internal/sms-gateway/modules/devices/repository.go`
* `internal/sms-gateway/modules/devices/service.go`
* `internal/sms-gateway/modules/devices/utils.go`
* `internal/sms-gateway/modules/push/service.go`
Copy link

coderabbitai bot commented May 22, 2025

Note

Generated docstrings for this pull request at #146

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
pkg/swagger/docs/swagger.yaml (2)

29-56: Add a top-level description for DeviceSettings
The DeviceSettings definition currently has no description to explain its overall purpose. Consider adding a concise description and, if certain nested objects are mandatory, a required list to guide consumers.

Proposed diff:

+    description: "Aggregated device-specific settings for encryption, gateway, logs, messages, ping, and webhooks."
    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.

122-133: Add description to LimitPeriod enum
The LimitPeriod enum lacks context. A brief description would help API consumers understand its role within SettingsMessages.

Proposed diff:

+    description: "Period over which message sending limits apply."
    enum:
    - Disabled
    - PerMinute
    - PerHour
    - PerDay
    type: string
    x-enum-varnames:
    - Disabled
    - PerMinute
    - PerHour
    - PerDay
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9cb324 and 509c601.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (15)
  • go.mod (1 hunks)
  • internal/sms-gateway/handlers/converters/devices_test.go (1 hunks)
  • internal/sms-gateway/handlers/settings/3rdparty.go (1 hunks)
  • internal/sms-gateway/handlers/settings/mobile.go (1 hunks)
  • internal/sms-gateway/models/migrations/mysql/20250521225803_add_device_settings.sql (1 hunks)
  • internal/sms-gateway/models/models.go (4 hunks)
  • internal/sms-gateway/modules/devices/models.go (1 hunks)
  • internal/sms-gateway/modules/devices/repository.go (2 hunks)
  • internal/sms-gateway/modules/devices/utils.go (1 hunks)
  • internal/sms-gateway/modules/push/service.go (5 hunks)
  • internal/sms-gateway/modules/push/types.go (1 hunks)
  • internal/sms-gateway/modules/webhooks/models.go (1 hunks)
  • pkg/swagger/docs/requests.http (1 hunks)
  • pkg/swagger/docs/swagger.json (7 hunks)
  • pkg/swagger/docs/swagger.yaml (7 hunks)
✅ Files skipped from review due to trivial changes (3)
  • internal/sms-gateway/modules/push/types.go
  • internal/sms-gateway/handlers/converters/devices_test.go
  • internal/sms-gateway/models/migrations/mysql/20250521225803_add_device_settings.sql
🚧 Files skipped from review as they are similar to previous changes (8)
  • go.mod
  • internal/sms-gateway/modules/devices/models.go
  • internal/sms-gateway/modules/devices/repository.go
  • pkg/swagger/docs/requests.http
  • internal/sms-gateway/handlers/settings/mobile.go
  • internal/sms-gateway/modules/push/service.go
  • internal/sms-gateway/modules/devices/utils.go
  • internal/sms-gateway/handlers/settings/3rdparty.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
internal/sms-gateway/modules/webhooks/models.go (1)
internal/sms-gateway/models/models.go (1)
  • SoftDeletableModel (22-25)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build / Docker image (linux/arm64)
🔇 Additional comments (17)
internal/sms-gateway/models/models.go (5)

17-20: Simplified TimedModel struct by removing DeletedAt field

The TimedModel struct now only contains timestamp fields for creation and updates, which better aligns with its core responsibility of tracking entity timestamps.


22-25: Good separation of concerns with SoftDeletableModel

Creating a separate SoftDeletableModel that embeds TimedModel follows good object-oriented design principles by:

  1. Separating core timestamp tracking from soft-deletion capability
  2. Creating a clear inheritance hierarchy
  3. Making the codebase more maintainable through composition

This change allows models to opt-in to soft deletion capability rather than forcing it on all timed models.


27-33: Updated model inheritance for User

The User model now embeds SoftDeletableModel instead of TimedModel, maintaining the same fields while gaining explicit soft deletion support.


35-46: Updated model inheritance for Device

The Device model now uses SoftDeletableModel instead of TimedModel, maintaining the same functionality while gaining explicit soft deletion support.


56-75: Updated model inheritance for Message

The Message model now embeds SoftDeletableModel instead of TimedModel, maintaining the same fields while gaining explicit soft deletion support.

internal/sms-gateway/modules/webhooks/models.go (2)

22-22: Updated Webhook model to use SoftDeletableModel

The change from models.TimedModel to models.SoftDeletableModel correctly implements soft deletion capability for webhooks, ensuring consistency with other models in the system. This change maintains backward compatibility while adding the ability to mark webhooks as deleted without physically removing them from the database.


25-27:

✅ Verification successful

Database migrations need verification

The migration function remains unchanged. Please verify that database migrations will properly handle the schema changes introduced by switching to SoftDeletableModel, particularly ensuring the deleted_at column is added to the webhooks table.


🏁 Script executed:

#!/bin/bash
# Check if there are any migration files that handle the addition of deleted_at column to webhooks table

# Search for migration files in the repository
find . -type f -name "*.go" -o -name "*.sql" | grep -i migr | grep -i webhook | xargs grep -l "deleted_at\|DeletedAt"

# Check if GORM's AutoMigrate will add the deleted_at column by looking at its behavior
grep -r "AutoMigrate" --include="*.go" .

Length of output: 564


🏁 Script executed:

#!/bin/bash
# Inspect the Webhook struct to verify SoftDelete usage
grep -R "type Webhook" -n internal/sms-gateway/modules/webhooks/models.go -A20

# Check for DeletedAt field or SoftDeletableModel embedding in the model
grep -R "DeletedAt" -n internal/sms-gateway/modules/webhooks/models.go || echo "No DeletedAt field in Webhook model"

# Inspect the SQL migration to see if deleted_at column is added
grep -n "deleted_at" internal/sms-gateway/models/migrations/mysql/20240607230753_webhooks.sql || echo "No deleted_at in SQL migration"

Length of output: 1305


🏁 Script executed:

#!/bin/bash
# Locate SoftDeletableModel definition to confirm it includes DeletedAt
grep -R "type SoftDeletableModel" -n internal/sms-gateway/models -A5

Length of output: 424


Migrations correctly include deleted_at
The SQL migration 20240607230753_webhooks.sql already adds the deleted_at column, and the SoftDeletableModel (embedded in Webhook) defines the DeletedAt field. The existing db.AutoMigrate(&Webhook{}) call will also ensure GORM recognizes this column. No further changes are required.

pkg/swagger/docs/swagger.yaml (2)

516-616: Nested settings schemas look correct
The newly introduced SettingsEncryption, SettingsGateway, SettingsLogs, SettingsMessages, SettingsPing, and SettingsWebhooks definitions are well-structured, with clear descriptions and validation constraints. They align with the device settings management requirements.


617-626: SimSelectionMode enum definition is consistent
The SimSelectionMode enum correctly lists valid SIM selection strategies and follows the established pattern of other enums.

pkg/swagger/docs/swagger.json (8)

396-545: New third-party settings endpoints are well-defined
The /3rdparty/v1/settings GET, PUT, and PATCH operations correctly reference the DeviceSettings schema, include proper security requirements, and define expected request/response payloads.


920-957: Mobile settings GET endpoint is correctly specified
The /mobile/v1/settings GET operation is secured with MobileToken and returns the DeviceSettings schema, mirroring the third-party endpoint for device-specific use cases.


1176-1227: DeviceSettings JSON definition matches YAML schema
The smsgateway.DeviceSettings object is accurately represented with nested properties. If you adopt the YAML suggestion, remember to add a top-level description.


1319-1333: LimitPeriod enum in JSON looks good
The JSON definition for LimitPeriod includes the full set of values and varnames consistent with the YAML. Consider adding a description as noted above.


1766-1773: Extended PushEventType enum is correct
The addition of MessagesExportRequested and SettingsUpdated (with corresponding varnames) aligns with the new push-notification capabilities.


1795-1796: PushNotification event value additions are consistent
The event property’s enum now includes MessagesExportRequested and SettingsUpdated, matching the PushEventType updates.


1842-1953: JSON nested settings schemas are accurate
The definitions for SettingsEncryption, SettingsGateway, SettingsLogs, SettingsMessages, SettingsPing, and SettingsWebhooks correctly mirror the YAML specifications, complete with property descriptions and validation rules.


1955-1966: SimSelectionMode JSON definition matches YAML
The SimSelectionMode enum in JSON accurately defines valid values and varnames, consistent with the overall schema design.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
internal/sms-gateway/handlers/settings/3rdparty.go (1)

89-120: Swagger documentation properly added, but validation logic needs simplification.

The Swagger documentation is now complete and comprehensive, addressing the previous review feedback. However, the same redundant validation issue exists here as in the put method.

The validation logic should be simplified similar to the put method suggestion.

🧹 Nitpick comments (4)
internal/sms-gateway/modules/settings/utils.go (1)

5-50: Consider improving rule definition clarity.

The use of empty strings as placeholders in the rule maps could be confusing. Consider using a more explicit approach to define the schema.

-var rules = map[string]any{
-	"encryption": map[string]any{
-		"passphrase": "",
-	},
+var rules = map[string]any{
+	"encryption": map[string]any{
+		"passphrase": nil, // or use a specific type marker
+	},

This would make the intent clearer that these are schema definitions rather than default values.

internal/sms-gateway/modules/settings/repository.go (2)

12-20: Clarify behavior when no settings exist for a user.

The Find() method returns an empty DeviceSettings struct when no records are found, rather than an error. This might cause confusion as the caller receives a non-nil result even when no settings exist in the database.

Consider using First() if you expect settings to always exist, or document that an empty struct with zero-value UserID indicates no settings found.

 func (r *repository) GetSettings(userID string) (*DeviceSettings, error) {
 	settings := &DeviceSettings{}
-	err := r.db.Where("user_id = ?", userID).Limit(1).Find(settings).Error
+	err := r.db.Where("user_id = ?", userID).First(settings).Error
+	if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
+		return nil, nil // or return a specific "not found" error
+	}
 	if err != nil {
 		return nil, err
 	}

 	return settings, nil
 }

39-44: Implementation looks good with minor naming suggestion.

The method correctly uses a transaction for the replace operation. Consider using the transaction parameter more explicitly for clarity.

 func (r *repository) ReplaceSettings(settings *DeviceSettings) (*DeviceSettings, error) {
-	err := r.db.Transaction(func(tx *gorm.DB) error {
+	var err error
+	err = r.db.Transaction(func(tx *gorm.DB) error {
 		return tx.Save(settings).Error
 	})
 	return settings, err
 }
internal/sms-gateway/handlers/settings/3rdparty.go (1)

69-87: Simplify redundant validation logic.

The method performs validation twice - first with BodyParserValidator against the DeviceSettings schema, then with BodyParser into a generic map. This is redundant and potentially confusing.

Consider simplifying to a single parsing step:

 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))
 	}

+	// Optional: Add custom validation for the settings map if needed
+
 	updated, err := h.settingsSvc.ReplaceSettings(user.ID, settings)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 509c601 and cf38806.

📒 Files selected for processing (8)
  • internal/sms-gateway/app.go (2 hunks)
  • internal/sms-gateway/handlers/settings/3rdparty.go (1 hunks)
  • internal/sms-gateway/handlers/settings/mobile.go (1 hunks)
  • internal/sms-gateway/modules/settings/models.go (1 hunks)
  • internal/sms-gateway/modules/settings/module.go (1 hunks)
  • internal/sms-gateway/modules/settings/repository.go (1 hunks)
  • internal/sms-gateway/modules/settings/service.go (1 hunks)
  • internal/sms-gateway/modules/settings/utils.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/sms-gateway/app.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/sms-gateway/handlers/settings/mobile.go
🧰 Additional context used
🧬 Code Graph Analysis (3)
internal/sms-gateway/modules/settings/models.go (1)
internal/sms-gateway/models/models.go (2)
  • User (27-33)
  • TimedModel (17-20)
internal/sms-gateway/modules/settings/module.go (4)
internal/sms-gateway/modules/devices/module.go (1)
  • Module (16-32)
internal/sms-gateway/app.go (1)
  • Module (30-48)
internal/sms-gateway/modules/settings/service.go (1)
  • NewService (65-70)
internal/sms-gateway/modules/settings/models.go (1)
  • Migrate (19-24)
internal/sms-gateway/modules/settings/repository.go (1)
internal/sms-gateway/modules/settings/models.go (1)
  • DeviceSettings (10-17)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Build / Docker image (linux/amd64)
  • GitHub Check: Build / Docker image (linux/arm64)
  • GitHub Check: Analyze (go)
🔇 Additional comments (13)
internal/sms-gateway/modules/settings/module.go (2)

9-21: LGTM! Module structure follows established patterns.

The dependency injection setup is well-structured and follows the same pattern as other modules in the codebase (like the devices module). The logger decoration provides proper scoping, and the separation between private repository and public service providers is appropriate.


23-25: Migration registration follows the expected pattern.

The migration registration in the init() function is consistent with how other modules handle database migrations in this codebase.

internal/sms-gateway/modules/settings/models.go (2)

10-17: Well-designed model with proper relationships.

The DeviceSettings struct is well-structured with:

  • Correct primary key and data type consistency with the User model (varchar(32))
  • Appropriate use of JSON serialization for flexible settings storage
  • Proper foreign key relationship with cascade delete behavior
  • Good embedding of TimedModel for automatic timestamp management

19-24: Migration function is simple and appropriate.

The migration approach using GORM's AutoMigrate is suitable for this use case, with proper error wrapping for debugging.

internal/sms-gateway/modules/settings/utils.go (1)

52-77: Good recursive filtering with proper error handling.

The filterMap function correctly handles nested map structures and provides appropriate error messages for type mismatches. The logic properly handles nil values and missing fields.

internal/sms-gateway/modules/settings/service.go (4)

8-20: Clean service structure with proper dependency injection.

The service struct and dependency injection setup follow good practices with clear separation of concerns.


22-29: GetSettings method is well-implemented.

The method correctly retrieves settings from the repository and applies public filtering for external consumption.


48-63: ReplaceSettings implementation is efficient and correct.

The method efficiently filters input, replaces settings, and returns the filtered result without unnecessary database calls.


65-70: Constructor follows dependency injection best practices.

The NewService constructor properly initializes the service with scoped logging.

internal/sms-gateway/modules/settings/repository.go (1)

46-50: Constructor implementation is correct.

Clean and straightforward dependency injection pattern.

internal/sms-gateway/handlers/settings/3rdparty.go (3)

18-33: Well-structured dependency injection setup.

The controller properly uses Uber Fx for dependency injection and correctly embeds the base handler for common functionality like logging and validation.


35-53: Get handler implementation is solid.

Good Swagger documentation, proper error handling with error wrapping, and clean service interaction.


122-137: Route registration and constructor are well-implemented.

Proper HTTP method mapping, consistent use of user authentication middleware, and clean dependency injection in the constructor.

@capcom6 capcom6 force-pushed the feature/device-settings branch from f92dfa7 to 4f23d10 Compare May 24, 2025 10:01
@capcom6 capcom6 force-pushed the feature/device-settings branch from 960f032 to a181aab Compare May 26, 2025 23:15
@capcom6 capcom6 marked this pull request as ready for review May 27, 2025 09:08
@capcom6 capcom6 merged commit 4ce4181 into master May 27, 2025
10 checks passed
@capcom6 capcom6 deleted the feature/device-settings branch May 27, 2025 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant