Skip to content

[messages] add scheduling#351

Merged
capcom6 merged 1 commit intomasterfrom
codex/add-message-scheduling-feature
Apr 27, 2026
Merged

[messages] add scheduling#351
capcom6 merged 1 commit intomasterfrom
codex/add-message-scheduling-feature

Conversation

@capcom6
Copy link
Copy Markdown
Owner

@capcom6 capcom6 commented Apr 10, 2026

Motivation

  • Enable scheduling of messages for future delivery by introducing a scheduleAt timestamp throughout the stack.
  • Prevent scheduled messages from being picked up before their time and allow worker scheduling instead of immediate processing.

Description

  • Add scheduleAt: Date? to the public API, Swagger (swagger.json), README examples, local server request model (PostMessageRequest) and validation that scheduleAt must be in the future.
  • Persist scheduleAt on Message (added DB index), propagate it through SendParams, MessagesRepository, GatewayApi/GatewayService, and enqueue logic.
  • Make DAO pending-selection queries require (scheduleAt IS NULL OR scheduleAt <= :now) and update getPendingLifo/getPendingFifo to accept a Date now parameter.
  • Add scheduled work support in SendMessagesWorker (startAt/SCHEDULED_NAME) and update MessagesService.enqueueMessage to use startAt when scheduleAt is in the future; bump Room DB version to 20 and add auto-migration entry.
  • Update unit tests to include the new field in Message construction in MessageWithRecipientsTest.

Testing

  • Updated unit tests in app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt to initialize scheduleAt and ran unit tests with ./gradlew test, which completed successfully.
  • Built the app with ./gradlew assembleDebug to validate compilation after the schema and API changes and the build succeeded.

Codex Task

Summary by CodeRabbit

  • New Features

    • API accepts an optional scheduleAt timestamp for messages (must be a future date); requests are validated.
  • Database

    • Local database upgraded to v21 to persist scheduled timestamps plus related delivery/webhook/log/token records.
  • Behavior

    • Queue and worker scheduling honor scheduleAt, deferring execution and scheduling future worker runs as needed.
  • Tests

    • Tests adjusted to include the scheduling field.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Walkthrough

Adds optional per-message scheduling (scheduleAt) across API, DTOs, Room schema and migration, DAO filtering, repository/service logic, worker scheduling, route validation, and persistence to support delayed message dispatch.

Changes

Cohort / File(s) Summary
API schema & gateway DTO
app/src/main/assets/api/swagger.json, app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
Added optional scheduleAt (string/date-timeDate?) to OpenAPI message schema and Gateway DTO.
Local server request & domain
app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt, app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/Message.kt
PostMessageRequest gains scheduleAt: Date? and validate() enforcing payload rules (exactly one message variant, non-empty fields, port bounds, simNumber ≥1, phoneNumbers non-empty, scheduleAt must be future). Domain Message adds scheduleAt.
Room DB, entity & schema
app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt, app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt, app/schemas/.../AppDatabase/21.json
DB version bumped 20→21 with AutoMigration(20→21); Message entity adds nullable scheduleAt and index on (state,scheduleAt); new schema v21 added.
DAO
app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
Pending retrieval queries now accept now: Date and filter state='Pending' AND (scheduleAt IS NULL OR scheduleAt <= :now); added nextScheduledTime() returning earliest pending scheduleAt in ms or 0.
Send params & gateway service
app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt, app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
SendParams includes scheduleAt: Date?; GatewayService.processMessage forwards message.scheduleAt into send params.
Repository, routes & service
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt, app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt, app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
Enqueue persists scheduleAt; routes call request.validate() and propagate scheduleAt; repository uses current now when querying DAO; service computes earliest scheduled time, uses updated worker start signature, and re-arms worker for the next scheduled send in a finally block.
Worker scheduling
app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
Companion.start gains scheduleAt: Long; computes initialDelay = max(0, scheduleAt - now) and applies setInitialDelay; expedited execution only when delay == 0; added IMMEDIATE constant.
Tests & misc
app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt, various files
Tests updated to pass scheduleAt = null; trailing newline/format fixes across files.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant API as LocalServer/API
    participant Service as MessagesService
    participant Repo as MessagesRepository
    participant DAO as MessagesDao
    participant Worker as SendMessagesWorker

    Client->>API: POST /messages (with scheduleAt)
    API->>API: request.validate() (scheduleAt must be future)
    API-->>Service: enqueue(request with scheduleAt)

    Service->>Repo: enqueue(request with scheduleAt)
    Repo-->>Repo: persist Message(scheduleAt)

    Service->>DAO: nextScheduledTime()
    DAO-->>Service: earliestScheduledMs

    alt scheduleAt in future
        Service->>Worker: start(context, force=false, scheduleAt=earliestScheduledMs)
        Worker->>Worker: initialDelay = max(0, scheduleAt - now)
        Worker->>Worker: setInitialDelay(initialDelay)
    else immediate or no scheduleAt
        Service->>Worker: start(context, force=true, scheduleAt=IMMEDIATE)
        Worker->>Worker: initialDelay = 0 (expedited)
    end

    Worker->>DAO: getPendingLifo(now)/getPendingFifo(now)
    DAO->>DAO: filter state='Pending' AND (scheduleAt IS NULL OR scheduleAt <= now)
    DAO-->>Worker: ready messages
    Worker->>Service: process messages
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

ready

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title '[messages] add scheduling' clearly and concisely summarizes the main change: adding message scheduling functionality. It directly matches the primary objective of introducing scheduleAt timestamp support throughout the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/add-message-scheduling-feature

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
app/src/main/assets/api/swagger.json (1)

606-610: Add format: "date-time" to scheduleAt for OpenAPI schema consistency.

The scheduleAt field at line 609 is declared as type: "string" without the format: "date-time" annotation. Multiple other timestamp fields in this specification (receivedAt, etc.) use format: "date-time" to comply with OpenAPI 3.0's RFC 3339 format specification, enabling proper code generation and validation in client tools. This field should follow the same pattern.

Note: The nearby validUntil field (line 604) has the same issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/assets/api/swagger.json` around lines 606 - 610, Add the OpenAPI
date-time format to the timestamp string fields: update the schema entries for
scheduleAt and validUntil to include format: "date-time" (in addition to type:
"string") so they follow the same RFC3339 pattern used by other timestamp fields
like receivedAt and enable proper code generation/validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/PostMessageRequest.kt`:
- Around line 52-55: The current init validation in PostMessageRequest allows
scheduleAt == now; change the check so scheduleAt must be strictly in the
future. In the PostMessageRequest init block (the validation using
scheduleAt?.before(Date())), replace that conditional with a strict future check
such as verifying scheduleAt?.after(Date()) == true (and throw
IllegalArgumentException if it is not) so any scheduleAt equal to or before now
is rejected.

In
`@app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt`:
- Around line 121-124: In SendMessagesWorker.startAt() the call to
WorkManager.enqueueUniqueWork(...) uses the fixed SCHEDULED_NAME with
ExistingWorkPolicy.REPLACE which causes later scheduled messages to overwrite
earlier ones; change this to either generate a per-message unique work ID (e.g.,
include the message ID in the unique name passed to enqueueUniqueWork) or switch
the policy to ExistingWorkPolicy.KEEP so earlier scheduled work isn't replaced;
update the unique name creation and the enqueueUniqueWork(...) invocation
accordingly.

In `@README.md`:
- Around line 203-210: Update the curl example's scheduleAt value to a truly
future ISO-8601 timestamp so the sample matches the "future delivery" text: edit
the code block containing the curl POST example and replace the scheduleAt value
(currently "2026-04-05T09:00:00Z") with a future date/time (e.g.,
"2027-04-05T09:00:00Z") while keeping the same JSON structure and header usage;
ensure the token name scheduleAt and the POST example remain unchanged except
for the timestamp.

---

Nitpick comments:
In `@app/src/main/assets/api/swagger.json`:
- Around line 606-610: Add the OpenAPI date-time format to the timestamp string
fields: update the schema entries for scheduleAt and validUntil to include
format: "date-time" (in addition to type: "string") so they follow the same
RFC3339 pattern used by other timestamp fields like receivedAt and enable proper
code generation/validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21d74b59-6af4-43cb-95fa-ec256adf949c

📥 Commits

Reviewing files that changed from the base of the PR and between 4d6169b and 144f052.

📒 Files selected for processing (14)
  • README.md
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt

Comment thread README.md Outdated
@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from 144f052 to f0ab658 Compare April 10, 2026 07:30
Copy link
Copy Markdown

@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 (2)
app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt (1)

240-248: Cloud messages bypass scheduleAt validation that exists for local requests.

PostMessageRequest validates that scheduleAt must be in the future (context: PostMessageRequest.kt init block), but cloud messages via GatewayApi.Message have no such validation. If the cloud sends a past scheduleAt, the message will be processed immediately (which is acceptable), but the behavior differs from the local API which rejects past values outright.

Consider whether server-side validation is sufficient or if client-side validation should be added for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt`
around lines 240 - 248, GatewayService is currently mapping GatewayApi.Message
into SendParams without validating scheduleAt, causing cloud requests to accept
past scheduleAt values unlike PostMessageRequest (see its init block). Add the
same server-side validation in GatewayService before creating SendParams: if
message.scheduleAt is non-null and is before the current time, reject the
request (return/throw the same BadRequest/validation error used by
PostMessageRequest) so cloud messages mirror local API behavior; implement this
check adjacent to the mapping code that constructs SendParams.
app/src/main/assets/api/swagger.json (1)

606-610: Consider adding format: date-time for better API documentation and client code generation.

The schema correctly documents the scheduleAt field with the validation requirement. Adding the OpenAPI format specifier would improve client tooling support.

📝 Suggested enhancement
          "scheduleAt": {
            "description": "Schedule message delivery at (must be in the future)",
            "example": "2020-01-01T08:30:00Z",
+           "format": "date-time",
            "type": "string"
          },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/assets/api/swagger.json` around lines 606 - 610, The OpenAPI
schema for the scheduleAt property lacks an explicit format; update the
scheduleAt schema in swagger.json to include "format": "date-time" alongside its
"type": "string" and existing description/example so client generators and
validators recognize it as an RFC3339 timestamp.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt`:
- Around line 59-60: The query in nextScheduledTime() currently uses
COALESCE(scheduleAt, 0) which mixes types; change the SQL to explicitly exclude
NULL scheduleAt values so it returns the minimum scheduled time for messages
that have an actual schedule: replace the query with "SELECT MIN(scheduleAt)
FROM message WHERE state = 'Pending' AND scheduleAt IS NOT NULL" and keep the
function signature fun nextScheduledTime(): Long? so when no scheduled messages
exist it still returns null (or handle null at call sites to treat as 0 if
desired).

---

Nitpick comments:
In `@app/src/main/assets/api/swagger.json`:
- Around line 606-610: The OpenAPI schema for the scheduleAt property lacks an
explicit format; update the scheduleAt schema in swagger.json to include
"format": "date-time" alongside its "type": "string" and existing
description/example so client generators and validators recognize it as an
RFC3339 timestamp.

In `@app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt`:
- Around line 240-248: GatewayService is currently mapping GatewayApi.Message
into SendParams without validating scheduleAt, causing cloud requests to accept
past scheduleAt values unlike PostMessageRequest (see its init block). Add the
same server-side validation in GatewayService before creating SendParams: if
message.scheduleAt is non-null and is before the current time, reject the
request (return/throw the same BadRequest/validation error used by
PostMessageRequest) so cloud messages mirror local API behavior; implement this
check adjacent to the mapping code that constructs SendParams.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90003aa0-eca6-4d8c-a94d-c03df1885895

📥 Commits

Reviewing files that changed from the base of the PR and between 144f052 and f0ab658.

📒 Files selected for processing (14)
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/20.json
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
✅ Files skipped from review due to trivial changes (1)
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
🚧 Files skipped from review as they are similar to previous changes (8)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt

@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from 1eb8fab to 38d928b Compare April 11, 2026 07:58
@capcom6 capcom6 changed the title Add scheduled message delivery support via scheduleAt [messages] add scheduling Apr 11, 2026
@capcom6 capcom6 marked this pull request as ready for review April 11, 2026 09:48
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 11, 2026

🤖 Pull request artifacts

file commit
app-release.apk 98b34bb
app-release.aab 98b34bb
app-insecure.apk 98b34bb
app-insecure.aab 98b34bb

@capcom6 capcom6 added the ready label Apr 12, 2026
@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from 38d928b to 2699dad Compare April 14, 2026 01:21
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt`:
- Around line 59-60: The MIN query in MessagesDao.nextScheduledTime() is
operating on ISO-8601 TEXT (scheduleAt) and returning an invalid Long, breaking
the scheduleAt < earliest comparison in MessagesService.kt; fix by changing the
DAO query to convert the ISO-8601 TEXT to epoch milliseconds in SQL (use
SQLite's strftime('%s', scheduleAt) * 1000, with COALESCE/null handling) so
nextScheduledTime() returns the correct millisecond epoch Long?, and ensure
Converters.dateToString()/storage remains consistent or migrate to INTEGER epoch
storage if you prefer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d9ada47e-2396-4ef9-abbd-af4118843fe1

📥 Commits

Reviewing files that changed from the base of the PR and between 38d928b and 2699dad.

📒 Files selected for processing (14)
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
✅ Files skipped from review due to trivial changes (1)
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
🚧 Files skipped from review as they are similar to previous changes (10)
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt

Comment thread app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt Outdated
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt`:
- Around line 59-60: The problem is nextScheduledTime() returns the post-insert
global minimum, so MessagesService.enqueueMessage() can't detect when the newly
inserted message became the earliest; to fix, read the previous minimum before
inserting rather than after: in MessagesService.enqueueMessage() call
dao.nextScheduledTime() to capture priorMin, then perform
messages.enqueue(request) (or insert), then call dao.nextScheduledTime() again
and compare against priorMin to decide whether to force
SendMessagesWorker.start(force=true). Alternatively, if you prefer a DAO-based
fix, add a new DAO method like nextScheduledTimeExcluding(excludeId: Long) with
SQL excluding that id (e.g., WHERE state='Pending' AND id != :excludeId) and use
it to compare correctly; reference nextScheduledTime(),
MessagesService.enqueueMessage(), messages.enqueue(request), and
SendMessagesWorker.start(force).
- Around line 59-60: The query in MessagesDao.nextScheduledTime() uses the
sqlite-specific unixepoch() function which may not exist on older Android SQLite
builds; replace unixepoch(MIN(`scheduleAt`)) * 1000 with CAST(strftime('%s',
MIN(`scheduleAt`)) AS INTEGER) * 1000 (or simply strftime('%s',
MIN(`scheduleAt`)) * 1000) to compute milliseconds in a portable way, update the
`@Query` string accordingly in the MessagesDao interface so nextScheduledTime()
returns the same epoch-ms value without relying on unixepoch().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec961037-00d9-4c62-8e44-b84e950940ba

📥 Commits

Reviewing files that changed from the base of the PR and between 2699dad and e81e39c.

📒 Files selected for processing (1)
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt

Comment thread app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt Outdated
@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from e81e39c to dc5a241 Compare April 15, 2026 02:15
@github-actions github-actions Bot removed the ready label Apr 15, 2026
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Around line 99-110: After processing pending messages, add reschedule logic at
the end of sendPendingMessages() (or in doWork() after successful completion) to
query dao.nextScheduledTime(), and if it returns a future timestamp scheduleAt >
System.currentTimeMillis(), call SendMessagesWorker.start(context, /* earliest =
*/ determine whether this next scheduled time is the earliest via
dao.nextScheduledTime() compared to any in-memory/queued state or reuse the
existing earliest computation logic, and pass scheduleAt) so the worker is
started for the next pending scheduled message; use the same
SendMessagesWorker.start(...) signature as in MessagesService.enqueue block and
ensure you only start when there is a valid future scheduleAt.

In
`@app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt`:
- Around line 80-91: The start(...) method in SendMessagesWorker currently calls
setExpedited(...) while also setting a non-zero initialDelay, which causes
WorkManager to throw "Expedited jobs cannot be delayed"; fix by removing the
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) call or
wrapping it so it is only applied when initialDelay == 0 before building the
OneTimeWorkRequestBuilder; keep the existing setBackoffCriteria(...) and
setInitialDelay(...) logic intact and ensure the builder only receives
setExpedited when initialDelay is zero.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 76e22709-c7cb-4074-b8b3-d108c642ea80

📥 Commits

Reviewing files that changed from the base of the PR and between e81e39c and dc5a241.

📒 Files selected for processing (14)
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
✅ Files skipped from review due to trivial changes (2)
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
🚧 Files skipped from review as they are similar to previous changes (6)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt

@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from dc5a241 to 21cdb7f Compare April 16, 2026 01:56
Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt (1)

92-113: ⚠️ Potential issue | 🔴 Critical

Critical: Duplicate messages.enqueue(request) call will fail or corrupt data.

The message is enqueued at line 94, and then enqueued again at line 105. This duplicate call will either:

  1. Throw SQLiteConstraintException (which is caught and rethrows ConflictException), causing the request to fail even though the message was already persisted
  2. If the ID is somehow different, create duplicate messages

Remove the duplicate enqueue call at line 105.

🐛 Proposed fix
     fun enqueueMessage(request: SendRequest): MessageWithRecipients {
         val message = try {
             messages.enqueue(request)
         } catch (_: android.database.sqlite.SQLiteConstraintException) {
             throw ConflictException()
         }

         val priority = request.params.priority ?: Message.PRIORITY_DEFAULT
         val scheduleAt = request.params.scheduleAt?.time
         val earliest = request.params.scheduleAt?.let {
             it.time < (dao.nextScheduledTime() ?: 0)
         } ?: true
-        
-        messages.enqueue(request)

         if (scheduleAt != null && scheduleAt > System.currentTimeMillis()) {
             SendMessagesWorker.start(context, earliest, scheduleAt)
         } else {
             SendMessagesWorker.start(context, priority >= Message.PRIORITY_EXPEDITED, 0)
         }

         return message
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`
around lines 92 - 113, In enqueueMessage (MessagesService.enqueueMessage) remove
the second duplicate call to messages.enqueue(request) that appears after
computing earliest/scheduleAt; the message is already persisted by the first
try/catch block (which throws ConflictException on SQLiteConstraintException),
so delete the later messages.enqueue(request) and ensure the existing local
variable message (MessageWithRecipients) is used when scheduling via
SendMessagesWorker.start(context, earliest, scheduleAt) or
SendMessagesWorker.start(context, priority >= Message.PRIORITY_EXPEDITED, 0) and
returned at the end.
♻️ Duplicate comments (3)
app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt (1)

80-91: ⚠️ Potential issue | 🔴 Critical

setExpedited() combined with setInitialDelay() will crash at runtime.

WorkManager throws IllegalArgumentException("Expedited jobs cannot be delayed") when both setExpedited() and a non-zero setInitialDelay() are used together. Since scheduled messages will have initialDelay > 0, this will crash when attempting to schedule a future message.

🐛 Proposed fix: Apply expedited only when delay is zero
         fun start(context: Context, force: Boolean, scheduleAt: Long) {
             val initialDelay = max(0, scheduleAt - System.currentTimeMillis())

             val work = OneTimeWorkRequestBuilder<SendMessagesWorker>()
-                .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
                 .setBackoffCriteria(
                     BackoffPolicy.LINEAR,
                     WorkRequest.MIN_BACKOFF_MILLIS,
                     TimeUnit.MILLISECONDS
                 )
                 .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS)
+                .apply {
+                    if (initialDelay == 0L) {
+                        setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
+                    }
+                }
                 .build()
WorkManager setExpedited with setInitialDelay compatibility Android
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt`
around lines 80 - 91, The start function in SendMessagesWorker currently always
calls setExpedited(...) while also setting a non-zero initialDelay which will
throw IllegalArgumentException; fix by applying expedited only when initialDelay
== 0: compute initialDelay as you already do, create the
OneTimeWorkRequestBuilder<SendMessagesWorker>(), and conditionally call
setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) only if
initialDelay == 0 (otherwise skip that call) before setting backoff criteria,
setInitialDelay(initialDelay, TimeUnit.MILLISECONDS) and build the request so
scheduled messages are not marked expedited.
app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt (1)

44-48: ⚠️ Potential issue | 🟡 Minor

Validation allows scheduleAt equal to current time.

The condition scheduleAt?.before(Date()) == true only rejects times strictly before now. If scheduleAt equals the current time, before() returns false and the validation passes. Per the PR objective ("scheduleAt must be in the future"), this edge case should also be rejected.

🐛 Proposed fix
     init {
-        if (scheduleAt?.before(Date()) == true) {
+        if (scheduleAt != null && !scheduleAt.after(Date())) {
             throw IllegalArgumentException("scheduleAt must be in the future")
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt`
around lines 44 - 48, The init validation in PostMessageRequest currently allows
scheduleAt equal to now because it only checks before(Date()); update the check
to reject values that are not strictly after the current time (i.e., treat
equal-to-now as invalid). Modify the init block around scheduleAt (in class
PostMessageRequest) to detect when scheduleAt is non-null and not after the
current Date (for example, using scheduleAt != null && !scheduleAt.after(Date())
or scheduleAt.time <= Date().time) and throw the same
IllegalArgumentException("scheduleAt must be in the future") in that case.
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt (1)

107-111: ⚠️ Potential issue | 🟠 Major

Missing reschedule for remaining scheduled messages after worker completes.

When the worker runs and processes all currently-due messages, it does not reschedule itself for the next pending scheduled message. Messages with scheduleAt in the future will sit in the database until another external trigger (new message, app restart) starts the worker again.

Consider adding reschedule logic at the end of sendPendingMessages() to query dao.nextScheduledTime() and start a new worker with that delay if there are remaining scheduled messages.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`
around lines 107 - 111, After processing due messages in sendPendingMessages(),
query dao.nextScheduledTime() and if it returns a future timestamp scheduleAt >
System.currentTimeMillis(), call SendMessagesWorker.start(...) to schedule the
worker for that nextScheduledTime so remaining scheduled messages are picked up;
use the same start signature used elsewhere in this file
(SendMessagesWorker.start(context, <priorityFlagOrEarliest>, scheduleAt)) and
ensure you only reschedule when there are pending future messages returned by
dao.nextScheduledTime().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Around line 101-103: The computed earliest flag in MessagesService.kt is wrong
because dao.nextScheduledTime() returns 0 when no pending scheduled messages,
making the comparison it.time < 0 always false; update the logic where earliest
is computed (the val earliest using request.params.scheduleAt and
dao.nextScheduledTime()) to treat a 0/absent nextScheduledTime as "no next" by
substituting a sentinel like Long.MAX_VALUE (or checking == 0) before comparing,
e.g. compute val next = dao.nextScheduledTime() ?: 0L; if (next == 0L) consider
earliest = true; otherwise set earliest = it.time < next so the first scheduled
message correctly becomes the earliest and the worker scheduling
(ExistingWorkPolicy.KEEP) behaves as intended.

---

Outside diff comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Around line 92-113: In enqueueMessage (MessagesService.enqueueMessage) remove
the second duplicate call to messages.enqueue(request) that appears after
computing earliest/scheduleAt; the message is already persisted by the first
try/catch block (which throws ConflictException on SQLiteConstraintException),
so delete the later messages.enqueue(request) and ensure the existing local
variable message (MessageWithRecipients) is used when scheduling via
SendMessagesWorker.start(context, earliest, scheduleAt) or
SendMessagesWorker.start(context, priority >= Message.PRIORITY_EXPEDITED, 0) and
returned at the end.

---

Duplicate comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt`:
- Around line 44-48: The init validation in PostMessageRequest currently allows
scheduleAt equal to now because it only checks before(Date()); update the check
to reject values that are not strictly after the current time (i.e., treat
equal-to-now as invalid). Modify the init block around scheduleAt (in class
PostMessageRequest) to detect when scheduleAt is non-null and not after the
current Date (for example, using scheduleAt != null && !scheduleAt.after(Date())
or scheduleAt.time <= Date().time) and throw the same
IllegalArgumentException("scheduleAt must be in the future") in that case.

In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Around line 107-111: After processing due messages in sendPendingMessages(),
query dao.nextScheduledTime() and if it returns a future timestamp scheduleAt >
System.currentTimeMillis(), call SendMessagesWorker.start(...) to schedule the
worker for that nextScheduledTime so remaining scheduled messages are picked up;
use the same start signature used elsewhere in this file
(SendMessagesWorker.start(context, <priorityFlagOrEarliest>, scheduleAt)) and
ensure you only reschedule when there are pending future messages returned by
dao.nextScheduledTime().

In
`@app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt`:
- Around line 80-91: The start function in SendMessagesWorker currently always
calls setExpedited(...) while also setting a non-zero initialDelay which will
throw IllegalArgumentException; fix by applying expedited only when initialDelay
== 0: compute initialDelay as you already do, create the
OneTimeWorkRequestBuilder<SendMessagesWorker>(), and conditionally call
setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) only if
initialDelay == 0 (otherwise skip that call) before setting backoff criteria,
setInitialDelay(initialDelay, TimeUnit.MILLISECONDS) and build the request so
scheduled messages are not marked expedited.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2686184a-c23a-4df7-8459-a8b9686fd107

📥 Commits

Reviewing files that changed from the base of the PR and between dc5a241 and 21cdb7f.

📒 Files selected for processing (14)
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
✅ Files skipped from review due to trivial changes (5)
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/assets/api/swagger.json
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt

Comment thread app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt Outdated
Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt (1)

210-251: ⚠️ Potential issue | 🔴 Critical

Critical: Reschedule logic is unreachable dead code.

The while (true) loop at line 210 exits only via return (line 211: ?: return). The reschedule logic at lines 242-251 is placed after the loop and will never execute. This means scheduled messages won't be picked up until the next external trigger (new message, app restart, etc.).

🐛 Proposed fix: Move reschedule logic before the return
     while (true) {
-        val message = messages.getPending(settings.processingOrder) ?: return
+        val message = messages.getPending(settings.processingOrder) ?: break
         delay(1L)
         // ... rest of loop unchanged
     }
     
     // After processing all pending messages, check if there are any scheduled messages for the future
     val nextScheduledTime = dao.nextScheduledTime() ?: 0
     if (nextScheduledTime > System.currentTimeMillis()) {
-        // Determine if this is the earliest scheduled time
-        // Since we've just processed all pending messages, this should be the earliest
-        val earliest = true
-        
-        SendMessagesWorker.start(context, earliest, nextScheduledTime)
+        SendMessagesWorker.start(context, true, nextScheduledTime)
     }
 }

Alternatively, inline the reschedule check before the return:

-    val message = messages.getPending(settings.processingOrder) ?: return
+    val message = messages.getPending(settings.processingOrder)
+    if (message == null) {
+        val nextScheduledTime = dao.nextScheduledTime() ?: 0
+        if (nextScheduledTime > System.currentTimeMillis()) {
+            SendMessagesWorker.start(context, true, nextScheduledTime)
+        }
+        return
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`
around lines 210 - 251, The reschedule block after the while(true) is dead
because messages.getPending(settings.processingOrder) uses ?: return to exit the
loop; move the scheduled-message check so it executes when getPending returns
null before returning. Specifically, inside the processing loop where you call
messages.getPending(... ) ?: return, replace that return with logic that calls
dao.nextScheduledTime(), compares it to System.currentTimeMillis(), and if a
future time exists call SendMessagesWorker.start(context, /* earliest= */ true,
nextScheduledTime) (or compute earliest as needed) and then return; keep
existing flow for applyLimit(), sendMessage(...), previousPriority and
settings.sendIntervalRange unchanged. Ensure you reference messages.getPending,
dao.nextScheduledTime, SendMessagesWorker.start, sendMessage and
previousPriority when editing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Around line 210-251: The reschedule block after the while(true) is dead
because messages.getPending(settings.processingOrder) uses ?: return to exit the
loop; move the scheduled-message check so it executes when getPending returns
null before returning. Specifically, inside the processing loop where you call
messages.getPending(... ) ?: return, replace that return with logic that calls
dao.nextScheduledTime(), compares it to System.currentTimeMillis(), and if a
future time exists call SendMessagesWorker.start(context, /* earliest= */ true,
nextScheduledTime) (or compute earliest as needed) and then return; keep
existing flow for applyLimit(), sendMessage(...), previousPriority and
settings.sendIntervalRange unchanged. Ensure you reference messages.getPending,
dao.nextScheduledTime, SendMessagesWorker.start, sendMessage and
previousPriority when editing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7732ce19-8636-45d3-b80f-eeb4e9cda7d1

📥 Commits

Reviewing files that changed from the base of the PR and between 21cdb7f and ef082db.

📒 Files selected for processing (2)
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt

@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from eba68bc to 38dcc15 Compare April 17, 2026 00:48
@capcom6 capcom6 added the ready label Apr 17, 2026
@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from 38dcc15 to 7febb1e Compare April 20, 2026 02:52
@github-actions github-actions Bot removed the ready label Apr 20, 2026
Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt (2)

317-355: ⚠️ Potential issue | 🟠 Major

Include scheduleAt in local API responses.

toDomain() is used for POST, list, and detail responses, but it does not map the persisted scheduled timestamp. Scheduled messages will therefore be returned without their scheduleAt value.

🐛 Proposed response mapping
             states = states.associate {
                 it.state to Date(it.updatedAt)
-            }
+            },
+            scheduleAt = message.scheduleAt
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt`
around lines 317 - 355, The toDomain() mapper for MessageWithRecipients does not
populate the domain Message.scheduleAt, so scheduled messages come back without
their timestamp; update MessageWithRecipients.toDomain (used for
POST/list/detail) to set the domain Message's scheduleAt by mapping the
persisted value (e.g., message.scheduleAt or message.scheduledAt) into the
domain type (convert to Date if needed) when constructing
me.capcom.smsgateway.modules.localserver.domain.messages.Message so responses
include the scheduled timestamp.

242-258: ⚠️ Potential issue | 🟠 Major

Reject non-future scheduleAt before enqueueing.

This handler now passes request.scheduleAt into SendParams, but it never rejects timestamps that are already due or in the past, so the local API can accept requests that violate the scheduling contract.

🐛 Proposed validation
             if (request.simNumber != null && request.simNumber < 1) {
                 call.respond(
                     HttpStatusCode.BadRequest,
                     mapOf("message" to "simNumber must be >= 1")
                 )
                 return@post
             }
+            request.scheduleAt?.let { scheduleAt ->
+                if (!scheduleAt.after(Date())) {
+                    call.respond(
+                        HttpStatusCode.BadRequest,
+                        mapOf("message" to "scheduleAt must be in the future")
+                    )
+                    return@post
+                }
+            }
             val skipPhoneValidation =
                 call.request.queryParameters["skipPhoneValidation"]
                     ?.toBooleanStrict() ?: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt`
around lines 242 - 258, The code builds a SendRequest with request.scheduleAt
but never validates that scheduleAt is in the future; add a guard in the request
handler (where SendRequest is constructed in MessagesRoutes.kt) to check if
request.scheduleAt != null and request.scheduleAt <= now, and if so respond with
a 400 Bad Request (or equivalent error flow) rejecting the request before
creating SendRequest or enqueueing; use the same time type used elsewhere
(Date/Instant) to compare against current time, and reference SendRequest,
SendParams and the handler code that currently constructs them to locate where
to add the validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt`:
- Around line 317-355: The toDomain() mapper for MessageWithRecipients does not
populate the domain Message.scheduleAt, so scheduled messages come back without
their timestamp; update MessageWithRecipients.toDomain (used for
POST/list/detail) to set the domain Message's scheduleAt by mapping the
persisted value (e.g., message.scheduleAt or message.scheduledAt) into the
domain type (convert to Date if needed) when constructing
me.capcom.smsgateway.modules.localserver.domain.messages.Message so responses
include the scheduled timestamp.
- Around line 242-258: The code builds a SendRequest with request.scheduleAt but
never validates that scheduleAt is in the future; add a guard in the request
handler (where SendRequest is constructed in MessagesRoutes.kt) to check if
request.scheduleAt != null and request.scheduleAt <= now, and if so respond with
a 400 Bad Request (or equivalent error flow) rejecting the request before
creating SendRequest or enqueueing; use the same time type used elsewhere
(Date/Instant) to compare against current time, and reference SendRequest,
SendParams and the handler code that currently constructs them to locate where
to add the validation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4e0fcff-d145-4ec1-be6a-54a7f57463cf

📥 Commits

Reviewing files that changed from the base of the PR and between 38dcc15 and 7febb1e.

📒 Files selected for processing (14)
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
✅ Files skipped from review due to trivial changes (2)
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
  • app/src/main/assets/api/swagger.json
🚧 Files skipped from review as they are similar to previous changes (8)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt

@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from 0e8b02a to 705522a Compare April 21, 2026 01:41
Copy link
Copy Markdown

@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 (1)
app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt (1)

90-93: ⚠️ Potential issue | 🟠 Major

Keep constructor validation strictly consistent with validate().

Line 91 still allows scheduleAt == now, while Line 83 rejects it. Centralize the check or use the same <= System.currentTimeMillis() comparison in both places so callers that construct the DTO directly cannot bypass the strict future requirement.

🐛 Proposed fix
     init {
-        if (scheduleAt?.before(Date()) == true) {
+        if (scheduleAt != null && scheduleAt.time <= System.currentTimeMillis()) {
             throw IllegalArgumentException("scheduleAt must be in the future")
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt`
around lines 90 - 93, The init block in PostMessageRequest currently checks
scheduleAt?.before(Date()) allowing scheduleAt == now while validate() uses a
stricter comparison; update the constructor/init to use the same strict future
check as validate() (e.g., compare scheduleAt.time <=
System.currentTimeMillis()) or centralize the validation into the validate()
method and call it from the init so both construction and validate() enforce
identical "must be in the future" logic; locate PostMessageRequest, its init
block and the validate() method to make the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt`:
- Around line 83-85: PostMessageRequest currently only checks scheduleAt is in
the future but not that it occurs before message expiry; update the validation
inside PostMessageRequest (check where scheduleAt is validated) to also reject
scheduleAt that is after the message's expiry by comparing scheduleAt against
the request's expiration parameters (use validUntil if present, otherwise
compute expiry from ttl) and throw an IllegalArgumentException (e.g.,
"scheduleAt must be before message expiry") when scheduleAt is >= expiry;
reference the scheduleAt, validUntil, ttl fields and the PostMessageRequest
validation block so the check covers both expiry semantics.

---

Duplicate comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt`:
- Around line 90-93: The init block in PostMessageRequest currently checks
scheduleAt?.before(Date()) allowing scheduleAt == now while validate() uses a
stricter comparison; update the constructor/init to use the same strict future
check as validate() (e.g., compare scheduleAt.time <=
System.currentTimeMillis()) or centralize the validation into the validate()
method and call it from the init so both construction and validate() enforce
identical "must be in the future" logic; locate PostMessageRequest, its init
block and the validate() method to make the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a8333077-cb20-4f6b-bc87-d634b8d3c8b9

📥 Commits

Reviewing files that changed from the base of the PR and between 0e8b02a and 705522a.

📒 Files selected for processing (15)
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
✅ Files skipped from review due to trivial changes (6)
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt

@capcom6 capcom6 added the ready label Apr 21, 2026
@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from 705522a to 9c91e3d Compare April 23, 2026 01:37
@github-actions github-actions Bot removed the ready label Apr 23, 2026
Copy link
Copy Markdown

@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 (2)
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt (2)

81-81: Nit: replace the magic 0 for "no scheduling" with a named constant or overload.

SendMessagesWorker.start(context, false, 0) and SendMessagesWorker.start(context, ..., 0) repeat a literal whose meaning ("run as soon as constraints allow") isn't obvious at the call site. Either an IMMEDIATE = 0L constant on the worker, a default parameter (scheduleAt: Long = 0L), or a small overload start(context, force) would make the intent explicit at all three call sites.

Also applies to: 106-110

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`
at line 81, Call sites use the magic literal 0 for immediate scheduling in
SendMessagesWorker.start(context, ..., 0); replace this with a named constant or
overload to make intent explicit — e.g., add IMMEDIATE_SCHEDULE = 0L (or
IMMEDIATE = 0L) as a public constant on SendMessagesWorker and update calls to
SendMessagesWorker.start(context, false, SendMessagesWorker.IMMEDIATE), or add a
default parameter scheduleAt: Long = IMMEDIATE and/or an overload start(context,
force: Boolean) that delegates to start(context, force, IMMEDIATE); update all
occurrences (including the other start calls) to use the new constant/overload.

207-250: Reschedule in finally will fire even on cancellation — consider scoping it.

Wrapping the loop in try { ... } finally { ... SendMessagesWorker.start(...) } means the reschedule runs on every exit path, including CancellationException (when WorkManager cancels the job). The finally block calls start(context, true, nextScheduledTime) with force=true, which uses ExistingWorkPolicy.REPLACE—immediately replacing a worker that was just cancelled. Meanwhile, doWork() catches the CancellationException and returns Result.retry(), creating a potential reschedule+retry loop.

While ExistingWorkPolicy.REPLACE with backoff mitigation prevents cascading, the logic is loose. Two safer alternatives:

  1. Only reschedule on normal completion / non-cancel exceptions:
    try {
        // ... loop
    } catch (e: CancellationException) {
        throw e
    } finally {
        if (coroutineContext[Job]?.isActive == true) {
            val nextScheduledTime = dao.nextScheduledTime() ?: 0
            if (nextScheduledTime > System.currentTimeMillis()) {
                SendMessagesWorker.start(context, true, nextScheduledTime)
            }
        }
    }
  2. Move the reschedule out of finally and into the worker's normal post-success path (in SendMessagesWorker.doWork() after sendPendingMessages() returns cleanly), so it only runs when the worker completes without cancellation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`
around lines 207 - 250, The finally block in sendPendingMessages currently
always calls SendMessagesWorker.start(...) (using dao.nextScheduledTime()) even
when the coroutine was cancelled; change this so rescheduling only happens on
normal completion: either (A) catch CancellationException and rethrow, then in
finally check coroutineContext[Job]?.isActive (or a local flag set on normal
exit) before calling SendMessagesWorker.start, or (B) remove the reschedule from
sendPendingMessages and instead invoke SendMessagesWorker.start(...) from the
caller (e.g., SendMessagesWorker.doWork()) after sendPendingMessages returns
successfully; reference sendPendingMessages, SendMessagesWorker.start,
dao.nextScheduledTime and doWork to locate where to apply the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt`:
- Around line 83-94: Remove the redundant init block that performs the same
scheduleAt check as validate(); specifically delete the init { if
(scheduleAt?.before(Date()) == true) { throw
IllegalArgumentException("scheduleAt must be in the future") } } so only
validate() enforces the rule (it uses scheduleAt?.after(Date()) == false which
is the intended stricter semantics); keep validate(), scheduleAt property and
callers such as MessagesRoutes.kt that call .validate() and ensure no other
implicit initialization checks remain.

---

Nitpick comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Line 81: Call sites use the magic literal 0 for immediate scheduling in
SendMessagesWorker.start(context, ..., 0); replace this with a named constant or
overload to make intent explicit — e.g., add IMMEDIATE_SCHEDULE = 0L (or
IMMEDIATE = 0L) as a public constant on SendMessagesWorker and update calls to
SendMessagesWorker.start(context, false, SendMessagesWorker.IMMEDIATE), or add a
default parameter scheduleAt: Long = IMMEDIATE and/or an overload start(context,
force: Boolean) that delegates to start(context, force, IMMEDIATE); update all
occurrences (including the other start calls) to use the new constant/overload.
- Around line 207-250: The finally block in sendPendingMessages currently always
calls SendMessagesWorker.start(...) (using dao.nextScheduledTime()) even when
the coroutine was cancelled; change this so rescheduling only happens on normal
completion: either (A) catch CancellationException and rethrow, then in finally
check coroutineContext[Job]?.isActive (or a local flag set on normal exit)
before calling SendMessagesWorker.start, or (B) remove the reschedule from
sendPendingMessages and instead invoke SendMessagesWorker.start(...) from the
caller (e.g., SendMessagesWorker.doWork()) after sendPendingMessages returns
successfully; reference sendPendingMessages, SendMessagesWorker.start,
dao.nextScheduledTime and doWork to locate where to apply the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67ea2955-0231-41f6-a4d6-f1d19ab6ce30

📥 Commits

Reviewing files that changed from the base of the PR and between 705522a and 9c91e3d.

📒 Files selected for processing (15)
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
✅ Files skipped from review due to trivial changes (5)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt

Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Around line 212-257: The finally block in sendPendingMessages() reschedules
work even after cancellation; remove the redundant catch(CancellationException)
and gate the reschedule so it only runs when the coroutine completed normally
(i.e., not cancelled). Concretely, import kotlin.coroutines.coroutineContext,
remove the catch block, and in the finally of sendPendingMessages() check
coroutineContext.isActive (or !coroutineContext.isActive to skip) before calling
SendMessagesWorker.start(...); this ensures SendMessagesWorker.start is not
invoked when stop()/cancellation occurred.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb1bfa13-5c80-4e18-9842-728a56ac1bb5

📥 Commits

Reviewing files that changed from the base of the PR and between 9c91e3d and 8b5a2ae.

📒 Files selected for processing (3)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt

@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from ebd0f17 to 3f7afde Compare April 23, 2026 03:53
Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt (1)

79-107: ⚠️ Potential issue | 🟠 Major

Separate immediate and scheduled WorkRequests, or always REPLACE for IMMEDIATE

Immediate and scheduled requests share the same unique name (SendMessagesWorker), so ExistingWorkPolicy.KEEP drops non-expedited IMMEDIATE tasks if a scheduled job is pending. Choose one:

  • Always REPLACE when initialDelay == 0L:
🛠 Minimal fix diff
 fun start(context: Context, force: Boolean, scheduleAt: Long) {
     val initialDelay = max(0, scheduleAt - System.currentTimeMillis())
     …
     WorkManager.getInstance(context)
         .enqueueUniqueWork(
             NAME,
-            when (force) {
-                true -> ExistingWorkPolicy.REPLACE
-                false -> ExistingWorkPolicy.KEEP
-            },
+            when {
+                force || initialDelay == 0L -> ExistingWorkPolicy.REPLACE
+                else -> ExistingWorkPolicy.KEEP
+            },
             work
         )
 }
  • Or introduce a separate SCHEDULED_NAME for scheduled runs to isolate the two queues.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt`
around lines 79 - 107, The current start function in SendMessagesWorker uses a
single unique NAME for both immediate (initialDelay == 0L) and scheduled runs
which can drop expedited IMMEDIATE runs when a scheduled job exists; update
start to distinguish immediate vs scheduled runs by either (A) when initialDelay
== 0L (IMMEDIATE) always use ExistingWorkPolicy.REPLACE for the
enqueueUniqueWork call so expedited work replaces existing scheduled work, or
(B) introduce a second constant SCHEDULED_NAME and use NAME for immediate runs
and SCHEDULED_NAME for delayed runs so scheduled and immediate queues are
isolated; implement the chosen approach inside start where initialDelay is
computed and before calling WorkManager.getInstance(context).enqueueUniqueWork.
🧹 Nitpick comments (1)
app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt (1)

81-96: Minor: scheduleAt parameter is ambiguous when callers pass IMMEDIATE (0L).

Using a sentinel 0L to mean "run now" works because of max(0, scheduleAt - now), but it's easy to misuse (e.g., accidentally passing a past timestamp yields the same effect as IMMEDIATE without the caller realizing). Consider a nullable scheduleAt: Long? where null means immediate, or two entry points (start(...) and startAt(..., scheduleAt)), to make the contract explicit at call sites in MessagesService.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt`
around lines 81 - 96, The start(...) method's scheduleAt: Long parameter is
ambiguous when callers pass 0L to mean "IMMEDIATE"; change the API to make
intent explicit by either (A) making scheduleAt nullable (scheduleAt: Long?)
where null means immediate and update SendMessagesWorker.start to compute
initialDelay when scheduleAt != null, or (B) add a separate entrypoint
startNow(context, force) and keep startAt(context, force, scheduleAt: Long) for
scheduled runs; update all callers in MessagesService to use the clearer
method/nullable contract and adjust the logic in OneTimeWorkRequestBuilder usage
and the initialDelay calculation accordingly so past timestamps and explicit
immediate requests are distinguishable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt`:
- Around line 59-60: nextScheduledTime() loses milliseconds because
strftime('%s', ...) yields whole seconds; update the `@Query` to compute epoch
milliseconds using a millisecond-preserving expression (e.g. convert
julianday(scheduleAt) to epoch ms via (julianday(scheduleAt) - 2440587.5) *
86400000) and remove COALESCE nullability handling if you keep COALESCE 0 then
change the Kotlin return type from Long? to Long to reflect non-null; locate the
SQL in the MessagesDao.nextScheduledTime() definition and adjust both the SQL
expression and the function signature accordingly.

---

Outside diff comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt`:
- Around line 79-107: The current start function in SendMessagesWorker uses a
single unique NAME for both immediate (initialDelay == 0L) and scheduled runs
which can drop expedited IMMEDIATE runs when a scheduled job exists; update
start to distinguish immediate vs scheduled runs by either (A) when initialDelay
== 0L (IMMEDIATE) always use ExistingWorkPolicy.REPLACE for the
enqueueUniqueWork call so expedited work replaces existing scheduled work, or
(B) introduce a second constant SCHEDULED_NAME and use NAME for immediate runs
and SCHEDULED_NAME for delayed runs so scheduled and immediate queues are
isolated; implement the chosen approach inside start where initialDelay is
computed and before calling WorkManager.getInstance(context).enqueueUniqueWork.

---

Nitpick comments:
In
`@app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt`:
- Around line 81-96: The start(...) method's scheduleAt: Long parameter is
ambiguous when callers pass 0L to mean "IMMEDIATE"; change the API to make
intent explicit by either (A) making scheduleAt nullable (scheduleAt: Long?)
where null means immediate and update SendMessagesWorker.start to compute
initialDelay when scheduleAt != null, or (B) add a separate entrypoint
startNow(context, force) and keep startAt(context, force, scheduleAt: Long) for
scheduled runs; update all callers in MessagesService to use the clearer
method/nullable contract and adjust the logic in OneTimeWorkRequestBuilder usage
and the initialDelay calculation accordingly so past timestamps and explicit
immediate requests are distinguishable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bcbb5ac5-3028-47d2-b158-b774e7bb33bd

📥 Commits

Reviewing files that changed from the base of the PR and between ebd0f17 and 3f7afde.

📒 Files selected for processing (15)
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
✅ Files skipped from review due to trivial changes (5)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/Message.kt
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
🚧 Files skipped from review as they are similar to previous changes (6)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt

Comment thread app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
Copy link
Copy Markdown

@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 (1)
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt (1)

97-100: ⚠️ Potential issue | 🟠 Major

Treat “no existing scheduled message” as earliest.

When dao.nextScheduledTime() returns 0, this currently sets earliest = false, so the first scheduled message is not treated as the earliest scheduled work item.

🐛 Proposed fix
         val earliest = request.params.scheduleAt?.let {
             val nextScheduled = dao.nextScheduledTime() ?: 0
-            nextScheduled != 0L && it.time < nextScheduled
+            nextScheduled == 0L || it.time < nextScheduled
         } ?: true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`
around lines 97 - 100, The logic computing earliest in MessagesService is
treating nextScheduledTime() == 0 as non-earliest; change it so "no existing
scheduled message" (nextScheduled == 0L) counts as earliest. Update the block
that sets earliest (using request.params.scheduleAt and dao.nextScheduledTime())
to return true when scheduleAt is null OR when nextScheduled == 0L, otherwise
compare it.time < nextScheduled; reference the variables earliest,
request.params.scheduleAt and dao.nextScheduledTime() to locate and fix the
condition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Around line 249-255: The final reschedule check currently only enqueues a
worker when dao.nextScheduledTime() > System.currentTimeMillis(), which drops
work that became due in the gap; change the logic to schedule whenever
dao.nextScheduledTime() != 0 and treat times <= System.currentTimeMillis() as
"due now" (i.e., call SendMessagesWorker.start(...) so it runs immediately when
nextScheduledTime <= now, otherwise schedule for the future), updating the call
site that uses coroutineContext, dao.nextScheduledTime() and
SendMessagesWorker.start(context, ...) accordingly.

---

Duplicate comments:
In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Around line 97-100: The logic computing earliest in MessagesService is
treating nextScheduledTime() == 0 as non-earliest; change it so "no existing
scheduled message" (nextScheduled == 0L) counts as earliest. Update the block
that sets earliest (using request.params.scheduleAt and dao.nextScheduledTime())
to return true when scheduleAt is null OR when nextScheduled == 0L, otherwise
compare it.time < nextScheduled; reference the variables earliest,
request.params.scheduleAt and dao.nextScheduledTime() to locate and fix the
condition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 210aa9b0-67c1-4aef-8a4b-b3e7e34d20f3

📥 Commits

Reviewing files that changed from the base of the PR and between 3f7afde and 1a49600.

📒 Files selected for processing (1)
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt

@capcom6 capcom6 force-pushed the codex/add-message-scheduling-feature branch from 1a49600 to 98b34bb Compare April 23, 2026 06:37
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (2)
app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt (1)

59-60: ⚠️ Potential issue | 🟡 Minor

nextScheduledTime() still loses millisecond precision and has a misleading nullable return type.

strftime('%s', MIN(scheduleAt)) * 1000 truncates the .SSS fragment from the stored ISO-8601 string, so a value like 2026-04-15T10:30:45.567Z is returned as 1744713045000 (not 1744713045567). MessagesService compares this directly to Date.time / System.currentTimeMillis() (full ms) both in enqueueMessage (for the earliest decision) and in the sendPendingMessages finally block (for the next-reschedule decision), so messages scheduled within the same second as an existing earliest can be misclassified and trigger unnecessary (or incorrect) worker re-scheduling.

Additionally, COALESCE(..., 0) guarantees a non-null result, so declaring the return type as Long? is misleading.

♻️ Suggested query preserving milliseconds
-    `@Query`("SELECT COALESCE(strftime('%s', MIN(`scheduleAt`)) * 1000, 0) FROM message WHERE state = 'Pending'")
-    fun nextScheduledTime(): Long?
+    `@Query`(
+        """
+        SELECT COALESCE(
+            CAST((julianday(MIN(`scheduleAt`)) - 2440587.5) * 86400000 AS INTEGER),
+            0
+        )
+        FROM message
+        WHERE state = 'Pending' AND `scheduleAt` IS NOT NULL
+        """
+    )
+    fun nextScheduledTime(): Long

Alternatively, consider storing scheduleAt as an INTEGER epoch-ms via the TypeConverter to avoid ISO-8601 arithmetic entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt` around lines
59 - 60, nextScheduledTime() loses millisecond precision and declares a nullable
return even though COALESCE forces a value; update the DAO query used by
nextScheduledTime() to compute epoch milliseconds without truncating the
fractional seconds (so MessagesService comparisons in enqueueMessage and
sendPendingMessages use full ms precision) and change the method signature to
return Long (non-nullable). You can do this by modifying the SQL used in
nextScheduledTime() to include the fractional seconds when converting
MIN(scheduleAt) to epoch-ms (or switch scheduleAt to stored INTEGER epoch-ms via
a TypeConverter) and ensure the function name nextScheduledTime() is updated to
return Long instead of Long?.
app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt (1)

249-256: ⚠️ Potential issue | 🟡 Minor

Scheduled messages that become due during the final reschedule can be stranded.

The guard nextScheduledTime > System.currentTimeMillis() drops any row whose scheduleAt became <= now between the loop's final getPending(...) returning null and this check. Such a message will not be re-enqueued here and will sit Pending until an external trigger (new enqueue, app restart, etc.) fires the worker again. Given the ms-precision truncation still present in dao.nextScheduledTime(), the race window is effectively at least ~1 second.

🛠️ Suggested change
         } finally {
             if (coroutineContext.isActive) {
-                // After processing all pending messages, check if there are any scheduled messages for the future
                 val nextScheduledTime = dao.nextScheduledTime() ?: 0
-                if (nextScheduledTime > System.currentTimeMillis()) {
-                    SendMessagesWorker.start(context, true, nextScheduledTime)
+                if (nextScheduledTime != 0L) {
+                    val now = System.currentTimeMillis()
+                    SendMessagesWorker.start(
+                        context,
+                        true,
+                        if (nextScheduledTime > now) nextScheduledTime else SendMessagesWorker.IMMEDIATE
+                    )
                 }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`
around lines 249 - 256, The final reschedule drops rows that became due between
getPending and the check because you only reschedule when nextScheduledTime >
System.currentTimeMillis(); change the logic in the finally block (referencing
coroutineContext.isActive, dao.nextScheduledTime(), and
SendMessagesWorker.start) so that when dao.nextScheduledTime() returns a
non-zero time you always re-arm the worker: if the time is <=
System.currentTimeMillis() start the worker to process immediately, otherwise
schedule it for the future timestamp; this ensures messages that became due
during the race are not stranded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt`:
- Around line 59-60: nextScheduledTime() loses millisecond precision and
declares a nullable return even though COALESCE forces a value; update the DAO
query used by nextScheduledTime() to compute epoch milliseconds without
truncating the fractional seconds (so MessagesService comparisons in
enqueueMessage and sendPendingMessages use full ms precision) and change the
method signature to return Long (non-nullable). You can do this by modifying the
SQL used in nextScheduledTime() to include the fractional seconds when
converting MIN(scheduleAt) to epoch-ms (or switch scheduleAt to stored INTEGER
epoch-ms via a TypeConverter) and ensure the function name nextScheduledTime()
is updated to return Long instead of Long?.

In `@app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt`:
- Around line 249-256: The final reschedule drops rows that became due between
getPending and the check because you only reschedule when nextScheduledTime >
System.currentTimeMillis(); change the logic in the finally block (referencing
coroutineContext.isActive, dao.nextScheduledTime(), and
SendMessagesWorker.start) so that when dao.nextScheduledTime() returns a
non-zero time you always re-arm the worker: if the time is <=
System.currentTimeMillis() start the worker to process immediately, otherwise
schedule it for the future timestamp; this ensures messages that became due
during the race are not stranded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7ad6d8be-d453-4e72-bfda-ad9bc25d5668

📥 Commits

Reviewing files that changed from the base of the PR and between 1a49600 and 98b34bb.

📒 Files selected for processing (15)
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
  • app/src/main/assets/api/swagger.json
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/src/main/java/me/capcom/smsgateway/data/dao/MessagesDao.kt
  • app/src/main/java/me/capcom/smsgateway/data/entities/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/Message.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/workers/SendMessagesWorker.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
✅ Files skipped from review due to trivial changes (4)
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/Message.kt
  • app/src/test/java/me/capcom/smsgateway/data/entities/MessageWithRecipientsTest.kt
  • app/src/main/java/me/capcom/smsgateway/data/AppDatabase.kt
  • app/schemas/me.capcom.smsgateway.data.AppDatabase/21.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt
  • app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesRepository.kt
  • app/src/main/java/me/capcom/smsgateway/modules/localserver/domain/messages/PostMessageRequest.kt

@capcom6 capcom6 merged commit 3f82b13 into master Apr 27, 2026
4 checks passed
@capcom6 capcom6 deleted the codex/add-message-scheduling-feature branch April 27, 2026 00:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Add Message Scheduling Functionality with Time-Appropriate Delivery

1 participant