Skip to content

Feature/send sms based on schedule#857

Open
AchoArnold wants to merge 13 commits intomainfrom
feature/send-sms-based-on-schedule
Open

Feature/send sms based on schedule#857
AchoArnold wants to merge 13 commits intomainfrom
feature/send-sms-based-on-schedule

Conversation

@AchoArnold
Copy link
Copy Markdown
Member

No description provided.

@what-the-diff
Copy link
Copy Markdown

what-the-diff Bot commented Apr 27, 2026

PR Summary

  • Introduction of Schedule Sending Mechanisms

    • New routes, listeners, entities, and services have been added to facilitate the scheduling of message sends. This includes methods for creating, updating, listing and deleting schedules. This addition will simplify and automate the process of sending messages at predefined times.
  • Enhancements in Code Documentation

    • Code documentation across several methods is updated for better understanding. This should increase code maintainability by making it easier for other developers to understand the functionality.
  • Repository Upgrades

    • The gorm_send_schedule_repository.go file was added to handle the persistence of scheduled messages and the send_schedule_repository.go interface was created to define all the methods related to managing send schedules. These updates improve the organization and clarity of our repositories.
  • Manager for Send Schedule Services

    • The SendScheduleService has had the necessary methods for managing send schedules implemented and integrated into the PhoneNotificationService. This includes access methods and validation procedures, providing a structured system for managing scheduled messaging.
  • Enhanced User Interface

    • Updates on index.vue provided a more user-friendly interface, allowing users to manage their send schedules with ease and efficiency. This includes new sections for navigating schedules, autocomplete fields for attaching schedules, improved formatting for readability and error notifications for better user feedback.
  • Improved Data Handling

    • Adjustments were made to "store/index.ts" for better management of send schedules. This includes new actions for interacting with the API and modifications to existing actions for improved handling of schedule IDs. These changes ensure that the data related to send schedules is handled in a more efficient and secure manner.
  • Logging Improvements

    • Log messages have been updated for better clarity and context within the PhoneNotificationService functions. This will facilitate debugging processes and monitoring of system behavior.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 27, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 145 complexity · 13 duplication

Metric Results
Complexity 145
Duplication 13

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR adds a MessageSendSchedule feature that lets users define weekly time windows during which a phone is allowed to send outgoing SMS, integrating with the existing rate-limiting notification scheduler.

  • P1 — cross-user schedule attachment: phone_handler_validator.go validates schedule_id only as a valid UUID. Nothing prevents a user from referencing another user's schedule. The DB stores the cross-user FK, and because Phone.Schedule carries constraint:OnDelete:SET NULL, the real owner deleting their schedule silently nulls out schedule_id on the attacker's phone — one user's action modifying another user's data.
  • P2 — active schedule with empty windows sends immediately: ResolveScheduledAt short-circuits to current.UTC() when len(Windows) == 0, so an active schedule with no windows has no blocking effect.

Confidence Score: 3/5

Not safe to merge as-is — the missing ownership check on schedule_id allows cross-user FK references that can be silently mutated by the schedule owner's delete action.

One P1 finding (cross-user schedule attachment enabling cross-user data mutation via FK cascade) caps the score at 4; the presence of a security-relevant gap lowers it further to 3.

api/pkg/validators/phone_handler_validator.go — needs an ownership check before accepting schedule_id on a phone update.

Security Review

  • Cross-user schedule reference (api/pkg/validators/phone_handler_validator.go): A user can set schedule_id to any valid UUID, including one owned by another user. The FK OnDelete:SET NULL means the real schedule owner can unintentionally clear another user's schedule_id by deleting their own schedule. No schedule data is disclosed, but cross-user data mutation is possible.

Important Files Changed

Filename Overview
api/pkg/validators/phone_handler_validator.go Adds UUID-format validation for schedule_id but lacks ownership verification — a user can attach another user's schedule to their phone, causing cross-user data mutation via FK cascade.
api/pkg/entities/send_schedule.go New entity and ResolveScheduledAt algorithm; logic is sound for typical schedules but an active schedule with empty windows silently falls back to "send immediately".
api/pkg/repositories/gorm_phone_notification_repository.go Schedule parameter wired into rate-limited notification scheduling; the two-step resolve (schedule window + rate limit) logic looks correct for both the transactional and non-transactional paths.
api/pkg/validators/send_schedule_handler_validator.go Validates windows per day, start/end ranges, and overlap; logic is correct, though empty-windows case is not blocked here.
api/pkg/services/phone_notification_service.go Schedule is loaded with the caller's userID before notification scheduling, providing correct ownership isolation at runtime; silently falls back to immediate send if schedule is not found.
web/store/index.ts Adds CRUD Vuex actions for send schedules; error handlers use unnecessary Promise.all wrapping, and getSendSchedules passes an ignored limit parameter to the backend.
api/pkg/di/container.go Wires new repository, service, validator, handler, and listener into the DI container; AutoMigrate for MessageSendSchedule is correctly ordered before Phone.
api/pkg/handlers/send_schedule_handler.go Standard CRUD handler with proper auth middleware, not-found handling, and validation flow; no issues found.
web/pages/settings/send-schedules/index.vue New dedicated page for managing send schedules with full CRUD UI; no significant issues found.

Sequence Diagram

sequenceDiagram
    participant Client
    participant SendScheduleHandler
    participant PhoneHandler
    participant PhoneNotificationService
    participant SendScheduleRepository
    participant PhoneNotificationRepository

    Client->>SendScheduleHandler: POST /v1/send-schedules
    SendScheduleHandler->>SendScheduleRepository: Store(schedule)

    Client->>PhoneHandler: PUT /v1/phones {schedule_id}
    PhoneHandler->>PhoneHandler: ValidateUUID(schedule_id) [no ownership check]
    PhoneHandler->>PhoneNotificationService: Upsert phone with ScheduleID

    Note over PhoneNotificationService,SendScheduleRepository: On message send
    PhoneNotificationService->>SendScheduleRepository: Load(userID, phone.ScheduleID)
    SendScheduleRepository-->>PhoneNotificationService: schedule (or ErrNotFound → nil)
    PhoneNotificationService->>PhoneNotificationRepository: Schedule(messagesPerMinute, schedule, notification)
    PhoneNotificationRepository->>PhoneNotificationRepository: resolveScheduledAt(schedule, now)
    Note right of PhoneNotificationRepository: Finds next window start, applies rate limit, re-resolves
    PhoneNotificationRepository-->>PhoneNotificationService: notification.ScheduledAt set
Loading

Reviews (1): Last reviewed commit: "Merge pull request #841 from giresse19/f..." | Re-trigger Greptile

Comment on lines 88 to +99
})

result := v.ValidateStruct()
if request.ScheduleID != nil && strings.TrimSpace(*request.ScheduleID) != "" {
if uuidErrors := validator.ValidateUUID(strings.TrimSpace(*request.ScheduleID), "schedule_id"); len(uuidErrors) > 0 {
for key, values := range uuidErrors {
for _, value := range values {
result.Add(key, value)
}
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Missing schedule ownership check

The validator confirms that schedule_id is a valid UUID, but never verifies that the schedule belongs to the authenticated user. A user can therefore store any schedule UUID (including one that belongs to a different user) on their phone.

At runtime the notification service does re-load the schedule using the caller's own userID (so it silently falls back to no schedule), but the DB still records the cross-user FK reference. Because Phone.Schedule carries constraint:OnDelete:SET NULL, whenever the real schedule owner deletes their schedule, GORM/the DB nulls out the schedule_id on the attacker's phone — meaning one user's action silently modifies another user's data.

An ownership check (e.g. service.Load(ctx, userID, scheduleID)) should be added to ValidateUpsert before the phone is persisted.

Comment on lines +31 to +33
func (schedule *MessageSendSchedule) ResolveScheduledAt(current time.Time) time.Time {
if schedule == nil || !schedule.IsActive || len(schedule.Windows) == 0 {
return current.UTC()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Active schedule with empty windows sends immediately

When IsActive is true but Windows is empty, ResolveScheduledAt returns current.UTC() (the early-exit path on line 32). This means messages are sent immediately instead of being held, which is the opposite of what a user who configured an active-but-window-less schedule might expect. Consider either disallowing an active schedule with zero windows (add a validator rule) or treating it the same as IsActive: false.

Comment thread web/store/index.ts
Comment on lines +1144 to +1150
resolve(response.data.data)
})
.catch(async (error: AxiosError) => {
await Promise.all([
context.dispatch('addNotification', {
message:
(error.response?.data as any)?.message ??
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Unnecessary Promise.all wrapping

All four new action error handlers wrap a single dispatch call in Promise.all([...]). Promise.all over a one-element array adds no value and makes the intent harder to read. A plain await context.dispatch(...) is clearer.

Comment thread web/store/index.ts
Comment on lines +1113 to +1117
axios
.get<ResponsesSendSchedulesResponse>(`/v1/send-schedules`, {
params: {
limit: 100,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Unused limit query param sent to unimplemented pagination

getSendSchedules sends { params: { limit: 100 } } but the backend GET /v1/send-schedules handler ignores all query params and returns every schedule for the user. The param is silently discarded, which may also mask the fact that if a user ever has more than 100 schedules the frontend won't show them all without backend pagination support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants