Skip to content

RIC-T39 Bug fixes#436

Merged
ucswift merged 3 commits into
masterfrom
develop
Jul 23, 2026
Merged

RIC-T39 Bug fixes#436
ucswift merged 3 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jul 23, 2026

Copy link
Copy Markdown
Member

Pull Request Description

This PR delivers several bug fixes and feature enhancements (RIC-T39) across notification settings, calendar RSVP messaging, and check-in timer functionality.

User-Level Modern Application Sounds

Introduces a per-user toggle (EnableModernApplicationSounds) that lets individuals opt into modern notification sounds independently of the department-wide setting. The effective setting is resolved by combining both department and user preferences—if either is enabled, modern sounds are used. The setting is exposed through the API config endpoint, applied to all push notification paths (messages, notifications, chats, calls), and added to the profile edit page with full localization across 9 languages.

Calendar RSVP Messages

Adds support for a new CalendarRsvp message type that allows users to respond to calendar event invitations directly from a message. When a user responds (attending/not attending), the system updates both the message recipient record and the corresponding calendar event attendee. This includes API endpoint support, web UI RSVP buttons in the message view, and proper authorization checks. System-generated messages now display "System" as the sender instead of the generating user.

Check-In Timer: Commander Check-In on Behalf of Personnel

Enables incident commanders with the ManageAccountability capability to perform personnel check-ins on behalf of dispatched users. The feature validates that the target user is actually dispatched on the call and restricts this to personnel-type timers only.

Message Inbox Fixes

Fixes issues where deleted and expired messages were still appearing in user inboxes and unread counts. The MessageService now filters out deleted and expired messages, and expired messages can no longer be viewed or responded to.

Summary by CodeRabbit

  • New Features
    • Added a user preference for modern application notification sounds, with department settings taking precedence.
    • Added calendar RSVP responses directly from messages, including event attendance updates.
    • Enabled authorized incident commanders to check in personnel on their behalf.
  • Bug Fixes
    • Inbox and unread counts now exclude deleted, expired, and already-read messages.
    • System-generated messages now display their sender correctly.
  • Documentation
    • Updated message, RSVP, configuration, and check-in service documentation.

@Resgrid-Bot

This comment has been minimized.

@request-info

request-info Bot commented Jul 23, 2026

Copy link
Copy Markdown

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • Tests/Resgrid.Tests/Services/PushServiceModernApplicationSoundTests.cs is excluded by !**/Tests/**

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4059731f-5f7d-40c1-a434-fafe14a13d66

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes add per-user modern sound preferences, active inbox filtering, calendar RSVP handling for API and web messages, and delegated personnel check-ins with accountability validation.

Changes

Modern application sounds

Layer / File(s) Summary
Sound setting contract and persistence
Core/Resgrid.Model/..., Providers/Resgrid.Providers.Migrations/...
Adds the serialized user preference, shared department-or-user resolver, and SQL Server/PostgreSQL migrations.
Sound resolution and configuration
Core/Resgrid.Services/PushService.cs, Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs, Web/Resgrid.Web.Services/Models/v4/Configs/GetConfigResult.cs
Push sounds and v4 configuration incorporate the user profile preference.
Profile setting management
Web/Resgrid.Web/Areas/User/..., Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
Adds the profile checkbox, saves the setting, and documents the configuration field.

Active inbox filtering

Layer / File(s) Summary
Active inbox and unread count calculation
Core/Resgrid.Services/MessageService.cs, Repositories/Resgrid.Repositories.DataRepository/...
Inbox and unread retrieval exclude deleted, expired, and already-read messages, including database unread-count queries.

Calendar RSVP messages

Layer / File(s) Summary
RSVP service transaction
Core/Resgrid.Model/Services/ICalendarService.cs, Core/Resgrid.Services/CalendarService.cs
Adds transactional event signup and message-recipient updates.
RSVP API contracts and response flow
Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs, Web/Resgrid.Web.Services/Models/v4/Messages/GetMessagesResult.cs, Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
The API validates RSVP responses, updates event attendance, and exposes calendar item and system-message metadata.
Web RSVP interaction
Web/Resgrid.Web/Areas/User/...
The web flow loads RSVP eligibility, renders yes/no controls, submits attendance, and updates the recipient response.

Delegated personnel check-ins

Layer / File(s) Summary
On-behalf-of check-in validation
Web/Resgrid.Web.Services/Models/v4/CheckInTimers/..., Web/Resgrid.Web.Services/Controllers/v4/CheckInTimersController.cs, Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
Personnel check-ins can target another user after accountability, dispatch, and active-timer validation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MessagesController
  participant ICalendarService
  participant MessageRecipient
  User->>MessagesController: submit CalendarRsvp response
  MessagesController->>ICalendarService: validate item and signup
  ICalendarService->>MessageRecipient: save response and read timestamp
  MessagesController-->>User: redirect or API response
Loading

Possibly related PRs

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related, but it's too generic to convey the main changes in this PR. Use a concise, specific title that names the primary fix area, such as modern sounds, calendar RSVPs, and inbox message filtering.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs (2)

41-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Constructor injection keeps growing in both MessagesController implementations. Per coding guidelines, dependencies should be resolved via Bootstrapper.GetKernel().Resolve<T>() in the constructor body rather than accumulating constructor parameters, and the number of injected dependencies should be kept small; each new feature (this PR adds ICalendarService to both) pushes both controllers further from that.

  • Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs#L41-L67: this constructor now takes 10 dependencies; consider resolving less-frequently-used ones (e.g., ICalendarService, IUnitsService) via the service locator pattern instead of adding more constructor parameters.
  • Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs#L34-L49: same pattern, now at 9 dependencies with the addition of ICalendarService.

As per coding guidelines, "Use Service Locator pattern via Bootstrapper.GetKernel().Resolve() to resolve dependencies explicitly in constructors, rather than constructor injection" and "Minimize constructor injection; keep the number of injected dependencies small."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs` around lines
41 - 67, Reduce constructor injection in both MessagesController implementations
by removing less-frequently-used dependencies, specifically ICalendarService and
IUnitsService where applicable, from their constructor parameters and resolving
them in the constructor body through Bootstrapper.GetKernel().Resolve<T>().
Apply the corresponding change in
Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs lines 41-67 and
Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs lines 34-49, while
preserving the existing field assignments and behavior.

Source: Coding guidelines


450-476: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Calendar RSVP eligibility-check-and-signup sequence is duplicated across the API and Web controllers. Both implement the same steps independently: parse calendarItemId from the recipient's note via TextResponsePromptMetadata.TryGetCalendarItemId, load the calendar item, verify department match, verify SignupType == RSVP, call CanUserCheckInToCalendarEventAsync, then call _calendarService.SignupForEvent. Any future fix or business-rule change to this sequence (e.g., an additional eligibility check) has to be made in three places and can silently drift out of sync.

  • Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs#L450-L476: extract this block into a shared method (e.g., on ICalendarService or a small static helper) that returns a typed result (success/calendar-item-not-found/unauthorized/invalid-response) for both callers to consume.
  • Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs#L257-L273: reuse the shared validation for computing CanRespondToCalendarRsvp/attendee lookup instead of re-implementing department/signup-type checks inline.
  • Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs#L280-L316: reuse the same shared method for the POST submit path instead of duplicating the validation+SignupForEvent call.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs` around lines
450 - 476, Calendar RSVP eligibility and signup logic is duplicated across three
controller sites. Extract the shared parsing, calendar lookup, department and
RSVP authorization checks, and signup operation into a reusable typed-result
method, then update
Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs lines 450-476,
Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs lines 257-273, and
Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs lines 280-316 to
consume it for validation, attendee lookup, CanRespondToCalendarRsvp, and
submission handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Core/Resgrid.Services/MessageService.cs`:
- Around line 85-89: Update IsActiveInboxMessage to be user-scoped and exclude
messages whose recipient record for the current user has
MessageRecipient.IsDeleted set, while preserving the existing null,
message-level deletion, and expiration checks. Alternatively, enforce the
equivalent recipient-level filter in the inbox repository before calculating
inbox results and unread counts.
- Around line 81-83: Update the unread-count logic around
GetUnreadInboxMessagesByUserIdAsync so it does not materialize all unread
Message entities or duplicate the inbox fetch. Compute the count in the database
using the same active predicates, or reuse the result already fetched by
GetInboxMessagesByUserIdAsync while preserving the existing count behavior.

In `@Web/Resgrid.Web.Services/Controllers/v4/CheckInTimersController.cs`:
- Around line 30-45: Update CheckInTimersController to remove
IIncidentCommandService constructor injection and resolve it inside the
constructor using Bootstrapper.GetKernel().Resolve<IIncidentCommandService>(),
while preserving the existing _incidentCommandService assignment and other
injected dependencies.

---

Nitpick comments:
In `@Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs`:
- Around line 41-67: Reduce constructor injection in both MessagesController
implementations by removing less-frequently-used dependencies, specifically
ICalendarService and IUnitsService where applicable, from their constructor
parameters and resolving them in the constructor body through
Bootstrapper.GetKernel().Resolve<T>(). Apply the corresponding change in
Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs lines 41-67 and
Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs lines 34-49, while
preserving the existing field assignments and behavior.
- Around line 450-476: Calendar RSVP eligibility and signup logic is duplicated
across three controller sites. Extract the shared parsing, calendar lookup,
department and RSVP authorization checks, and signup operation into a reusable
typed-result method, then update
Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs lines 450-476,
Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs lines 257-273, and
Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs lines 280-316 to
consume it for validation, attendee lookup, CanRespondToCalendarRsvp, and
submission handling.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 13e07686-f466-41cd-97eb-dbd144992421

📥 Commits

Reviewing files that changed from the base of the PR and between 8d2e6c2 and 0da913b.

⛔ Files ignored due to path filters (21)
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.uk.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.uk.resx is excluded by !**/*.resx
  • Tests/Resgrid.Tests/Services/MessageServiceInboxTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/PushServiceModernApplicationSoundTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/Services/CheckInTimersControllerTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (18)
  • Core/Resgrid.Model/ModernApplicationSoundSettings.cs
  • Core/Resgrid.Model/UserProfile.cs
  • Core/Resgrid.Services/MessageService.cs
  • Core/Resgrid.Services/PushService.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0100_AddModernApplicationSoundsUserSetting.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0100_AddModernApplicationSoundsUserSettingPg.cs
  • Web/Resgrid.Web.Services/Controllers/v4/CheckInTimersController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs
  • Web/Resgrid.Web.Services/Models/v4/CheckInTimers/CheckInTimerModels.cs
  • Web/Resgrid.Web.Services/Models/v4/Configs/GetConfigResult.cs
  • Web/Resgrid.Web.Services/Models/v4/Messages/GetMessagesResult.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
  • Web/Resgrid.Web/Areas/User/Controllers/HomeController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs
  • Web/Resgrid.Web/Areas/User/Models/Messages/ViewMessageView.cs
  • Web/Resgrid.Web/Areas/User/Views/Home/EditUserProfile.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Messages/ViewMessage.cshtml

Comment thread Core/Resgrid.Services/MessageService.cs Outdated
Comment on lines +85 to +89
private static bool IsActiveInboxMessage(Message message)
{
return message != null
&& !message.IsDeleted
&& (!message.ExpireOn.HasValue || message.ExpireOn.Value > DateTime.UtcNow);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Include recipient-level deletion in the active predicate.

IsActiveInboxMessage only checks Message.IsDeleted, but this service also supports per-user deletion through MessageRecipient.IsDeleted. Because the inbox repository contract is unfiltered, a message deleted only for the current user can still appear in the inbox and unread count. Make this predicate user-scoped and exclude the user’s deleted recipient record, or enforce the equivalent filter in the repository.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Services/MessageService.cs` around lines 85 - 89, Update
IsActiveInboxMessage to be user-scoped and exclude messages whose recipient
record for the current user has MessageRecipient.IsDeleted set, while preserving
the existing null, message-level deletion, and expiration checks. Alternatively,
enforce the equivalent recipient-level filter in the inbox repository before
calculating inbox results and unread counts.

Comment on lines +30 to +45
private readonly IIncidentCommandService _incidentCommandService;

public CheckInTimersController(
ICheckInTimerService checkInTimerService,
ICallsService callsService,
IDepartmentSettingsService departmentSettingsService,
IDepartmentsService departmentsService,
IUserProfileService userProfileService)
IUserProfileService userProfileService,
IIncidentCommandService incidentCommandService)
{
_checkInTimerService = checkInTimerService;
_callsService = callsService;
_departmentSettingsService = departmentSettingsService;
_departmentsService = departmentsService;
_userProfileService = userProfileService;
_incidentCommandService = incidentCommandService;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Resolve IIncidentCommandService through the required Service Locator pattern.

Line 38 adds constructor injection, which conflicts with the repository’s required dependency-resolution approach.

Proposed fix
 		public CheckInTimersController(
 			ICheckInTimerService checkInTimerService,
 			ICallsService callsService,
 			IDepartmentSettingsService departmentSettingsService,
 			IDepartmentsService departmentsService,
-			IUserProfileService userProfileService,
-			IIncidentCommandService incidentCommandService)
+			IUserProfileService userProfileService)
 		{
 			_checkInTimerService = checkInTimerService;
 			_callsService = callsService;
 			_departmentSettingsService = departmentSettingsService;
 			_departmentsService = departmentsService;
 			_userProfileService = userProfileService;
-			_incidentCommandService = incidentCommandService;
+			_incidentCommandService = Bootstrapper.GetKernel().Resolve<IIncidentCommandService>();
 		}

As per coding guidelines, use Service Locator pattern via Bootstrapper.GetKernel().Resolve<T>() to resolve dependencies explicitly in constructors, rather than constructor injection.

📝 Committable suggestion

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

Suggested change
private readonly IIncidentCommandService _incidentCommandService;
public CheckInTimersController(
ICheckInTimerService checkInTimerService,
ICallsService callsService,
IDepartmentSettingsService departmentSettingsService,
IDepartmentsService departmentsService,
IUserProfileService userProfileService)
IUserProfileService userProfileService,
IIncidentCommandService incidentCommandService)
{
_checkInTimerService = checkInTimerService;
_callsService = callsService;
_departmentSettingsService = departmentSettingsService;
_departmentsService = departmentsService;
_userProfileService = userProfileService;
_incidentCommandService = incidentCommandService;
private readonly IIncidentCommandService _incidentCommandService;
public CheckInTimersController(
ICheckInTimerService checkInTimerService,
ICallsService callsService,
IDepartmentSettingsService departmentSettingsService,
IDepartmentsService departmentsService,
IUserProfileService userProfileService)
{
_checkInTimerService = checkInTimerService;
_callsService = callsService;
_departmentSettingsService = departmentSettingsService;
_departmentsService = departmentsService;
_userProfileService = userProfileService;
_incidentCommandService = Bootstrapper.GetKernel().Resolve<IIncidentCommandService>();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Web/Resgrid.Web.Services/Controllers/v4/CheckInTimersController.cs` around
lines 30 - 45, Update CheckInTimersController to remove IIncidentCommandService
constructor injection and resolve it inside the constructor using
Bootstrapper.GetKernel().Resolve<IIncidentCommandService>(), while preserving
the existing _incidentCommandService assignment and other injected dependencies.

Source: Coding guidelines

Comment on lines 79 to +83
public async Task<int> GetUnreadMessagesCountByUserIdAsync(string userId)
{
return await _messageRepository.GetUnreadMessageCountAsync(userId);
var messages = await GetUnreadInboxMessagesByUserIdAsync(userId);
return messages.Count;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Performance high

Unbounded memory allocation: GetUnreadMessagesCountByUserIdAsync replaces a DB-side COUNT query with GetUnreadInboxMessagesByUserIdAsync, loading ALL inbox Message entities (bodies, recipients, etc.) into memory on every Inbox and ViewMessage page load just to return .Count. Add ExpireOn and IsDeleted predicates to the existing server-side COUNT query (SelectUnreadMessageCountQuery) instead of materializing full entities.

public async Task<int> GetUnreadMessagesCountByUserIdAsync(string userId)
{
	return await _messageRepository.GetUnreadMessageCountAsync(userId);
	// (update SelectUnreadMessageCountQuery SQL to also exclude IsDeleted and expired messages)
Prompt for LLM

File Core/Resgrid.Services/MessageService.cs:

Line 79 to 83:

Unbounded memory allocation: `GetUnreadMessagesCountByUserIdAsync` replaces a DB-side COUNT query with `GetUnreadInboxMessagesByUserIdAsync`, loading ALL inbox `Message` entities (bodies, recipients, etc.) into memory on every Inbox and ViewMessage page load just to return `.Count`. Add `ExpireOn` and `IsDeleted` predicates to the existing server-side COUNT query (`SelectUnreadMessageCountQuery`) instead of materializing full entities.

Suggested Code:

public async Task<int> GetUnreadMessagesCountByUserIdAsync(string userId)
{
	return await _messageRepository.GetUnreadMessageCountAsync(userId);
	// (update SelectUnreadMessageCountQuery SQL to also exclude IsDeleted and expired messages)

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

}
catch (Exception ex)
{
Framework.Logging.LogException(ex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Missing structured logging context: the catch block in GetModernApplicationSoundsEnabledAsync calls Framework.Logging.LogException(ex) with only a bare exception. Include the operation name and relevant identifiers (e.g., departmentId) as structured fields in the log call.

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Core/Resgrid.Services/PushService.cs:

Line 269:

Missing structured logging context: the catch block in `GetModernApplicationSoundsEnabledAsync` calls `Framework.Logging.LogException(ex)` with only a bare exception. Include the operation name and relevant identifiers (e.g., `departmentId`) as structured fields in the log call.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


public override void Down()
{
Delete.Column("EnableModernApplicationSounds".ToLower()).FromTable("UserProfiles".ToLower());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Duplicated string literals: "EnableModernApplicationSounds" and "UserProfiles" are repeated as raw literals across migration methods, requiring synchronized edits on any rename. Define const fields for the table and column names at the class level and reuse them in every method.

Kody rule violation: Centralize string constants

Prompt for LLM

File Providers/Resgrid.Providers.MigrationsPg/Migrations/M0100_AddModernApplicationSoundsUserSettingPg.cs:

Line 16:

Duplicated string literals: `"EnableModernApplicationSounds"` and `"UserProfiles"` are repeated as raw literals across migration methods, requiring synchronized edits on any rename. Define `const` fields for the table and column names at the class level and reuse them in every method.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

UserId = TargetUserId
}, CancellationToken.None);

response.Result.Should().BeOfType<OkObjectResult>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules critical

Blocking async operation: .Result or .Wait() causes deadlocks and prevents efficient asynchronous execution. Use await instead for proper async behavior.

Kody rule violation: Avoid Blocking Calls to Async Methods

Prompt for LLM

File Tests/Resgrid.Tests/Web/Services/CheckInTimersControllerTests.cs:

Line 102:

Blocking async operation: `.Result` or `.Wait()` causes deadlocks and prevents efficient asynchronous execution. Use `await` instead for proper async behavior.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

UserId = TargetUserId
}, CancellationToken.None);

response.Result.Should().BeOfType<OkObjectResult>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Blocking async operation: .Result or .Wait() on async methods can cause deadlocks. Use await end-to-end and configure awaits appropriately.

Kody rule violation: Await async operations properly

Prompt for LLM

File Tests/Resgrid.Tests/Web/Services/CheckInTimersControllerTests.cs:

Line 102:

Blocking async operation: `.Result` or `.Wait()` on async methods can cause deadlocks. Use `await` end-to-end and configure awaits appropriately.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


response.Result.Should().BeOfType<OkObjectResult>();
savedRecord.Should().NotBeNull();
savedRecord.UserId.Should().Be(TargetUserId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Potential NullReferenceException: savedRecord is accessed without a null check, which throws if the previous assertion fails or is bypassed. Use a null check or pattern matching (e.g., if (savedRecord != null)).

Kody rule violation: Add null checks to prevent NullReferenceException

Prompt for LLM

File Tests/Resgrid.Tests/Web/Services/CheckInTimersControllerTests.cs:

Line 104:

Potential `NullReferenceException`: `savedRecord` is accessed without a null check, which throws if the previous assertion fails or is bypassed. Use a null check or pattern matching (e.g., `if (savedRecord != null)`).

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

.Callback<CheckInRecord, CancellationToken>((record, _) => savedRecord = record)
.ReturnsAsync(new CheckInRecord { CheckInRecordId = "record-1" });

var response = await _controller.PerformCheckIn(new PerformCheckInInput

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Duplicated code block initializing and passing a PerformCheckInInput object reduces maintainability and risks inconsistent updates. Extract the creation of PerformCheckInInput and the controller call into a helper method (e.g., ExecutePerformCheckIn(CheckInTimerTargetType type)).

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File Tests/Resgrid.Tests/Web/Services/CheckInTimersControllerTests.cs:

Line 95:

Duplicated code block initializing and passing a `PerformCheckInInput` object reduces maintainability and risks inconsistent updates. Extract the creation of `PerformCheckInInput` and the controller call into a helper method (e.g., `ExecutePerformCheckIn(CheckInTimerTargetType type)`).

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

result.Data.AnalyticsHost = "";

bool departmentModernApplicationSoundsEnabled = departmentId > 0
&& await _departmentSettingsService.GetModernNotificationsEnabledAsync(departmentId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unhandled exception: the awaited call to GetModernNotificationsEnabledAsync is not wrapped in try/catch, so a failure from the department settings service propagates and produces an unhelpful 500 error. Wrap the await in a try/catch, log the error with context (departmentId, operation name), and provide a safe default (e.g., false).

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs:

Line 118:

Unhandled exception: the awaited call to `GetModernNotificationsEnabledAsync` is not wrapped in try/catch, so a failure from the department settings service propagates and produces an unhelpful 500 error. Wrap the await in a try/catch, log the error with context (`departmentId`, operation name), and provide a safe default (e.g., `false`).

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

return NotFound();

if (calendarItem.DepartmentId != DepartmentId)
return Unauthorized();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Incorrect HTTP status code: Unauthorized() (401) is returned when a calendar item belongs to a different department, but the user is authenticated and only lacks authorization. Return Forbid() (403) or NotFound() to correctly indicate authorization failure and avoid information leakage.

Kody rule violation: Use appropriate HTTP status codes

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs:

Line 461:

Incorrect HTTP status code: `Unauthorized()` (401) is returned when a calendar item belongs to a different department, but the user is authenticated and only lacks authorization. Return `Forbid()` (403) or `NotFound()` to correctly indicate authorization failure and avoid information leakage.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

response.Response = responseInput.Type.ToString();
if (message.Type == (int)MessageTypes.CalendarRsvp)
{
if ((responseInput.Type != 1 && responseInput.Type != 3)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Magic numbers 1 and 3 used to check response types obscure intent and are error-prone during maintenance. Define named constants (e.g., ResponseTypeAttending = 1, ResponseTypeNotAttending = 3) or compare against enum values.

Kody rule violation: Replace magic numbers with named constants

Prompt for LLM

File Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs:

Line 452:

Magic numbers `1` and `3` used to check response types obscure intent and are error-prone during maintenance. Define named constants (e.g., `ResponseTypeAttending = 1`, `ResponseTypeNotAttending = 3`) or compare against enum values.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

public int CheckInType { get; set; }

/// <summary>Optional personnel user id when an incident commander checks in on that person's behalf.</summary>
public string UserId { get; set; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Uninitialized property: the UserId string auto-property defaults to null, risking downstream NullReferenceExceptions. Initialize it to string.Empty to provide a safe default.

Kody rule violation: Initialize properties with default values

Prompt for LLM

File Web/Resgrid.Web.Services/Models/v4/CheckInTimers/CheckInTimerModels.cs:

Line 145:

Uninitialized property: the `UserId` string auto-property defaults to `null`, risking downstream `NullReferenceException`s. Initialize it to `string.Empty` to provide a safe default.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +309 to +310
await _calendarService.SignupForEvent(calendarItemId, UserId, normalizedResponse,
attendeeType, cancellationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unhandled exception: the SignupForEvent service call — an external/database write — is not wrapped in try/catch, so connection failures or constraint violations produce a generic 500 with no diagnostic context. Wrap the write in try/catch, log structured error details (messageId, calendarItemId, UserId), and return a user-friendly error result.

Kody rule violation: Add try-catch blocks for external calls

Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs:

Line 309 to 310:

Unhandled exception: the `SignupForEvent` service call — an external/database write — is not wrapped in try/catch, so connection failures or constraint violations produce a generic 500 with no diagnostic context. Wrap the write in try/catch, log structured error details (`messageId`, `calendarItemId`, `UserId`), and return a user-friendly error result.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +309 to +313
await _calendarService.SignupForEvent(calendarItemId, UserId, normalizedResponse,
attendeeType, cancellationToken);
recipient.Response = normalizedResponse;
recipient.ReadOn = DateTime.UtcNow;
await _messageService.SaveMessageRecipientAsync(recipient, cancellationToken);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Non-atomic writes: SignupForEvent and SaveMessageRecipientAsync modify persisted state without a wrapping transaction, leaving the system inconsistent if the first write succeeds and the second fails. Wrap both writes in a database transaction (BeginTransaction/Commit), issue a single SaveChanges, and roll back on failure.

Kody rule violation: Handle transaction rollbacks properly

Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs:

Line 309 to 313:

Non-atomic writes: `SignupForEvent` and `SaveMessageRecipientAsync` modify persisted state without a wrapping transaction, leaving the system inconsistent if the first write succeeds and the second fails. Wrap both writes in a database transaction (`BeginTransaction`/`Commit`), issue a single `SaveChanges`, and roll back on failure.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

|| !await _authorizationService.CanUserCheckInToCalendarEventAsync(UserId, calendarItemId))
return Unauthorized();

var normalizedResponse = attending ? "Yes" : "No";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Hardcoded string literals "Yes" and "No" represent a finite RSVP set but introduce typos, are hard to search for, and duplicate domain meaning. Define an enum (e.g., CalendarRsvpResponse { Yes, No }) or reference an existing response-value constant.

Kody rule violation: Use enums instead of magic strings

Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs:

Line 304:

Hardcoded string literals `"Yes"` and `"No"` represent a finite RSVP set but introduce typos, are hard to search for, and duplicate domain meaning. Define an enum (e.g., `CalendarRsvpResponse { Yes, No }`) or reference an existing response-value constant.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@Resgrid-Bot

This comment has been minimized.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
Core/Resgrid.Services/CalendarService.cs (1)

30-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the mandated dependency-resolution pattern.

These dependencies are added through constructor injection, but the repository guideline requires resolving dependencies with Bootstrapper.GetKernel().Resolve<T>() instead. Please follow that convention or confirm this service is explicitly exempt.

As per coding guidelines, **/*.cs: Use Service Locator pattern via Bootstrapper.GetKernel().Resolve<T>() to resolve dependencies explicitly in constructors, rather than constructor injection.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Resgrid.Services/CalendarService.cs` around lines 30 - 37, Update the
CalendarService constructor dependency setup to follow the mandated
service-locator pattern: remove the newly injected dependencies from the
constructor parameters and resolve them with
Bootstrapper.GetKernel().Resolve<T>() inside the constructor. Apply this
consistently to the affected repository and unit-of-work dependencies, or
document the service’s explicit exemption if constructor injection must remain.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Core/Resgrid.Services/CalendarService.cs`:
- Around line 30-37: Update the CalendarService constructor dependency setup to
follow the mandated service-locator pattern: remove the newly injected
dependencies from the constructor parameters and resolve them with
Bootstrapper.GetKernel().Resolve<T>() inside the constructor. Apply this
consistently to the affected repository and unit-of-work dependencies, or
document the service’s explicit exemption if constructor injection must remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f490994a-1999-4736-a71c-2f7b1076b282

📥 Commits

Reviewing files that changed from the base of the PR and between 0da913b and 35cd43d.

⛔ Files ignored due to path filters (4)
  • Tests/Resgrid.Tests/Services/CalendarServiceCheckInTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/CalendarServiceRsvpTransactionTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/CalendarServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/MessageServiceInboxTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (10)
  • Core/Resgrid.Model/Services/ICalendarService.cs
  • Core/Resgrid.Services/CalendarService.cs
  • Core/Resgrid.Services/MessageService.cs
  • Repositories/Resgrid.Repositories.DataRepository/MessageRepository.cs
  • Repositories/Resgrid.Repositories.DataRepository/Queries/Messages/SelectUnreadMessageCountQuery.cs
  • Repositories/Resgrid.Repositories.DataRepository/Servers/PostgreSql/PostgreSqlConfiguration.cs
  • Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs
  • Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs
🚧 Files skipped from review as they are similar to previous changes (3)
  • Web/Resgrid.Web.Services/Controllers/v4/ConfigController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs
  • Web/Resgrid.Web.Services/Controllers/v4/MessagesController.cs

Comment on lines +508 to +512
catch
{
_unitOfWork.DiscardChanges();
throw;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

The catch block rethrows without logging context, violating Rule 30 which requires operation name and identifiers before propagation for diagnosability. Change to catch (Exception ex) and log structured fields such as operation name SignupForEventAndUpdateMessageRecipientAsync, calendarEventItemId, and userId before throw;.

Kody rule violation: Avoid empty catch blocks

Prompt for LLM

File Core/Resgrid.Services/CalendarService.cs:

Line 508 to 512:

The `catch` block rethrows without logging context, violating Rule 30 which requires operation name and identifiers before propagation for diagnosability. Change to `catch (Exception ex)` and log structured fields such as operation name `SignupForEventAndUpdateMessageRecipientAsync`, `calendarEventItemId`, and `userId` before `throw;`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@@ -75,6 +81,13 @@ public async Task<int> GetUnreadMessagesCountByUserIdAsync(string userId)
return await _messageRepository.GetUnreadMessageCountAsync(userId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unguarded awaited repository call violates Rule 1, which requires every awaited async operation to be wrapped in try/catch with explicit rejection handling. Wrap the call in try/catch, log the exception (e.g., logger.LogError(ex, "GetUnreadMessageCountAsync failed")) before throw;, or document caller responsibility for rejection handling.

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File Core/Resgrid.Services/MessageService.cs:

Line 81:

Unguarded awaited repository call violates Rule 1, which requires every awaited async operation to be wrapped in try/catch with explicit rejection handling. Wrap the call in try/catch, log the exception (e.g., `logger.LogError(ex, "GetUnreadMessageCountAsync failed")`) before `throw;`, or document caller responsibility for rejection handling.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@@ -75,6 +81,13 @@ public async Task<int> GetUnreadMessagesCountByUserIdAsync(string userId)
return await _messageRepository.GetUnreadMessageCountAsync(userId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unguarded external repository call violates Rule 28, which requires wrapping external calls in try/catch with structured context before mapping to application-level errors. Wrap the call in try/catch, log userId and operation context, and transduce the exception to an application-level error before propagation.

Kody rule violation: Add try-catch blocks for external calls

Prompt for LLM

File Core/Resgrid.Services/MessageService.cs:

Line 81:

Unguarded external repository call violates Rule 28, which requires wrapping external calls in try/catch with structured context before mapping to application-level errors. Wrap the call in try/catch, log `userId` and operation context, and transduce the exception to an application-level error before propagation.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +317 to +318
Logging.LogException(ex,
$"CalendarRsvp response transaction failed. messageId={messageId}, calendarItemId={calendarItemId}, UserId={UserId}.");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

String interpolation in the error log bakes identifiers into the message template, defeating structured logging required by SIEM tools for filtering and aggregation. Pass a message template with named placeholders ({MessageId}, {CalendarItemId}, {UserId}) and supply values as separate arguments to Logging.LogException or ILogger.LogError.

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Web/Resgrid.Web/Areas/User/Controllers/MessagesController.cs:

Line 317 to 318:

String interpolation in the error log bakes identifiers into the message template, defeating structured logging required by SIEM tools for filtering and aggregation. Pass a message template with named placeholders (`{MessageId}`, `{CalendarItemId}`, `{UserId}`) and supply values as separate arguments to `Logging.LogException` or `ILogger.LogError`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@ucswift

ucswift commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is approved.

@Resgrid-Bot

Resgrid-Bot commented Jul 23, 2026

Copy link
Copy Markdown

Kody Review Complete

Great news! 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@ucswift
ucswift merged commit 6070e65 into master Jul 23, 2026
17 of 18 checks passed
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