Skip to content

Comments

[localserver] add settings import and export#226

Merged
capcom6 merged 3 commits intomasterfrom
issue/214-settings-export-import
May 28, 2025
Merged

[localserver] add settings import and export#226
capcom6 merged 3 commits intomasterfrom
issue/214-settings-export-import

Conversation

@capcom6
Copy link
Owner

@capcom6 capcom6 commented May 20, 2025

Summary by CodeRabbit

  • New Features

    • Enabled import/export functionality for multiple settings modules, including encryption, gateway, messages, ping, logs, and webhooks.
    • Added /settings API endpoint with GET and PATCH methods for retrieving and updating configuration.
    • Introduced background worker (SettingsUpdateWorker) for periodic settings synchronization.
    • Extended event handling to trigger settings updates via SettingsUpdated event.
    • Added a new notification icon for settings changes (notif_settings.xml).
    • Added notification support for settings changes with direct app launch from notification.
    • Introduced a centralized SettingsService to manage and notify about settings updates.
    • Added local server route for settings management with GET and PATCH endpoints.
  • Improvements

    • Updated API documentation with detailed settings schemas and descriptions.
    • Organized service and worker classes for better maintainability.
    • Added validation for URLs and numeric settings during import to prevent invalid configurations.
    • Enhanced string resources for clearer user messages related to settings and webhooks.
  • Bug Fixes

    • Implemented validation for URL and numeric settings during import to prevent invalid configurations.

@capcom6 capcom6 linked an issue May 20, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented May 20, 2025

Walkthrough

This update introduces comprehensive settings management, including new interfaces (Exporter, Importer), their implementations in multiple settings classes, and a centralized SettingsService. It adds a /settings API with GET and PATCH endpoints, integrates background synchronization via a worker, and updates notifications and documentation. Additionally, it extends the push event enum and API schema.

Changes

File(s) Change Summary
.../encryption/EncryptionSettings.kt Implements Importer; adds import() for passphrase import.
.../gateway/GatewaySettings.kt Implements Exporter and Importer; adds export() and import() for serverUrl and privateToken with validation.
.../logs/LogsSettings.kt, .../ping/PingSettings.kt, .../webhooks/WebhooksSettings.kt Implement Exporter and Importer; add export() and import() for respective settings with type parsing and validation.
.../messages/MessagesSettings.kt Implements Exporter and Importer; adds export() and import() for all message-related settings with validation and enum handling.
.../settings/Exporter.kt, .../settings/Importer.kt Add new interfaces: Exporter (with export()), Importer (with import()).
.../settings/SettingsService.kt Adds SettingsService for aggregating and updating all settings, with notification on update.
.../settings/Module.kt Registers SettingsService as a singleton in Koin DI module.
.../localserver/routes/SettingsRoutes.kt Adds SettingsRoutes class with GET and PATCH endpoints for settings management.
.../localserver/WebService.kt Registers /settings route using SettingsRoutes.
.../notifications/NotificationsService.kt Adds notification for settings changes, new notification ID, icon, and builder logic.
.../push/Event.kt Adds SettingsUpdated event to the Event enum.
.../gateway/GatewayApi.kt Adds getSettings(token: String): Map<String, *> method for fetching settings from API.
.../gateway/workers/SettingsUpdateWorker.kt Introduces SettingsUpdateWorker for periodic background settings sync with start/stop controls.
.../gateway/GatewayService.kt Refactors code regions for clarity; adds SettingsUpdateWorker to lifecycle methods (structural only).
.../push/Event.kt, .../services/PushService.kt Handles SettingsUpdated event by starting SettingsUpdateWorker.
.../res/drawable/notif_settings.xml Adds new vector drawable resource for settings notification icon.
docs/api/swagger.json Adds /settings endpoint (GET/PATCH), Settings schema, and improves descriptions throughout the API.

Sequence Diagram(s)

Settings Update Flow (Local Server)

sequenceDiagram
    participant Client
    participant WebService
    participant SettingsRoutes
    participant SettingsService
    participant NotificationsService

    Client->>WebService: PATCH /settings (with data)
    WebService->>SettingsRoutes: handle PATCH
    SettingsRoutes->>SettingsService: update(data)
    SettingsService->>SettingsService: Import data into settings modules
    SettingsService->>NotificationsService: notify settings changed
    SettingsService-->>SettingsRoutes: updated settings
    SettingsRoutes-->>Client: 200 OK (updated settings)
Loading

Periodic Settings Sync (Cloud Push/Event)

sequenceDiagram
    participant PushService
    participant SettingsUpdateWorker
    participant GatewayService
    participant SettingsService

    PushService->>SettingsUpdateWorker: Start on SettingsUpdated event
    SettingsUpdateWorker->>GatewayService: getSettings()
    GatewayService-->>SettingsUpdateWorker: settings map
    SettingsUpdateWorker->>SettingsService: update(settings)
    SettingsService-->>SettingsUpdateWorker: (done)
Loading

GET Settings API Flow

sequenceDiagram
    participant Client
    participant WebService
    participant SettingsRoutes
    participant SettingsService

    Client->>WebService: GET /settings
    WebService->>SettingsRoutes: handle GET
    SettingsRoutes->>SettingsService: getAll()
    SettingsService-->>SettingsRoutes: settings map
    SettingsRoutes-->>Client: 200 OK (settings map)
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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: 7

🧹 Nitpick comments (7)
app/src/main/java/me/capcom/smsgateway/modules/encryption/EncryptionSettings.kt (2)

9-9: Consider implementing the Exporter interface for consistency

The class currently implements only the Importer interface while other settings classes implement both Exporter and Importer. For consistency and to support full export/import capabilities, consider implementing the Exporter interface as well.


43-49: Add validation for the passphrase

The import method directly converts any value to a string without validation. Since the passphrase is security-sensitive, consider adding validation before storing it.

override fun import(data: Map<String, *>) {
    data.forEach { (key, value) ->
        when (key) {
-           PASSPHRASE -> storage.set(key, value?.toString())
+           PASSPHRASE -> {
+               val passphraseStr = value?.toString()
+               // Validate passphrase is not empty if provided
+               if (passphraseStr != null && passphraseStr.isNotBlank()) {
+                   storage.set(key, passphraseStr)
+               }
+           }
        }
    }
}
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt (2)

42-46: Inconsistency between export and import methods

The export() method only includes the CLOUD_URL field, while the import() method handles both CLOUD_URL and PRIVATE_TOKEN. If this is intentional (for security reasons), consider adding a comment explaining why PRIVATE_TOKEN is write-only. Otherwise, consider including it in the export for consistency.

override fun export(): Map<String, *> {
    return mapOf(
        CLOUD_URL to serverUrl,
+       // PRIVATE_TOKEN is excluded for security reasons
+       // Or include it if appropriate:
+       // PRIVATE_TOKEN to privateToken,
    )
}

48-55: Add validation for imported values

The current implementation converts values to strings without validation. Consider adding validation and error handling for the imported values to ensure they meet your requirements.

override fun import(data: Map<String, *>) {
    data.forEach { (key, value) ->
        when (key) {
-           CLOUD_URL -> storage.set(key, value?.toString())
-           PRIVATE_TOKEN -> storage.set(key, value?.toString())
+           CLOUD_URL -> {
+               val url = value?.toString()
+               if (url != null && url.isNotBlank() && (url.startsWith("http://") || url.startsWith("https://"))) {
+                   storage.set(key, url)
+               }
+           }
+           PRIVATE_TOKEN -> {
+               val token = value?.toString()
+               if (token != null && token.isNotBlank()) {
+                   storage.set(key, token)
+               }
+           }
        }
    }
}
app/src/main/java/me/capcom/smsgateway/modules/localserver/LocalServerSettings.kt (1)

49-55: Consider adding support for other settings in import method

The current implementation only handles the PORT setting, but ignores other settings like ENABLED, DEVICE_ID, etc.

override fun import(data: Map<String, *>) {
    data.forEach { (key, value) ->
        when (key) {
            PORT -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
+           ENABLED -> value?.toString()?.toBooleanStrictOrNull()?.let { storage.set(key, it) }
+           DEVICE_ID -> value?.toString()?.let { storage.set(key, it) }
+           USERNAME -> value?.toString()?.let { storage.set(key, it) }
        }
    }
}
app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt (2)

21-32: Add specific error types in GET response

The error handling is good, but consider providing more specific error information in the response.

 get {
     try {
         val settings = settingsService.getAll()
         call.respond(settings)
     } catch (e: Exception) {
+        e.printStackTrace() // Add this for consistency with the PATCH route
         call.respond(
             HttpStatusCode.InternalServerError,
-            mapOf("message" to "Failed to get settings: ${e.message}")
+            mapOf(
+                "message" to "Failed to get settings: ${e.message}",
+                "error" to e.javaClass.simpleName
+            )
         )
     }
 }

33-50: Consider adding input validation

The PATCH route correctly handles the settings update, but should validate the input data structure before attempting to apply changes.

 patch {
     try {
         val settings = call.receive<Map<String, *>>()
+        
+        // Validate that we have valid module names
+        val invalidModules = settings.keys.filter { !settingsService.hasModule(it) }
+        if (invalidModules.isNotEmpty()) {
+            call.respond(
+                HttpStatusCode.BadRequest,
+                mapOf("message" to "Unknown module(s): ${invalidModules.joinToString()}")
+            )
+            return@patch
+        }
 
         settingsService.apply(settings)
 
         call.respond(
             HttpStatusCode.OK,
             settingsService.getAll()
         )
     } catch (e: Exception) {
         e.printStackTrace()
         call.respond(
             HttpStatusCode.BadRequest,
             mapOf("message" to "Invalid request: ${e.message}")
         )
     }
 }

Note: This requires adding a hasModule(name: String) method to the SettingsService class to check if a module name is valid.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 45edf1b and 22e77a4.

📒 Files selected for processing (16)
  • app/src/main/java/me/capcom/smsgateway/modules/encryption/EncryptionSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/LocalServerSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/WebService.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/logs/LogsSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/notifications/NotificationsService.kt (4 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/ping/PingSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/settings/Exporter.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/settings/Importer.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/settings/Module.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt (2 hunks)
  • app/src/main/res/drawable/notif_settings.xml (1 hunks)
  • docs/api/swagger.json (26 hunks)
🔇 Additional comments (27)
app/src/main/java/me/capcom/smsgateway/modules/settings/Exporter.kt (1)

1-5: Well-designed interface for settings export.

The Exporter interface establishes a clean contract for settings serialization with a single method that returns a map of configuration values. This design supports a consistent export mechanism across different settings classes, enabling centralized configuration management.

app/src/main/res/drawable/notif_settings.xml (1)

1-12: Appropriate vector drawable for settings notification.

The settings icon follows Android's material design guidelines with appropriate sizing (24dp) and styling. The white tint allows the system to apply theme-appropriate coloring in notifications.

app/src/main/java/me/capcom/smsgateway/modules/localserver/WebService.kt (1)

175-178: Good implementation of settings route.

The settings route is properly implemented following the same pattern as other authenticated routes. It's appropriately placed behind authentication protection, which is essential for secure settings management.

app/src/main/java/me/capcom/smsgateway/modules/settings/Importer.kt (1)

1-5: Well-designed interface for settings import.

The Importer interface provides a clean contract for settings deserialization with a single method that accepts a map of configuration values. This complements the Exporter interface to create a complete import/export system for application settings.

app/src/main/java/me/capcom/smsgateway/modules/settings/Module.kt (1)

13-14: LGTM: Clean registration of SettingsService singleton

The addition of the SettingsService as a singleton in the Koin module is well-implemented.

Also applies to: 18-18

app/src/main/java/me/capcom/smsgateway/modules/logs/LogsSettings.kt (1)

41-45: LGTM: Export implementation is clean and straightforward

The export method is well-implemented, providing a clear mapping of the lifetimeDays setting.

app/src/main/java/me/capcom/smsgateway/modules/localserver/LocalServerSettings.kt (2)

4-5: Implementation of Exporter and Importer interfaces looks good

The class now properly implements the Exporter and Importer interfaces which enables settings management through a unified interface.

Also applies to: 11-11


43-47: Export implementation is clean and straightforward

The export method correctly maps the PORT setting to its current value.

app/src/main/java/me/capcom/smsgateway/modules/ping/PingSettings.kt (2)

3-4: Implementation of Exporter and Importer interfaces looks good

The class now properly implements the Exporter and Importer interfaces which enables settings management through a unified interface.

Also applies to: 10-10


22-26: Export implementation is clean and straightforward

The export method correctly maps the INTERVAL_SECONDS setting to its current value.

app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt (2)

4-5: Implementation of Exporter and Importer interfaces looks good

The class now properly implements the Exporter and Importer interfaces which enables settings management through a unified interface.

Also applies to: 11-11


32-37: Export implementation is clean and straightforward

The export method correctly maps the INTERNET_REQUIRED and RETRY_COUNT settings to their current values.

app/src/main/java/me/capcom/smsgateway/modules/notifications/NotificationsService.kt (5)

24-25: New notification type for settings changes added correctly

The code properly adds a new notification type with corresponding icon for settings changes.


27-31: Good use of builder pattern for notification configuration

Creating a map of builder functions allows for clean and extensible configuration of different notification types.


46-48: Helpful convenience method for notifications

The new notify method simplifies the process of sending notifications by handling the creation and display in one step.


55-63: Well-implemented notification action with PendingIntent

The code properly sets up a PendingIntent to launch the app when the notification is tapped and applies any additional configuration from the builders map.

Consider generating a unique request code for the PendingIntent instead of using 0 to avoid potential intent collisions if multiple notifications are created:

PendingIntent.getActivity(
    context,
-   0,
+   id, // Use notification ID as request code for uniqueness
    context.packageManager.getLaunchIntentForPackage(context.packageName),
    PendingIntent.FLAG_IMMUTABLE
)

74-74: Notification ID constant properly defined

The new notification ID constant is appropriately defined and follows the existing pattern.

app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (3)

3-4: Good implementation of interfaces

Adding the Exporter and Importer interfaces to support unified settings management is a good approach for extending functionality.


10-10: Interface implementation looks good

The class now properly implements the Exporter and Importer interfaces.


89-98: Well-structured export implementation

The export method is well-structured and exports all relevant settings with their storage keys.

app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt (1)

12-19: Good route registration design pattern

The class follows a good design pattern for Ktor route registration with constructor dependency injection.

app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt (1)

24-32: Good organization of settings modules

The approach of organizing settings by module with a map is clean and maintainable.

docs/api/swagger.json (5)

6-6: Good clarification in API description

The updated description more clearly states that this API is for sending SMS messages.


489-555: Well-structured API documentation for settings endpoints

The settings endpoints are well-documented with proper request/response schemas and security requirements.


568-569: Good addition of Settings tag

Adding a dedicated tag for Settings endpoints improves API documentation organization.


627-636: Good response definition for settings

The response schema for settings is well-defined and reuses the Settings schema.


965-1077: Complete settings schema definition

The Settings schema is comprehensive, covering all modules with appropriate property types and nullability.

One suggestion for future consideration: It might be helpful to add descriptions for each setting property to explain its purpose and constraints.

Comment on lines +47 to +53
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
LIFETIME_DAYS -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify the lifetimeDays conversion logic

The current conversion chain (toString()?.toFloat()?.toInt()?.toString()) is complex and error-prone. A simpler approach would be to convert directly to an integer and handle potential conversion errors.

override fun import(data: Map<String, *>) {
    data.forEach { (key, value) ->
        when (key) {
-           LIFETIME_DAYS -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
+           LIFETIME_DAYS -> {
+               try {
+                   val days = when (value) {
+                       is Int -> value
+                       is Double, is Float -> value.toString().toFloat().toInt()
+                       is String -> value.toIntOrNull()
+                       else -> null
+                   }
+                   
+                   if (days != null && days > 0) {
+                       storage.set(key, days.toString())
+                   }
+               } catch (e: NumberFormatException) {
+                   // Handle conversion error - maybe log it
+               }
+           }
        }
    }
}
📝 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
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
LIFETIME_DAYS -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
}
}
}
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
LIFETIME_DAYS -> {
try {
val days = when (value) {
is Int -> value
is Double, is Float -> value.toString().toFloat().toInt()
is String -> value.toIntOrNull()
else -> null
}
if (days != null && days > 0) {
storage.set(key, days.toString())
}
} catch (e: NumberFormatException) {
// Handle conversion error - maybe log it
}
}
}
}
}
🤖 Prompt for AI Agents
In app/src/main/java/me/capcom/smsgateway/modules/logs/LogsSettings.kt around
lines 47 to 53, simplify the conversion of lifetimeDays by directly converting
the value to an integer instead of chaining toString, toFloat, toInt, and back
to string. Implement a safer conversion that attempts to parse the value as an
integer and handles any potential errors or nulls gracefully before storing it.

Comment on lines 49 to 55
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
PORT -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify type conversion and add error handling in import method

The current type conversion chain is overly complex and lacks error handling for conversion failures.

override fun import(data: Map<String, *>) {
    data.forEach { (key, value) ->
        when (key) {
-           PORT -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
+           PORT -> try {
+               val portValue = value?.toString()?.toIntOrNull()
+               if (portValue != null && portValue > 0) {
+                   storage.set(key, portValue.toString())
+               }
+           } catch (e: Exception) {
+               // Log error or handle invalid port value
+           }
        }
    }
}
📝 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
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
PORT -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
}
}
}
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
PORT -> try {
val portValue = value?.toString()?.toIntOrNull()
if (portValue != null && portValue > 0) {
storage.set(key, portValue.toString())
}
} catch (e: Exception) {
// Log error or handle invalid port value
}
}
}
}
🤖 Prompt for AI Agents
In
app/src/main/java/me/capcom/smsgateway/modules/localserver/LocalServerSettings.kt
around lines 49 to 55, simplify the type conversion in the import method by
directly converting the value to an integer if possible, avoiding unnecessary
chaining through float and string conversions. Add error handling to catch any
exceptions or invalid values during conversion and handle them gracefully, such
as by logging an error or skipping the invalid entry, to prevent runtime
crashes.

Comment on lines +28 to +37
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
INTERVAL_SECONDS -> storage.set(
key,
value?.toString()?.toFloat()?.toInt()?.toString()
)
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify type conversion and add error handling in import method

The current type conversion chain is overly complex and lacks error handling for conversion failures.

override fun import(data: Map<String, *>) {
    data.forEach { (key, value) ->
        when (key) {
-           INTERVAL_SECONDS -> storage.set(
-               key,
-               value?.toString()?.toFloat()?.toInt()?.toString()
-           )
+           INTERVAL_SECONDS -> try {
+               val intervalValue = value?.toString()?.toIntOrNull()
+               if (intervalValue != null && intervalValue >= 0) {
+                   storage.set(key, intervalValue.toString())
+               }
+           } catch (e: Exception) {
+               // Log error or handle invalid interval value
+           }
        }
    }
}
📝 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
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
INTERVAL_SECONDS -> storage.set(
key,
value?.toString()?.toFloat()?.toInt()?.toString()
)
}
}
}
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
INTERVAL_SECONDS -> try {
val intervalValue = value?.toString()?.toIntOrNull()
if (intervalValue != null && intervalValue >= 0) {
storage.set(key, intervalValue.toString())
}
} catch (e: Exception) {
// Log error or handle invalid interval value
}
}
}
}
🤖 Prompt for AI Agents
In app/src/main/java/me/capcom/smsgateway/modules/ping/PingSettings.kt around
lines 28 to 37, simplify the type conversion in the import method by directly
converting the value to an integer in a safer way. Add error handling to catch
any exceptions or invalid conversions during this process, ensuring the method
does not fail silently or crash. Use a clear and concise approach to convert and
store the value, handling nulls and invalid formats gracefully.

Comment on lines 39 to 47
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
INTERNET_REQUIRED -> storage.set(key, value?.toString()?.toBoolean())
RETRY_COUNT -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
SIGNING_KEY -> storage.set(key, value?.toString())
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type conversion and add error handling in import method

The current implementation has a complex conversion chain for RETRY_COUNT and lacks error handling for conversion failures.

override fun import(data: Map<String, *>) {
    data.forEach { (key, value) ->
        when (key) {
-           INTERNET_REQUIRED -> storage.set(key, value?.toString()?.toBoolean())
-           RETRY_COUNT -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
+           INTERNET_REQUIRED -> value?.toString()?.toBooleanStrictOrNull()?.let { storage.set(key, it) }
+           RETRY_COUNT -> try {
+               val retryValue = value?.toString()?.toIntOrNull()
+               if (retryValue != null && retryValue >= 0) {
+                   storage.set(key, retryValue.toString())
+               }
+           } catch (e: Exception) {
+               // Log error or handle invalid retry count
+           }
            SIGNING_KEY -> storage.set(key, value?.toString())
        }
    }
}
📝 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
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
INTERNET_REQUIRED -> storage.set(key, value?.toString()?.toBoolean())
RETRY_COUNT -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
SIGNING_KEY -> storage.set(key, value?.toString())
}
}
}
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
INTERNET_REQUIRED -> value
?.toString()
?.toBooleanStrictOrNull()
?.let { storage.set(key, it) }
RETRY_COUNT -> try {
val retryValue = value
?.toString()
?.toIntOrNull()
if (retryValue != null && retryValue >= 0) {
storage.set(key, retryValue.toString())
}
} catch (e: Exception) {
// Log error or handle invalid retry count
}
SIGNING_KEY -> storage.set(key, value?.toString())
}
}
}
🤖 Prompt for AI Agents
In app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt
around lines 39 to 47, the import method uses a convoluted conversion chain for
RETRY_COUNT and lacks error handling for conversion failures. Simplify the
conversion by directly converting the value to an integer safely, and add
try-catch or null checks to handle any conversion errors gracefully, ensuring
the method does not throw exceptions on invalid input.

Comment on lines 100 to 125
override fun import(data: Map<String, *>) {
data.forEach { (key, value) ->
when (key) {
SEND_INTERVAL_MIN -> storage.set(
key,
value?.toString()?.toFloat()?.toInt()?.toString()
)

SEND_INTERVAL_MAX -> storage.set(
key,
value?.toString()?.toFloat()?.toInt()?.toString()
)

LIMIT_PERIOD -> storage.set(key, value?.let { Period.valueOf(it.toString()) })
LIMIT_VALUE -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
SIM_SELECTION_MODE -> storage.set(
key,
value?.let { SimSelectionMode.valueOf(it.toString()) })

LOG_LIFETIME_DAYS -> storage.set(
key,
value?.toString()?.toFloat()?.toInt()?.toString()
)
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for enum parsing

The import method has potential exception scenarios when parsing enum values that should be handled.

 override fun import(data: Map<String, *>) {
     data.forEach { (key, value) ->
         when (key) {
             SEND_INTERVAL_MIN -> storage.set(
                 key,
                 value?.toString()?.toFloat()?.toInt()?.toString()
             )

             SEND_INTERVAL_MAX -> storage.set(
                 key,
                 value?.toString()?.toFloat()?.toInt()?.toString()
             )

-            LIMIT_PERIOD -> storage.set(key, value?.let { Period.valueOf(it.toString()) })
+            LIMIT_PERIOD -> storage.set(key, value?.let { 
+                try {
+                    Period.valueOf(it.toString())
+                } catch (e: IllegalArgumentException) {
+                    null // Or provide a default value like Period.Disabled
+                }
+            })
             LIMIT_VALUE -> storage.set(key, value?.toString()?.toFloat()?.toInt()?.toString())
-            SIM_SELECTION_MODE -> storage.set(
-                key,
-                value?.let { SimSelectionMode.valueOf(it.toString()) })
+            SIM_SELECTION_MODE -> storage.set(
+                key,
+                value?.let { 
+                    try {
+                        SimSelectionMode.valueOf(it.toString())
+                    } catch (e: IllegalArgumentException) {
+                        null // Or provide a default value like SimSelectionMode.OSDefault
+                    }
+                })

             LOG_LIFETIME_DAYS -> storage.set(
                 key,
                 value?.toString()?.toFloat()?.toInt()?.toString()
             )
         }
     }
 }
🤖 Prompt for AI Agents
In app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt
between lines 100 and 125, the import method parses enum values without error
handling, which can cause exceptions if the input is invalid. Wrap the enum
parsing calls for Period.valueOf and SimSelectionMode.valueOf in try-catch
blocks to catch IllegalArgumentException or other relevant exceptions, and
handle them gracefully, such as by logging the error or skipping the invalid
value, to prevent the method from crashing.

Comment on lines 38 to 50
fun apply(data: Map<String, *>) {
data.forEach { (key, value) ->
settings[key]?.let {
(it as? Importer)?.import(value as Map<String, *>)
}
}

notificationsService.notify(
context,
NotificationsService.NOTIFICATION_ID_SETTINGS_CHANGED,
"Settings changed via API. Restart the app to apply changes."
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation and better error handling in apply()

The apply() method should validate input data and handle errors more gracefully.

 fun apply(data: Map<String, *>) {
+    val errors = mutableListOf<String>()
+    
     data.forEach { (key, value) ->
         settings[key]?.let {
-            (it as? Importer)?.import(value as Map<String, *>)
+            if (it !is Importer) {
+                errors.add("Module '$key' does not support importing settings")
+                return@forEach
+            }
+            
+            if (value !is Map<*, *>) {
+                errors.add("Settings for module '$key' must be a map")
+                return@forEach
+            }
+            
+            try {
+                @Suppress("UNCHECKED_CAST")
+                it.import(value as Map<String, *>)
+            } catch (e: Exception) {
+                errors.add("Failed to import settings for module '$key': ${e.message}")
+            }
         }
     }
 
-    notificationsService.notify(
-        context,
-        NotificationsService.NOTIFICATION_ID_SETTINGS_CHANGED,
-        "Settings changed via API. Restart the app to apply changes."
-    )
+    if (errors.isEmpty()) {
+        notificationsService.notify(
+            context,
+            NotificationsService.NOTIFICATION_ID_SETTINGS_CHANGED,
+            "Settings changed via API. Restart the app to apply changes."
+        )
+    } else {
+        throw IllegalArgumentException(errors.joinToString("\n"))
+    }
 }

Additionally, consider adding a public method to check if a module exists:

fun hasModule(name: String): Boolean {
    return settings.containsKey(name)
}
🤖 Prompt for AI Agents
In app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt
between lines 38 and 50, the apply() method lacks input validation and error
handling. Add checks to ensure that the value is a Map before casting and
importing, and handle any exceptions during import gracefully, possibly logging
errors without stopping the entire process. Also, add a public hasModule(name:
String): Boolean method that returns true if the settings map contains the given
module name, to allow external checks for module existence.

Comment on lines +34 to +36
fun getAll(): Map<String, *> {
return settings.mapValues { (it.value as? Exporter)?.export() }
}
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 handling null values in getAll()

The getAll() method uses a safe cast but doesn't handle the case where a module isn't an Exporter.

 fun getAll(): Map<String, *> {
-    return settings.mapValues { (it.value as? Exporter)?.export() }
+    return settings.mapValues { (_, value) -> 
+        (value as? Exporter)?.export() ?: emptyMap<String, Any>() 
+    }
 }
📝 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
fun getAll(): Map<String, *> {
return settings.mapValues { (it.value as? Exporter)?.export() }
}
fun getAll(): Map<String, *> {
return settings.mapValues { (_, value) ->
(value as? Exporter)?.export() ?: emptyMap<String, Any>()
}
}
🤖 Prompt for AI Agents
In app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt at
lines 34 to 36, the getAll() method uses a safe cast to Exporter but does not
handle null values when the cast fails. Update the mapValues transformation to
provide a default value or handle the null case explicitly for entries where the
value is not an Exporter, ensuring the returned map does not contain nulls or
unexpected values.

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: 1

♻️ Duplicate comments (2)
app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt (2)

39-54: Improve type conversion and add error handling in import method

The current implementation has a complex conversion chain and insufficient error handling for conversion failures. The retry count validation is good, but the code should handle potential conversion exceptions.

override fun import(data: Map<String, *>) {
    data.forEach { (key, value) ->
        when (key) {
-           INTERNET_REQUIRED -> storage.set(key, value?.toString()?.toBoolean())
+           INTERNET_REQUIRED -> value?.toString()?.toBooleanStrictOrNull()?.let { storage.set(key, it) }
            RETRY_COUNT -> {
-               val retryCount = value?.toString()?.toInt()
-               if (retryCount != null && retryCount < 1) {
-                   throw IllegalArgumentException("Retry count must be >= 1")
-               }
-               storage.set(key, retryCount?.toString())
+               try {
+                   val retryCount = value?.toString()?.toIntOrNull()
+                   if (retryCount != null) {
+                       if (retryCount < 1) {
+                           throw IllegalArgumentException("Retry count must be >= 1")
+                       }
+                       storage.set(key, retryCount.toString())
+                   }
+               } catch (e: Exception) {
+                   throw IllegalArgumentException("Invalid retry count: ${value}", e)
+               }
            }

            SIGNING_KEY -> storage.set(key, value?.toString())
        }
    }
}

43-49: 🛠️ Refactor suggestion

Consider using toIntOrNull() instead of toInt() to safely handle conversions

The current implementation could throw NumberFormatException if the retry count is not a valid integer. Using toIntOrNull() would make the code more robust.

RETRY_COUNT -> {
-   val retryCount = value?.toString()?.toInt()
+   val retryCount = value?.toString()?.toIntOrNull()
    if (retryCount != null && retryCount < 1) {
        throw IllegalArgumentException("Retry count must be >= 1")
    }
    storage.set(key, retryCount?.toString())
}
🧹 Nitpick comments (2)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt (2)

48-62: Good implementation with security check, but consider null handling improvements.

The import method has appropriate validation for the HTTPS scheme, which is important for security. However, the current implementation has a few areas for improvement:

  1. The code passes potentially null URL to storage.set() without explicit handling
  2. Consider adding more comprehensive URL validation

Consider enhancing the null handling and URL validation:

override fun import(data: Map<String, *>) {
    data.forEach { (key, value) ->
        when (key) {
            CLOUD_URL -> {
                val url = value?.toString()
                if (url != null && !url.startsWith("https://")) {
                    throw IllegalArgumentException("url must start with https://")
                }
-               storage.set(key, url)
+               // Only set non-null values or clear the setting if explicitly null
+               if (value != null) {
+                   storage.set(key, url)
+               } else {
+                   storage.remove(key)
+               }
            }

-           PRIVATE_TOKEN -> storage.set(key, value?.toString())
+           PRIVATE_TOKEN -> {
+               if (value != null) {
+                   storage.set(key, value.toString())
+               } else {
+                   storage.remove(key)
+               }
+           }
        }
    }
}

Note: The above suggestion assumes your KeyValueStorage has a remove() method. If not, adjust accordingly.


48-57: Consider adding KDoc comments for better documentation.

The implemented methods would benefit from KDoc comments explaining their purpose, parameters, and potential exceptions.

Add documentation for clarity:

/**
 * Imports settings from a map of key-value pairs.
 * 
 * @param data Map containing settings to import
 * @throws IllegalArgumentException if CLOUD_URL doesn't start with "https://"
 */
override fun import(data: Map<String, *>) {
    // Implementation...
}

/**
 * Exports current settings as a map of key-value pairs.
 * 
 * @return Map containing exported settings
 */
override fun export(): Map<String, *> {
    // Implementation...
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 22e77a4 and 3dddab4.

📒 Files selected for processing (5)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt
🔇 Additional comments (4)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt (3)

3-4: Import statements added for interfaces - Good addition.

The import statements for Exporter and Importer interfaces align with the PR objective of adding settings import/export functionality.


10-10: Well-implemented interface adoption.

The class now implements the Exporter and Importer interfaces as part of the unified settings management system.


42-46: Export implementation only includes serverUrl.

The export method correctly serializes the serverUrl under the CLOUD_URL key. However, I notice that privateToken is not included in the export while it's handled in the import method.

Is this asymmetry between export and import intentional? If privateToken is sensitive, consider adding a comment explaining why it's excluded from exports.

app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt (1)

4-5: Implementation of Exporter and Importer interfaces looks good.

The WebhooksSettings class now correctly implements the Exporter and Importer interfaces, which aligns with the broader settings management system changes.

Also applies to: 11-11

Comment on lines +32 to +37
override fun export(): Map<String, *> {
return mapOf(
INTERNET_REQUIRED to internetRequired,
RETRY_COUNT to retryCount,
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

The export method should include the signingKey in returned data.

The export method creates a map of settings but omits the signingKey property. This is inconsistent with the import method which handles the signingKey. For completeness and symmetry between import/export operations, consider adding the signingKey to the exported map.

override fun export(): Map<String, *> {
    return mapOf(
        INTERNET_REQUIRED to internetRequired,
        RETRY_COUNT to retryCount,
+       SIGNING_KEY to signingKey,
    )
}
📝 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
override fun export(): Map<String, *> {
return mapOf(
INTERNET_REQUIRED to internetRequired,
RETRY_COUNT to retryCount,
)
}
override fun export(): Map<String, *> {
return mapOf(
INTERNET_REQUIRED to internetRequired,
RETRY_COUNT to retryCount,
SIGNING_KEY to signingKey,
)
}
🤖 Prompt for AI Agents
In app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt
around lines 32 to 37, the export method returns a map missing the signingKey
property. To fix this, add the signingKey to the map returned by export so it
matches the import method and includes all relevant settings.

Repository owner deleted a comment from coderabbitai bot May 20, 2025
Repository owner deleted a comment from coderabbitai bot May 20, 2025
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: 1

🧹 Nitpick comments (3)
app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt (3)

21-37: Consider improving error handling and logging.

The error handling could be enhanced in a few ways:

  1. Use a proper logging system instead of printStackTrace()
  2. Consider implementing a maximum retry count or conditional retry logic
  3. Catch more specific exceptions rather than the broad Throwable
override suspend fun doWork(): Result {
    val gatewaySvc: GatewayService = get()
    val settingsSvc: SettingsService = get()

    return try {
        val settings = gatewaySvc.getSettings()

        settings?.let {
            settingsSvc.update(settings)
        }

        Result.success()
-    } catch (th: Throwable) {
-        th.printStackTrace()
-        Result.retry()
+    } catch (e: Exception) {
+        android.util.Log.e("SettingsUpdateWorker", "Failed to update settings", e)
+        // Optional: Check how many times this work has been retried
+        val runAttemptCount = runAttemptCount
+        return if (runAttemptCount < 3) {
+            Result.retry()
+        } else {
+            Result.failure()
+        }
    }
}

42-64: Consider making the sync interval configurable.

The 24-hour interval is hard-coded. Consider making it configurable through settings, allowing adjustments based on user needs or app requirements.

Additionally, consider adding battery optimization constraints (e.g., device charging) for better battery life.

fun start(context: Context) {
+    // Get interval from settings, default to 24 hours
+    val intervalHours = 24L
    val work = PeriodicWorkRequestBuilder<SettingsUpdateWorker>(
-        24,
+        intervalHours,
        TimeUnit.HOURS
    )
        .setBackoffCriteria(
            BackoffPolicy.EXPONENTIAL,
            WorkRequest.MIN_BACKOFF_MILLIS,
            TimeUnit.MILLISECONDS
        )
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
+                .setRequiresBatteryNotLow(true) // Optional: Add battery constraint
                .build()
        )
        .build()
    WorkManager.getInstance(context)
        .enqueueUniquePeriodicWork(
            NAME,
            ExistingPeriodicWorkPolicy.REPLACE,
            work
        )
}

28-31: Consider handling the null settings case explicitly.

When settings is null, the code silently continues. Consider adding a log or specific handling for this case to help with debugging.

    val settings = gatewaySvc.getSettings()

    settings?.let {
        settingsSvc.update(settings)
+    } ?: run {
+        android.util.Log.d("SettingsUpdateWorker", "Retrieved settings were null")
    }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3dddab4 and 35c794a.

📒 Files selected for processing (5)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (5 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt
🔇 Additional comments (2)
app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt (2)

19-20: Good use of CoroutineWorker for background processing!

Using CoroutineWorker is a great choice for background tasks that involve network operations. The implementation with KoinComponent provides clean dependency injection.


1-71: Overall well-structured worker implementation.

The worker is well-structured with appropriate constraints for periodic execution. The use of WorkManager's unique work name ensures only one instance runs at a time, and the proper lifecycle management with start/stop methods provides good control over the worker's execution.

Comment on lines +53 to +55
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider network type constraints based on app requirements.

The worker currently runs on any network connection. Depending on your app's requirements and the potential size of settings data, you might want to consider specifying whether it should run on metered connections.


🏁 Script executed:

#!/bin/bash
# Checking if there are any settings for network type preference elsewhere in the code
rg -i "networktype" --type kotlin

Length of output: 3546


Review NetworkType Constraints

All gateway and webhook workers—including SettingsUpdateWorker—currently use NetworkType.CONNECTED. If your settings payload is small and occasional metered uploads are acceptable, this is fine. Otherwise, you may want to:

  • Use NetworkType.UNMETERED to restrict work to unmetered networks, or
  • Expose a runtime/user preference so the app can choose between CONNECTED and UNMETERED.

Affected workers for consistency review:
• SettingsUpdateWorker.kt
• RegistrationWorker.kt
• PullMessagesWorker.kt
• SendStateWorker.kt
• WebhooksUpdateWorker.kt
• SendWebhookWorker.kt

🤖 Prompt for AI Agents
In
app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt
around lines 53 to 55, the network constraint is set to NetworkType.CONNECTED,
allowing the worker to run on any network including metered ones. To align with
app requirements and manage data usage, update the constraint to
NetworkType.UNMETERED if uploads should avoid metered networks, or implement a
runtime/user preference to toggle between CONNECTED and UNMETERED. This change
ensures the worker respects network usage policies and maintains consistency
with other gateway and webhook workers.

@capcom6 capcom6 force-pushed the issue/214-settings-export-import branch 2 times, most recently from c81978b to a9a00cc Compare May 26, 2025 09:31
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: 2

🔭 Outside diff range comments (1)
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (1)

38-42: 🛠️ Refactor suggestion

Potential type mismatch between persisted data and accessor

sendIntervalMin/Max are retrieved as Int (storage.get<Int>), but the import() logic stores them as String (toString()).
If KeyValueStorage is strict about types, future reads will return null or crash with ClassCastException.

Either:

  1. Persist them as Int directly, or
  2. Change the getters to read String and convert.
♻️ Duplicate comments (5)
app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt (1)

32-37: The export method should include the signingKey in returned data.

The export method creates a map of settings but omits the signingKey property. This is inconsistent with the import method which handles the signingKey. For completeness and symmetry between import/export operations, consider adding the signingKey to the exported map.

app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (1)

100-135: Enum parsing still lacks error-handling
Previous review already pointed this out; the risk remains: invalid values in the import payload will crash the whole PATCH request.

Wrap valueOf calls in runCatching { … }.getOrNull() (or explicit try/catch) and decide what to do with bad data (ignore, default, log).

app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt (1)

53-55: Review network constraint consistency

The worker is allowed on any network (CONNECTED). Earlier reviews flagged similar workers: if settings payloads are sizeable or sensitive to metered data, switch to UNMETERED or expose a user preference.

app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt (2)

31-33: Handle null values in getAll() method

The current implementation can return null values when a settings module doesn't implement the Exporter interface, which could cause issues for API consumers.

 fun getAll(): Map<String, *> {
-    return settings.mapValues { (it.value as? Exporter)?.export() }
+    return settings.mapValues { (_, value) -> 
+        (value as? Exporter)?.export() ?: emptyMap<String, Any>() 
+    }
 }

35-51: Add comprehensive input validation and error handling

The current update() method lacks proper input validation and could fail unexpectedly with unclear error messages.

 fun update(data: Map<String, *>) {
+    val errors = mutableListOf<String>()
+    
     data.forEach { (key, value) ->
-        try {
-            settings[key]?.let {
-                (it as? Importer)?.import(value as Map<String, *>)
+        settings[key]?.let {
+            if (it !is Importer) {
+                errors.add("Module '$key' does not support importing settings")
+                return@forEach
             }
-        } catch (e: IllegalArgumentException) {
-            throw IllegalArgumentException("Failed to import $key: ${e.message}", e)
+            
+            if (value !is Map<*, *>) {
+                errors.add("Settings for module '$key' must be a map")
+                return@forEach
+            }
+            
+            try {
+                @Suppress("UNCHECKED_CAST")
+                it.import(value as Map<String, *>)
+            } catch (e: Exception) {
+                errors.add("Failed to import settings for module '$key': ${e.message}")
+            }
         }
     }

-    notificationsService.notify(
-        context,
-        NotificationsService.NOTIFICATION_ID_SETTINGS_CHANGED,
-        "Settings changed via API. Restart the app to apply changes."
-    )
+    if (errors.isEmpty()) {
+        notificationsService.notify(
+            context,
+            NotificationsService.NOTIFICATION_ID_SETTINGS_CHANGED,
+            "Settings changed via API. Restart the app to apply changes."
+        )
+    } else {
+        throw IllegalArgumentException(errors.joinToString("\n"))
+    }
 }
🧹 Nitpick comments (4)
app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt (1)

26-32: Prefer structured logging over printStackTrace()

Using printStackTrace() clutters stdout and makes it hard to trace issues in production. Ktor already provides logging facilities (or you can inject an SLF4J logger). Replace with a proper logger at an appropriate level (warn/error).

-            } catch (e: Exception) {
-                e.printStackTrace()
-                call.respond(
+            } catch (e: Exception) {
+                logger.error(e) { "Failed to get settings" }
+                call.respond(

Remember to add

private val logger = LoggerFactory.getLogger(SettingsRoutes::class.java)

and the corresponding import.

app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (1)

103-112: Redundant float→int→string conversion

value?.toString()?.toFloat()?.toInt()?.toString() does two unnecessary hops and can introduce rounding errors.

- value?.toString()?.toFloat()?.toInt()?.toString()
+ value?.toString()?.toIntOrNull()

Simpler, safer, and avoids precision loss.

app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt (1)

34-35: Use logger instead of printStackTrace()

Same reasoning as in SettingsRoutes.

app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt (1)

12-29: Consider adding a module existence check method

Based on previous review feedback, adding a public method to check module existence would be beneficial for external validation.

fun hasModule(name: String): Boolean {
    return settings.containsKey(name)
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c81978b and a9a00cc.

📒 Files selected for processing (11)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (5 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/push/Event.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/services/PushService.kt (2 hunks)
  • docs/api/swagger.json (26 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/me/capcom/smsgateway/modules/push/Event.kt
  • app/src/main/java/me/capcom/smsgateway/services/PushService.kt
🔇 Additional comments (13)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt (1)

119-123: LGTM! Well-implemented API method.

The getSettings method follows the established pattern of other API methods in the class, properly uses bearer authentication, and correctly handles the HTTP GET request to the /settings endpoint.

app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt (2)

42-46: Verify intentional omission of PRIVATE_TOKEN from export.

The export method only includes CLOUD_URL but the import method can handle both CLOUD_URL and PRIVATE_TOKEN. This might be intentional for security reasons (not exporting sensitive tokens), but please confirm this design decision.


48-62: Excellent validation and error handling.

The import method properly validates that URLs start with "https://" and throws descriptive error messages for invalid inputs. The type conversion handling is also well implemented.

app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt (1)

43-49: Good improvement in type conversion and validation.

The retry count handling now uses direct integer conversion instead of the complex string→float→int→string chain mentioned in previous reviews. The validation ensuring retry count >= 1 is also well implemented with a clear error message.

app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (4)

36-59: Excellent code organization with region comments.

The addition of region comments and proper grouping of lifecycle methods (start, stop, isActiveLiveData) significantly improves code readability and maintainability. The integration of SettingsUpdateWorker into the lifecycle is consistent with other workers.


175-179: Well-designed sealed class for registration modes.

The RegistrationMode sealed class provides excellent type safety and clarity compared to the previous ad-hoc approach. The three variants (Anonymous, WithCredentials, WithCode) clearly represent all possible registration scenarios.


152-173: Properly implemented device update method.

The updateDevice method follows the established patterns in the class, properly handles the registration state check, and emits appropriate events. The implementation is consistent with other device management methods.


223-265: New API methods are well-implemented.

The sendState, getWebHooks, and getSettings methods all follow established patterns in the class:

  • Proper state validation with registrationInfo checks
  • Consistent error handling
  • Appropriate return types and null safety

These methods provide the necessary bridge between the service layer and the new settings management functionality.

docs/api/swagger.json (5)

489-555: Well-structured settings endpoints with comprehensive documentation

The new /settings endpoints are well-designed with proper HTTP methods, security, and documentation. The PATCH method correctly uses partial update semantics, and both endpoints support both local and cloud server URLs.


965-1069: Comprehensive settings schema with proper validation attributes

The Settings schema is well-structured with appropriate use of:

  • nullable: true for optional fields
  • writeOnly: true for sensitive data like passphrases and tokens
  • Proper enum definitions for constrained values
  • Clear property descriptions

The schema covers all major configuration areas including encryption, gateway, messages, ping, logs, and webhooks.


627-636: Properly defined response component

The SettingsResponse component correctly references the Settings schema and provides appropriate content-type specification.


567-569: New Settings tag properly integrated

The new "Settings" tag is correctly added to the tags array for proper API organization.


6-6: Improved documentation clarity throughout the API

The various textual improvements enhance the API documentation:

  • More precise descriptions for endpoints and parameters
  • Clarification about webhooks in different modes
  • Better grammatical consistency
  • Enhanced field descriptions

These changes improve the developer experience when working with the API.

Also applies to: 40-40, 131-131, 214-214, 252-252, 424-424, 440-440

Comment on lines +44 to +50
} catch (e: Exception) {
e.printStackTrace()
call.respond(
HttpStatusCode.BadRequest,
mapOf("message" to "Invalid request: ${e.message}")
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Differentiate client-side errors from server faults

All exceptions in the PATCH handler currently return 400 Bad Request.
• Deserialization problems or validation failures → 400
• Internal errors (e.g., storage failure) → 500

Distinguishing them gives clients clearer feedback and keeps monitoring/alerting meaningful.

Consider catching SerializationException, IllegalArgumentException, etc. separately and mapping them to 400, while treating everything else as 500.

🤖 Prompt for AI Agents
In
app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt
between lines 44 and 50, all exceptions are currently caught and responded with
a 400 Bad Request, which does not differentiate between client-side errors and
server faults. To improve this, catch specific exceptions such as
SerializationException or IllegalArgumentException separately and respond with
400 for these, while catching general exceptions and responding with 500
Internal Server Error. This provides clearer feedback to clients and better
monitoring of server issues.

Comment on lines +26 to +33
val settings = gatewaySvc.getSettings()

settings?.let {
settingsSvc.update(settings)
}

Result.success()
} catch (th: Throwable) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Null settings still return Result.success()

If the gateway returns null, no update is applied, yet the worker reports success and the work manager won’t retry.
Return Result.retry() (or Result.failure()) when settings == null so the job is re-attempted.

-            settings?.let {
-                settingsSvc.update(settings)
-            }
-
-            Result.success()
+            if (settings == null) {
+                return Result.retry()
+            }
+            settingsSvc.update(settings)
+            Result.success()
📝 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
val settings = gatewaySvc.getSettings()
settings?.let {
settingsSvc.update(settings)
}
Result.success()
} catch (th: Throwable) {
val settings = gatewaySvc.getSettings()
if (settings == null) {
return Result.retry()
}
settingsSvc.update(settings)
Result.success()
} catch (th: Throwable) {
🤖 Prompt for AI Agents
In
app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt
around lines 26 to 33, when the getSettings() method returns null, the code
still returns Result.success(), which causes the worker to report success even
though no update occurs. To fix this, add a check for null settings and return
Result.retry() or Result.failure() if settings is null, ensuring the work is
retried or failed appropriately instead of falsely indicating success.

@capcom6 capcom6 marked this pull request as ready for review May 26, 2025 23:35
@github-actions
Copy link
Contributor

github-actions bot commented May 26, 2025

🤖 Pull request artifacts

file commit
app-release.apk 973a056
app-release.aab 973a056

@capcom6 capcom6 force-pushed the issue/214-settings-export-import branch from 037844d to 973a056 Compare May 27, 2025 23:14
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 (1)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (1)

268-275: Consider using the existing api property for consistency.

The method creates a new GatewayApi instance instead of using the existing api property. This is inconsistent with other methods and potentially inefficient.

Consider this refactor to use the existing api property:

-    suspend fun getPublicIP(): String {
-        return GatewayApi(
-            settings.serverUrl,
-            settings.privateToken
-        )
-            .getDevice(settings.registrationInfo?.token)
-            .externalIp
-    }
+    suspend fun getPublicIP(): String {
+        return api.getDevice(settings.registrationInfo?.token).externalIp
+    }

Also consider making this method internal for consistency with other similar methods in the class.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 037844d and 973a056.

📒 Files selected for processing (12)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (5 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/push/Event.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt (1 hunks)
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt (2 hunks)
  • app/src/main/java/me/capcom/smsgateway/services/PushService.kt (2 hunks)
  • app/src/main/res/values/strings.xml (6 hunks)
  • docs/api/swagger.json (26 hunks)
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (10)
  • app/src/main/java/me/capcom/smsgateway/services/PushService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/push/Event.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt
  • app/src/main/java/me/capcom/smsgateway/modules/webhooks/WebhooksSettings.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/workers/SettingsUpdateWorker.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/SettingsRoutes.kt
  • docs/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/modules/settings/SettingsService.kt
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build / build-apk
🔇 Additional comments (9)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (9)

13-13: LGTM!

The import is correctly placed and needed for the SettingsUpdateWorker integration.


36-46: Good integration of SettingsUpdateWorker in startup sequence.

The placement of SettingsUpdateWorker.start() is appropriate, and the region organization improves code readability.


48-56: Proper worker lifecycle management in stop method.

The SettingsUpdateWorker is correctly stopped in reverse order of startup, following good lifecycle management practices.


152-173: Well-implemented device update method.

The method follows existing patterns with proper error handling, early returns for invalid states, and consistent event emission.


175-179: Good encapsulation of RegistrationMode within GatewayService.

Moving the sealed class inside the service improves organization and encapsulation since it's only used within this class.


223-245: Solid implementation of sendState method.

The method properly handles null safety, data transformation, and follows the established patterns in the class.


249-256: Clean implementation of getWebHooks method.

The method has proper null safety with a sensible fallback to an empty list when no registration info is available.


260-264: Appropriate implementation of getSettings method.

The method correctly handles null safety and uses a flexible return type for the settings data structure.


36-276: Excellent code organization and feature integration.

The region-based organization significantly improves code readability and maintainability. The new settings management functionality is well-integrated with proper lifecycle management and consistent patterns throughout.

@capcom6 capcom6 merged commit 0712df0 into master May 28, 2025
3 checks passed
@capcom6 capcom6 deleted the issue/214-settings-export-import branch May 28, 2025 10:04
This was referenced Sep 3, 2025
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.

Android App - Import/Export Settings and Accounts

1 participant