Skip to content

RE1-T36 Fixing Postgres and Paddle issues#346

Merged
ucswift merged 2 commits intomasterfrom
develop
Apr 25, 2026
Merged

RE1-T36 Fixing Postgres and Paddle issues#346
ucswift merged 2 commits intomasterfrom
develop

Conversation

@ucswift
Copy link
Copy Markdown
Member

@ucswift ucswift commented Apr 25, 2026

Summary by CodeRabbit

  • New Features

    • System now supports Postgres as a document database backend for location tracking, providing flexible configuration options for personnel and unit locations in addition to existing MongoDB support.
  • Bug Fixes

    • Fixed Paddle subscription payment redirect URL to properly complete payment processing workflow.
  • Tests

    • Added comprehensive tests for database provider selection, ensuring correct backend routing and event handling.

@request-info
Copy link
Copy Markdown

request-info Bot commented Apr 25, 2026

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Warning

Rate limit exceeded

@ucswift has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 36 minutes and 14 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 36 minutes and 14 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5e30a999-34b2-441c-9484-0d47c8109162

📥 Commits

Reviewing files that changed from the base of the PR and between 2bf0049 and 7c6b1a5.

📒 Files selected for processing (2)
  • Repositories/Resgrid.Repositories.NoSqlRepository/PersonnelLocationsDocRepository.cs
  • Repositories/Resgrid.Repositories.NoSqlRepository/UnitLocationsDocRepository.cs
📝 Walkthrough

Walkthrough

This PR adds Postgres-based document storage support for personnel and unit locations alongside existing Mongo repositories, introducing conditional routing in services based on DocDatabaseType configuration. Repository interfaces now include UpdateAsync methods, services implement lazy initialization of Mongo repositories, and a comprehensive test suite validates provider selection. Minor subscription endpoint URLs are updated.

Changes

Cohort / File(s) Summary
Repository Interfaces
Core/Resgrid.Model/Repositories/IPersonnelLocationsDocRepository.cs, Core/Resgrid.Model/Repositories/IUnitLocationsDocRepository.cs
Added UpdateAsync(TEntity location) method signatures to support update operations for Postgres document repositories.
Repository Implementations
Repositories/Resgrid.Repositories.NoSqlRepository/PersonnelLocationsDocRepository.cs, Repositories/Resgrid.Repositories.NoSqlRepository/UnitLocationsDocRepository.cs
Implemented UpdateAsync methods for conditional row updates by PgId; adjusted DISTINCT ON ordering in SQL queries to include entity ID and timestamp; modified GetByIdAsync to validate result set existence before returning.
Service Layer — Dual Provider Support
Core/Resgrid.Services/UnitsService.cs, Core/Resgrid.Services/UsersService.cs
Introduced conditional routing between Postgres and Mongo repositories based on DocDataConfig.DocDatabaseType; converted direct Mongo repository references to lazy-initialized instances; implemented insert-vs-update branching for Postgres paths; updated event payloads to use location.GetId() instead of .Id.ToString().
Mapping Service
Core/Resgrid.Services/MappingService.cs
Converted IMongoRepository<MapLayer> to Lazy<IMongoRepository<MapLayer>> for deferred initialization; updated method calls to access repository via .Value.
Test Suite
Tests/Resgrid.Tests/Services/DocumentDatabaseProviderSelectionTests.cs
Added new NUnit test fixture validating Postgres provider selection when DocDatabaseType = Postgres, including repository method invocations, event emission with matching RecordId, and prevention of Mongo repository resolution via intentionally-throwing lazy instances.
Subscription Endpoint Updates
Web/Resgrid.Web/Areas/User/Controllers/SubscriptionController.cs, Web/Resgrid.Web/Areas/User/Views/Subscription/Index.cshtml, Web/Resgrid.Web/Areas/User/Views/Subscription/SelectRegistrationPlan.cshtml
Updated Paddle checkout success redirect URL from /User/Subscription/PaddleProcessing?planId= to /User/Subscription/Processing?planId=; adjusted controller view name targeting in PaddleProcessing action.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Service as UnitsService/<br/>UsersService
    participant Config as Config.<br/>DataConfig
    participant PgRepo as Postgres<br/>Repository
    participant MongoRepo as Mongo<br/>Repository (Lazy)
    participant EventAgg as IEventAggregator

    Client->>Service: AddUnitLocation/<br/>SavePersonnelLocation
    Service->>Config: Check DocDatabaseType
    
    alt DocDatabaseType == Postgres
        Service->>PgRepo: location.PgId is blank?
        alt PgId blank
            PgRepo->>PgRepo: InsertAsync(location)
            PgRepo-->>Service: recordId
        else PgId exists
            PgRepo->>PgRepo: UpdateAsync(location)
            PgRepo-->>Service: location with PgId
        end
        Service->>Service: Set event.RecordId = location.GetId()
    else DocDatabaseType != Postgres
        Service->>MongoRepo: InsertAsync/ReplaceAsync(location)
        MongoRepo-->>Service: result
        Service->>Service: Set event.RecordId = location.GetId()
    end
    
    Service->>EventAgg: Publish UnitLocationUpdatedEvent/<br/>PersonnelLocationUpdatedEvent
    EventAgg-->>Client: Event published
Loading
sequenceDiagram
    actor Client
    participant Service as UnitsService/<br/>UsersService
    participant Config as Config.<br/>DataConfig
    participant PgRepo as Postgres<br/>Repository
    participant MongoRepo as Mongo<br/>Repository (Lazy)

    Client->>Service: GetLatestUnitLocations/<br/>GetLatestLocationsForDepartmentPersonnel
    Service->>Config: Check DocDatabaseType
    
    alt DocDatabaseType == Postgres
        Service->>PgRepo: Query Postgres repository
        PgRepo-->>Service: Personnel/Unit locations
    else DocDatabaseType != Postgres
        Service->>MongoRepo: Query via .Value (lazy init)
        MongoRepo-->>Service: Personnel/Unit locations
    end
    
    Service-->>Client: Return locations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • RE1-T112 Fixes #324: Modifies the same subscription controller and view files (SubscriptionController.cs, Index.cshtml, SelectRegistrationPlan.cshtml), indicating direct overlap in Paddle checkout redirect URL handling.
  • RE1-T115 Paddle updates #343: Also updates Paddle checkout flow in Web/Resgrid.Web/Areas/User, altering success endpoint URLs and client-side checkout initiation patterns.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title clearly summarizes the main changes: fixing Postgres database provider selection and Paddle checkout redirect issues.
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 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 and usage tips.

Copy link
Copy Markdown
Contributor

@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

Caution

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

⚠️ Outside diff range comments (1)
Repositories/Resgrid.Repositories.NoSqlRepository/UnitLocationsDocRepository.cs (1)

58-77: ⚠️ Potential issue | 🟡 Minor

Heads-up: the .Any() guard now activates a previously-dead fallback path.

Previously, unitLocationsData != null was almost always true (Dapper returns an empty IEnumerable, not null) so the else branch was unreachable. With the new && unitLocationsData.Any(), a miss on oid now falls through to WHERE ul.id = {id}. If id is the typical Mongo ObjectId / UUID string and the id column is bigint, Postgres will throw an invalid input syntax error rather than gracefully returning null. Worth confirming the id column type and what shapes of id are passed in.

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

In
`@Repositories/Resgrid.Repositories.NoSqlRepository/UnitLocationsDocRepository.cs`
around lines 58 - 77, GetByIdAsync currently uses raw-interpolated SQL and uses
unitLocationsData.Any() which makes the fallback to the second query execute for
non-matching oids and causes Postgres to try to parse string ids as bigints; fix
by parameterizing both queries and only executing the second "WHERE ul.id = `@id`"
query if the incoming id can be safely converted to the id column type (e.g.,
long) — use long.TryParse(id, out var idVal) and call
connection.QueryAsync<UnitsLocation>(sql, new { oid = id }) for the oid lookup
and connection.QueryAsync<UnitsLocation>(sql, new { id = idVal }) for the
numeric id lookup so you avoid SQL injection and invalid input syntax errors
while preserving the original fallback behavior in GetByIdAsync.
🧹 Nitpick comments (3)
Web/Resgrid.Web/Areas/User/Controllers/SubscriptionController.cs (1)

826-832: PaddleProcessing action and view are now dead/orphaned code.

With the Paddle success URLs in Index.cshtml and SelectRegistrationPlan.cshtml updated to point directly at /User/Subscription/Processing, this PaddleProcessing action is no longer reachable from the checkout flow, and Views/Subscription/PaddleProcessing.cshtml is orphaned (the action renders the "Processing" view instead).

A few cleanup options worth considering:

  1. Delete PaddleProcessing and Views/Subscription/PaddleProcessing.cshtml entirely if no in-flight Paddle checkouts or external configurations still reference the old URL.
  2. If you need to keep the old URL alive for backward compatibility, convert it to a thin redirect instead of duplicating the logic:
♻️ Suggested simplification
-		public async Task<IActionResult> PaddleProcessing(int planId)
-		{
-			ProcessingView model = new ProcessingView();
-			model.PlanId = planId;
-
-			return View("Processing", model);
-		}
+		public IActionResult PaddleProcessing(int planId)
+			=> RedirectToAction(nameof(Processing), new { planId });

Note: The method is declared async but contains no await, which will produce a CS1998 compiler warning.

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

In `@Web/Resgrid.Web/Areas/User/Controllers/SubscriptionController.cs` around
lines 826 - 832, The PaddleProcessing action (PaddleProcessing) and the view
Views/Subscription/PaddleProcessing.cshtml are now orphaned because checkout
URLs point to /User/Subscription/Processing; either delete the PaddleProcessing
method and the PaddleProcessing.cshtml view if nothing external references the
old URL, or convert PaddleProcessing into a thin redirect to the existing
Processing endpoint (e.g., return RedirectToAction("Processing", new { planId
})) to preserve backward compatibility; also remove the unnecessary async
modifier (no await) to eliminate the CS1998 warning when you modify the method.
Tests/Resgrid.Tests/Services/DocumentDatabaseProviderSelectionTests.cs (2)

17-32: Recommend marking the fixture [NonParallelizable].

DataConfig.DocDatabaseType is a static field. The [SetUp]/[TearDown] save/restore protects you under NUnit's default serial execution, but if any assembly-level or fixture-level [Parallelizable] is introduced (now or later), tests in this fixture can race with each other and with any other fixture that reads DocDatabaseType, producing flaky failures. Adding [NonParallelizable] to the fixture (or to each test) makes the contract explicit.

♻️ Proposed change
 [TestFixture]
+[NonParallelizable]
 public class DocumentDatabaseProviderSelectionTests
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/Resgrid.Tests/Services/DocumentDatabaseProviderSelectionTests.cs`
around lines 17 - 32, The fixture DocumentDatabaseProviderSelectionTests mutates
the static DataConfig.DocDatabaseType in SetUp/TearDown which can race under
parallel test execution; add the NUnit [NonParallelizable] attribute to the
DocumentDatabaseProviderSelectionTests class declaration so the whole fixture
runs serially and avoids concurrent access to DataConfig.DocDatabaseType.

59-95: Consider adding coverage for the UpdateAsync branch.

Both AddUnitLocationAsync_should_publish_postgres_record_id_* and SavePersonnelLocationAsync_should_publish_postgres_record_id_* only exercise the insert branch (input has no PgId). The update branch (PgId already set → UpdateAsync is called and its return value flows into the published event's RecordId) is untested, which leaves the new IUnitLocationsDocRepository.UpdateAsync / IPersonnelLocationsDocRepository.UpdateAsync paths uncovered.

Also applies to: 121-157

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

In `@Tests/Resgrid.Tests/Services/DocumentDatabaseProviderSelectionTests.cs`
around lines 59 - 95, Add tests that exercise the UpdateAsync branch by
providing inputs with an existing PgId and mocking the repository UpdateAsync to
return a UnitsLocation/PersonnelLocation whose PgId is the value that should be
published; for example, in the AddUnitLocationAsync test set location.PgId =
"314", Setup unitLocationsDocRepository.UpdateAsync(It.IsAny<UnitsLocation>())
to return a UnitsLocation with PgId = "updated-314", call
service.AddUnitLocationAsync, Verify UpdateAsync was called once (instead of
InsertAsync) and assert the published UnitLocationUpdatedEvent.RecordId equals
the returned PgId; do the analogous change for SavePersonnelLocationAsync to
cover IPersonnelLocationsDocRepository.UpdateAsync and the corresponding
PersonnelLocationUpdatedEvent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@Repositories/Resgrid.Repositories.NoSqlRepository/PersonnelLocationsDocRepository.cs`:
- Line 49: The DISTINCT ON query in PersonnelLocationsDocRepository (used in the
unitLocationsData fetch) and the GetByIdAsync fallback are vulnerable because
they interpolate departmentId/id directly and rely on ORDER BY/.Any() behavior
that can trigger a SQL branch with non-numeric id values; change the raw
interpolated SQL to use parameterized queries (pass departmentId and id as
parameters to QueryAsync) and preserve the DISTINCT ON ... ORDER BY userid,
timestamp DESC ordering, and update GetByIdAsync to validate/normalize the id
type before executing the fallback (or use a parameterized WHERE ul.id = `@id` so
Postgres will error safely), ensuring the .Any() guard logic still prevents
unnecessary fallbacks but no longer injects or mis-types the id/departmentId
values.
- Around line 105-116: The UpdateAsync in PersonnelLocationsDocRepository
currently builds an interpolated SQL string (uses
JsonConvert.SerializeObject(location), location.UserId and location.PgId) which
opens SQL-injection and quote-escaping bugs and silently no-ops when PgId is
blank; change UpdateAsync to use a parameterized Dapper call (pass departmentId,
userId, dataJson and id as parameters rather than string-interpolating),
serialize the location to JSON into a parameter (dont inline the JSON), validate
location.PgId (throw ArgumentException/InvalidOperationException if missing)
instead of returning silently, and apply the same parameterization fixes to
InsertAsync for userid and data fields.

In
`@Repositories/Resgrid.Repositories.NoSqlRepository/UnitLocationsDocRepository.cs`:
- Around line 105-116: UpdateAsync currently builds SQL via string interpolation
which allows SQL injection and will break on single quotes in the serialized
JSON; change UpdateAsync to use Dapper parameterization: pass the serialized
JSON (or better, the object) as a parameter and set the column using a parameter
(e.g., data = `@data`::jsonb) and use a parameter for id (WHERE id = `@id`) instead
of inlining location.PgId and JsonConvert.SerializeObject(location). Also handle
blank/null location.PgId explicitly (either throw ArgumentException or return
null) instead of silently no-op so callers know the update was skipped; apply
the same parameterized pattern to InsertAsync (and ideally other Get* methods)
to conform to the repository Dapper pattern.

---

Outside diff comments:
In
`@Repositories/Resgrid.Repositories.NoSqlRepository/UnitLocationsDocRepository.cs`:
- Around line 58-77: GetByIdAsync currently uses raw-interpolated SQL and uses
unitLocationsData.Any() which makes the fallback to the second query execute for
non-matching oids and causes Postgres to try to parse string ids as bigints; fix
by parameterizing both queries and only executing the second "WHERE ul.id = `@id`"
query if the incoming id can be safely converted to the id column type (e.g.,
long) — use long.TryParse(id, out var idVal) and call
connection.QueryAsync<UnitsLocation>(sql, new { oid = id }) for the oid lookup
and connection.QueryAsync<UnitsLocation>(sql, new { id = idVal }) for the
numeric id lookup so you avoid SQL injection and invalid input syntax errors
while preserving the original fallback behavior in GetByIdAsync.

---

Nitpick comments:
In `@Tests/Resgrid.Tests/Services/DocumentDatabaseProviderSelectionTests.cs`:
- Around line 17-32: The fixture DocumentDatabaseProviderSelectionTests mutates
the static DataConfig.DocDatabaseType in SetUp/TearDown which can race under
parallel test execution; add the NUnit [NonParallelizable] attribute to the
DocumentDatabaseProviderSelectionTests class declaration so the whole fixture
runs serially and avoids concurrent access to DataConfig.DocDatabaseType.
- Around line 59-95: Add tests that exercise the UpdateAsync branch by providing
inputs with an existing PgId and mocking the repository UpdateAsync to return a
UnitsLocation/PersonnelLocation whose PgId is the value that should be
published; for example, in the AddUnitLocationAsync test set location.PgId =
"314", Setup unitLocationsDocRepository.UpdateAsync(It.IsAny<UnitsLocation>())
to return a UnitsLocation with PgId = "updated-314", call
service.AddUnitLocationAsync, Verify UpdateAsync was called once (instead of
InsertAsync) and assert the published UnitLocationUpdatedEvent.RecordId equals
the returned PgId; do the analogous change for SavePersonnelLocationAsync to
cover IPersonnelLocationsDocRepository.UpdateAsync and the corresponding
PersonnelLocationUpdatedEvent.

In `@Web/Resgrid.Web/Areas/User/Controllers/SubscriptionController.cs`:
- Around line 826-832: The PaddleProcessing action (PaddleProcessing) and the
view Views/Subscription/PaddleProcessing.cshtml are now orphaned because
checkout URLs point to /User/Subscription/Processing; either delete the
PaddleProcessing method and the PaddleProcessing.cshtml view if nothing external
references the old URL, or convert PaddleProcessing into a thin redirect to the
existing Processing endpoint (e.g., return RedirectToAction("Processing", new {
planId })) to preserve backward compatibility; also remove the unnecessary async
modifier (no await) to eliminate the CS1998 warning when you modify the method.
🪄 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

Run ID: a7e85061-48bd-4e66-9c6d-73771fe351a2

📥 Commits

Reviewing files that changed from the base of the PR and between b0b417c and 2bf0049.

📒 Files selected for processing (11)
  • Core/Resgrid.Model/Repositories/IPersonnelLocationsDocRepository.cs
  • Core/Resgrid.Model/Repositories/IUnitLocationsDocRepository.cs
  • Core/Resgrid.Services/MappingService.cs
  • Core/Resgrid.Services/UnitsService.cs
  • Core/Resgrid.Services/UsersService.cs
  • Repositories/Resgrid.Repositories.NoSqlRepository/PersonnelLocationsDocRepository.cs
  • Repositories/Resgrid.Repositories.NoSqlRepository/UnitLocationsDocRepository.cs
  • Tests/Resgrid.Tests/Services/DocumentDatabaseProviderSelectionTests.cs
  • Web/Resgrid.Web/Areas/User/Controllers/SubscriptionController.cs
  • Web/Resgrid.Web/Areas/User/Views/Subscription/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Subscription/SelectRegistrationPlan.cshtml

@ucswift
Copy link
Copy Markdown
Member Author

ucswift commented Apr 25, 2026

Approve

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 8541773 into master Apr 25, 2026
18 of 19 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.

1 participant