Skip to content

VPR-59 [2/4] CMS migration: content blocks, left nav, link collections (backend)#252

Open
rlorenzo wants to merge 4 commits into
VPR-59-cms-r1-files-backendfrom
VPR-59-cms-r2-content-nav-backend
Open

VPR-59 [2/4] CMS migration: content blocks, left nav, link collections (backend)#252
rlorenzo wants to merge 4 commits into
VPR-59-cms-r1-files-backendfrom
VPR-59-cms-r2-content-nav-backend

Conversation

@rlorenzo

@rlorenzo rlorenzo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Slice 2 of 4 (stacks on #251; the diff shows only this slice). Built from the CI-verified branch tip — see #251 for why the earlier 6-PR stack (#245-#250) was replaced.

Scope — content/nav backend

  • Content blocks: CmsContentBlockService + rebuilt CMSContentController — server-paged filterable list, version history with htmldiff-based sanitized diffs, restore, attached-file GUID deltas, ModifiedOn 409 concurrency guard, content-only PATCH, admin-gated permanent delete, and a minimal public DTO on the anonymous content/fn/{name} display endpoint (no editor login ids, permission names, placement metadata, or file keys).
  • Left nav: CmsLeftNavService + CMSLeftNavController — menu CRUD, batch item save with ordering and per-item permissions, unknown-id rejection, empty-header spacer rows preserved (legacy parity), display filtering that hides permission-less items from anonymous requests but shows them to any signed-in user (legacy parity).
  • Link collections: routable 201 Location, restore/delete/tag fixes, scheme-relative URL rejection in SafeUrl validation.
  • Unit tests for all of the above.

Stack: #251 (files backend) → this PR → management SPA.

Copilot AI 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.

Pull request overview

Adds the backend portion of the CMS migration for content blocks, left-nav menu management, and link collections, aligning behavior with the legacy CMS while introducing new APIs and unit tests.

Changes:

  • Introduces CmsContentBlockService + rebuilt CMSContentController with paging/filtering, edit history (including sanitized diffs), restore/delete, and concurrency guards.
  • Adds left-nav menu management via CmsLeftNavService + CMSLeftNavController, including batch item replace semantics and legacy-parity display filtering.
  • Fixes/adjusts link-collection API behaviors (Location headers, delete cascades, tag-category create/reorder robustness) and adds comprehensive unit tests.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
web/Areas/CMS/Services/CmsNavMenu.cs Updates CMS admin nav composition and permission gating for new CMS features.
web/Areas/CMS/Services/CmsLeftNavService.cs New service for left-nav menu CRUD + batch item save semantics.
web/Areas/CMS/Services/CmsContentBlockService.cs New service implementing content-block CRUD, history, diffs, concurrency, and paging/filtering.
web/Areas/CMS/Models/DTOs/LeftNavDtos.cs New DTOs + validation for left-nav menus/items.
web/Areas/CMS/Models/DTOs/ContentBlockDto.cs New DTOs for content blocks, history, diffs, and anonymous public rendering.
web/Areas/CMS/Models/CmsContentBlockMapper.cs Mapperly mapper for content-block DTO shapes (full + public).
web/Areas/CMS/Models/CMSBlockAddEdit.cs Extends content-block add/edit contract with file GUIDs + LastModifiedOn concurrency token.
web/Areas/CMS/Data/LeftNavMenu.cs Adjusts display-time filtering to match legacy behavior (permission-less items visible to signed-in users).
web/Areas/CMS/Controllers/CMSLinkCollectionLinks.cs Adjusts POST Location to a routable action for link creation.
web/Areas/CMS/Controllers/CMSLinkCollectionController.cs Permission constant usage + delete cascade fix + tag-category create/reorder robustness.
web/Areas/CMS/Controllers/CMSLeftNavController.cs New API controller for left-nav menu management.
web/Areas/CMS/Controllers/CMSContentController.cs Rebuilt content API controller delegating to services; adds history/diff endpoints and admin-only permanent delete.
test/CMS/CMSLinkCollectionLinksTests.cs New tests for link CRUD, ordering, grouping, and tag save behavior.
test/CMS/CMSLinkCollectionControllerTests.cs New tests for collection CRUD and tag-category behaviors.
test/CMS/CmsLeftNavServiceTests.cs New tests for left-nav service CRUD + batch item replace behavior.
test/CMS/CmsLeftNavDisplayTests.cs New tests for left-nav display permission filtering parity.
test/CMS/CMSLeftNavControllerTests.cs New controller wiring tests for left-nav endpoints and status mapping.
test/CMS/CMSContentControllerTests.cs New controller wiring tests for content endpoints (including history/diff) and admin-gated permanent delete.
test/CMS/CmsContentBlockServiceTests.cs New tests for content-block service filtering, history semantics, diffs, concurrency, and delete/restore behavior.

Comment thread web/Areas/CMS/Services/CmsNavMenu.cs
Comment thread web/Areas/CMS/Services/CmsLeftNavService.cs Outdated
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.

Comment thread web/Areas/CMS/Services/CmsNavMenu.cs
Comment thread web/Areas/CMS/Data/LeftNavMenu.cs
Comment thread web/Areas/CMS/Services/CmsLeftNavService.cs
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

CMS content-block handling now uses DTO/service endpoints, left-nav management adds a dedicated service/controller path, and link-collection endpoints change permission constants, deletion order, and validation. Tests cover the new controller and service flows across all three areas.

Changes

Content Block Service and Controller

Layer / File(s) Summary
Content DTOs and mapper
web/Areas/CMS/Models/DTOs/ContentBlockDto.cs, web/Areas/CMS/Models/CmsContentBlockMapper.cs, web/Areas/CMS/Models/CMSBlockAddEdit.cs
Adds content, public-content, file, history, diff, and audit DTOs; extends CMSBlockAddEdit; maps content blocks into DTOs and file URLs.
CmsContentBlockService implementation
web/Areas/CMS/Services/CmsContentBlockService.cs
Implements listing, retrieval, create/update, content-only update, delete/restore, history, diff, audit paging, section-path lookup, and validation helpers.
CMSContentController refactor
web/Areas/CMS/Controllers/CMSContentController.cs
Uses injected services, returns DTOs, adds section-paths/folders/history/diff endpoints, and maps validation/concurrency/deletion results to HTTP responses.
Tests
test/CMS/CMSContentControllerTests.cs, test/CMS/CmsContentBlockServiceTests.cs
Covers controller response mapping, permission gating, serialization, and service CRUD, concurrency, history, diff, and security behavior.

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

Left-Nav Menu Service, Controller, and Permission Filtering

Layer / File(s) Summary
Left-nav DTOs
web/Areas/CMS/Models/DTOs/LeftNavDtos.cs
Adds menu/item DTOs and create/update payloads with validation.
CmsLeftNavService implementation
web/Areas/CMS/Services/CmsLeftNavService.cs
Implements menu CRUD, batch item save/reorder, permission syncing, audit stamping, and DTO mapping.
CMSLeftNavController
web/Areas/CMS/Controllers/CMSLeftNavController.cs
Adds menu retrieval, create, update, save-items, and delete endpoints with exception mapping.
LeftNavMenu and CmsNavMenu updates
web/Areas/CMS/Data/LeftNavMenu.cs, web/Areas/CMS/Services/CmsNavMenu.cs
Switches to injected IUserHelper and CmsPermissions, and changes left-nav item filtering rules.
Tests
test/CMS/CMSLeftNavControllerTests.cs, test/CMS/CmsLeftNavServiceTests.cs, test/CMS/CmsLeftNavDisplayTests.cs
Covers controller wiring, service CRUD/save-items behavior, and display filtering rules.

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

Link Collection Controller Updates

Layer / File(s) Summary
Permission constants and deletion ordering
web/Areas/CMS/Controllers/CMSLinkCollectionController.cs, web/Areas/CMS/Controllers/CMSLinkCollectionLinks.cs
Switches to permission constants, adds tag-category DTO mapping, changes created-action targets, reorders dependent deletions, and strengthens reorder validation.
Tests
test/CMS/CMSLinkCollectionControllerTests.cs, test/CMS/CMSLinkCollectionLinksTests.cs
Covers collection/tag-category CRUD and link CRUD, reorder, and tag-save behavior.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Suggested reviewers: bsedwards

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly matches the backend CMS migration changes for content blocks, left nav, and link collections.
Description check ✅ Passed The description is directly related to the implemented CMS backend services, controllers, and tests.
✨ 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 VPR-59-cms-r2-content-nav-backend

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.

@rlorenzo

rlorenzo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 9

Caution

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

⚠️ Outside diff range comments (2)
web/Areas/CMS/Controllers/CMSLinkCollectionLinks.cs (2)

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

Permission strings still hardcoded — inconsistent with sibling controller.

CMSLinkCollectionController.cs was updated to use CmsPermissions.ManageContentBlocks, but this file still hardcodes "SVMSecure.CMS.ManageContentBlocks" in five places. Same string, but future changes to the constant won't propagate here.

♻️ Proposed fix
+using Viper.Areas.CMS.Constants;
...
-        [Permission(Allow = "SVMSecure.CMS.ManageContentBlocks")]
+        [Permission(Allow = CmsPermissions.ManageContentBlocks)]

(repeat for all five occurrences)

Also applies to: 132-132, 157-157, 179-179, 210-210

🤖 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/Areas/CMS/Controllers/CMSLinkCollectionLinks.cs` at line 99, Replace the
hardcoded permission string in CMSLinkCollectionLinks so it uses the shared
CmsPermissions.ManageContentBlocks constant instead of
"SVMSecure.CMS.ManageContentBlocks". Update all five Permission attributes in
this controller (including the one on the visible action and the other
occurrences in the class) to match the sibling CMSLinkCollectionController
pattern and keep permission changes centralized.

198-202: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard each LinkId lookup. updateDto can still carry an unknown or duplicate LinkId with the same item count, so links.First(...) can throw or update the wrong row. Use FirstOrDefault (or a lookup) and return BadRequest when an id is missing.

🤖 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/Areas/CMS/Controllers/CMSLinkCollectionLinks.cs` around lines 198 - 202,
The link update loop in CMSLinkCollectionLinks is doing an unsafe
`links.First(...)` lookup for each `LinkId`, which can throw on unknown ids or
silently target the wrong row when duplicates are present. Update the `foreach`
logic to use a safe lookup in the `updateDto` processing path (for example,
`FirstOrDefault` or a keyed lookup), and validate that every `li.LinkId` exists
in `links` before applying `SortOrder`. If any id is missing, return
`BadRequest` instead of continuing.
🤖 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 `@test/CMS/CmsLeftNavDisplayTests.cs`:
- Around line 66-96: Add test coverage for the anonymous-user path in
LeftNavMenu.GetLeftNavMenus: both existing tests use a signed-in AaudUser, so
they miss the currentUser == null branch. Add a new test alongside
GetLeftNavMenus_HidesItemsTheUserLacksPermissionFor and
GetLeftNavMenus_ReturnsAllItems_WhenFilteringDisabled that sets
_userHelper.GetCurrentUser() to return null/ReturnsNull and asserts
permissionless items are hidden for an anonymous caller, using the existing
SeedMixedMenuAsync and DataLeftNav setup.

In `@test/CMS/CMSLinkCollectionLinksTests.cs`:
- Around line 265-306: Add a test for UpdateLinkOrder that uses the correct
number of updates but includes an unknown LinkId, mirroring
UpdateLinkCollectionTagCategoryOrder_RejectsUnknownCategoryId. In
CMSLinkCollectionLinksTests, extend the UpdateLinkOrder coverage with a
count-matching but invalid LinkId case and assert the controller does not crash;
this should expose the .First() failure path in UpdateLinkOrder on the
CMSLinkCollectionLinksController flow.

In `@web/Areas/CMS/Controllers/CMSLinkCollectionController.cs`:
- Around line 223-230: Replace the hand-written ToTagCategoryDto helper with a
Mapperly static partial mapper to match the repo convention. Move the
LinkCollectionTagCategory to LinkCollectionTagCategoryDto mapping into a
dedicated mapper class for this area, using [Mapper(RequiredMappingStrategy =
RequiredMappingStrategy.None)] like CmsContentBlockMapper. Then update
CMSLinkCollectionController to use the generated mapper instead of manually
constructing the DTO.
- Around line 102-113: The delete flow in CMSLinkCollectionController currently
queries Links twice using the same collection id, once for collectionLinkIds and
again for RemoveRange, causing a redundant SELECT. Update the removal logic
around collectionLinkIds so the Links set is materialized once and reused for
both LinkTags cleanup and link deletion, while keeping the existing atomic
delete order in the same controller action.

In `@web/Areas/CMS/Models/CmsContentBlockMapper.cs`:
- Around line 10-24: The ContentBlock file projection is duplicated between
CmsContentBlockMapper.ToDto and CmsContentBlockService.GetContentBlocksAsync.
Extract the shared FileGuid/FriendlyName/Url mapping into a reusable helper on
CmsContentBlockMapper, such as a public ToFileDto method, and update both ToDto
and GetContentBlocksAsync to call it so the file-mapping logic lives in one
place.

In `@web/Areas/CMS/Services/CmsContentBlockService.cs`:
- Around line 160-193: The file projection in
CmsContentBlockService.GetContentBlocksAsync is duplicating
CmsContentBlockMapper.ToDto by manually building ContentBlockFileDto with
FileGuid, FriendlyName, and Url via Data.CMS.GetFriendlyURL. Refactor this block
to reuse the existing mapper/DTO projection instead of re-implementing the file
mapping, so the file shape and URL-generation logic live in one place and stay
consistent.
- Around line 212-261: Add a unique constraint for ContentBlock.FriendlyName so
duplicate blocks cannot be created even if AssertFriendlyNameUniqueAsync passes
concurrently. Update the create/update flow in CreateContentBlockAsync and
UpdateContentBlockAsync to rely on the database constraint as the final source
of truth, and keep the pre-check only for user-friendly validation. If the save
fails due to a duplicate key, translate that into a clear validation error for
FriendlyName.

In `@web/Areas/CMS/Services/CmsLeftNavService.cs`:
- Around line 44-73: GetMenusAsync is loading full LeftNavMenu entity graphs
with Include/ThenInclude/AsSplitQuery and then mapping them to DTOs afterward,
which is unnecessary for this read-only query. Update
CmsLeftNavService.GetMenusAsync to project directly to LeftNavMenuDto inside the
IQueryable with Select, keeping AsNoTracking and the existing filters/order, and
remove the Include/ThenInclude/AsSplitQuery chain since EF can resolve needed
navigations in the projection.
- Around line 95-111: UpdateMenuAsync currently overwrites the record without
checking whether the caller is working from a stale copy. Make
LeftNavMenuAddEdit round-trip ModifiedOn (or another concurrency token) and,
inside CmsLeftNavService.UpdateMenuAsync, compare the incoming value against the
loaded menu before ApplyMenuFields/save; if it does not match, reject the update
with a 409-style concurrency response consistent with the content-block flow.

---

Outside diff comments:
In `@web/Areas/CMS/Controllers/CMSLinkCollectionLinks.cs`:
- Line 99: Replace the hardcoded permission string in CMSLinkCollectionLinks so
it uses the shared CmsPermissions.ManageContentBlocks constant instead of
"SVMSecure.CMS.ManageContentBlocks". Update all five Permission attributes in
this controller (including the one on the visible action and the other
occurrences in the class) to match the sibling CMSLinkCollectionController
pattern and keep permission changes centralized.
- Around line 198-202: The link update loop in CMSLinkCollectionLinks is doing
an unsafe `links.First(...)` lookup for each `LinkId`, which can throw on
unknown ids or silently target the wrong row when duplicates are present. Update
the `foreach` logic to use a safe lookup in the `updateDto` processing path (for
example, `FirstOrDefault` or a keyed lookup), and validate that every
`li.LinkId` exists in `links` before applying `SortOrder`. If any id is missing,
return `BadRequest` instead of continuing.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 6056fa5e-e25f-4cbf-a133-e7a09e767771

📥 Commits

Reviewing files that changed from the base of the PR and between b5c5a0c and 27aeae3.

📒 Files selected for processing (19)
  • test/CMS/CMSContentControllerTests.cs
  • test/CMS/CMSLeftNavControllerTests.cs
  • test/CMS/CMSLinkCollectionControllerTests.cs
  • test/CMS/CMSLinkCollectionLinksTests.cs
  • test/CMS/CmsContentBlockServiceTests.cs
  • test/CMS/CmsLeftNavDisplayTests.cs
  • test/CMS/CmsLeftNavServiceTests.cs
  • web/Areas/CMS/Controllers/CMSContentController.cs
  • web/Areas/CMS/Controllers/CMSLeftNavController.cs
  • web/Areas/CMS/Controllers/CMSLinkCollectionController.cs
  • web/Areas/CMS/Controllers/CMSLinkCollectionLinks.cs
  • web/Areas/CMS/Data/LeftNavMenu.cs
  • web/Areas/CMS/Models/CMSBlockAddEdit.cs
  • web/Areas/CMS/Models/CmsContentBlockMapper.cs
  • web/Areas/CMS/Models/DTOs/ContentBlockDto.cs
  • web/Areas/CMS/Models/DTOs/LeftNavDtos.cs
  • web/Areas/CMS/Services/CmsContentBlockService.cs
  • web/Areas/CMS/Services/CmsLeftNavService.cs
  • web/Areas/CMS/Services/CmsNavMenu.cs

Comment thread test/CMS/CmsLeftNavDisplayTests.cs
Comment thread test/CMS/CMSLinkCollectionLinksTests.cs
Comment thread web/Areas/CMS/Controllers/CMSLinkCollectionController.cs
Comment thread web/Areas/CMS/Controllers/CMSLinkCollectionController.cs
Comment thread web/Areas/CMS/Models/CmsContentBlockMapper.cs
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs
Comment thread web/Areas/CMS/Services/CmsLeftNavService.cs
Comment thread web/Areas/CMS/Services/CmsLeftNavService.cs

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

Comment thread web/Areas/CMS/Services/CmsLeftNavService.cs Outdated
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs Outdated
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs Outdated
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs Outdated
Comment thread web/Areas/CMS/Controllers/CMSLinkCollectionController.cs Outdated

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Comment thread web/Areas/CMS/Controllers/CMSLinkCollectionController.cs

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

@rlorenzo

rlorenzo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@rlorenzo rlorenzo changed the title VPR-59 [2/3] CMS migration: content blocks, left nav, link collections (backend) VPR-59 [2/4] CMS migration: content blocks, left nav, link collections (backend) Jul 6, 2026
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r2-content-nav-backend branch from 1b1df26 to 1ab4737 Compare July 10, 2026 07:57
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r1-files-backend branch from 5b15989 to 8b2cee3 Compare July 10, 2026 08:28
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r2-content-nav-backend branch 2 times, most recently from 0ff3092 to 1e7597e Compare July 10, 2026 15:11
@rlorenzo
rlorenzo requested review from bniedzie and bsedwards July 15, 2026 16:00
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r2-content-nav-backend branch from 1e7597e to d364f87 Compare July 17, 2026 05:11
@rlorenzo
rlorenzo requested a review from Copilot July 17, 2026 05:12

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Comment on lines +185 to +204
foreach (var requested in items)
{
if (requested.LeftNavItemId > 0 && existingById.TryGetValue(requested.LeftNavItemId, out var existing))
{
existing.MenuItemText = requested.MenuItemText;
existing.IsHeader = requested.IsHeader;
existing.Url = requested.IsHeader ? null : requested.Url;
existing.DisplayOrder = order;
ApplyItemPermissionDeltas(existing, CleanList(requested.Permissions));
}
else
{
var newItem = new LeftNavItem
{
LeftNavMenuId = leftNavMenuId,
MenuItemText = requested.MenuItemText,
IsHeader = requested.IsHeader,
Url = requested.IsHeader ? null : requested.Url,
DisplayOrder = order
};
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r2-content-nav-backend branch from d364f87 to c783cfc Compare July 17, 2026 07:50
@rlorenzo
rlorenzo requested a review from Copilot July 17, 2026 07:51

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.

Comment on lines +28 to +31
if (canManageBlocks || canCreateBlocks || canManageFiles || canManageNav)
{
nav.Add(new NavMenuItem { MenuItemText = "Home", MenuItemURL = "Home", IndentLevel = 1 });
}
Comment on lines +38 to +48
nav.Add(new NavMenuItem { MenuItemText = "Manage Content Blocks", MenuItemURL = "ManageContentBlocks", IndentLevel = 1 });
}
if (canCreateBlocks)
{
nav.Add(new NavMenuItem { MenuItemText = "Add Content Block", MenuItemURL = "ManageContentBlocks/Edit", IndentLevel = 1 });
}
if (canManageBlocks)
{
nav.Add(new NavMenuItem { MenuItemText = "Edit History", MenuItemURL = "ManageContentBlocks/History", IndentLevel = 1 });
nav.Add(new NavMenuItem { MenuItemText = "Manage Link Collections", MenuItemURL = "ManageLinkCollections", IndentLevel = 1 });
}
Comment on lines +53 to +57
nav.Add(new NavMenuItem { MenuItemText = "Files", IsHeader = true });
nav.Add(new NavMenuItem { MenuItemText = "Manage Files", MenuItemURL = "ManageFiles", IndentLevel = 1 });
nav.Add(new NavMenuItem { MenuItemText = "Add File", MenuItemURL = "ManageFiles?upload=1", IndentLevel = 1 });
nav.Add(new NavMenuItem { MenuItemText = "Audit Trail", MenuItemURL = "ManageFiles/Audit", IndentLevel = 1 });
}
Comment on lines +61 to 64
nav.Add(new NavMenuItem { MenuItemText = "Left Navigation Menus", IsHeader = true });
nav.Add(new NavMenuItem { MenuItemText = "Manage Left-Nav Menus", MenuItemURL = "ManageLeftNav", IndentLevel = 1 });
nav.Add(new NavMenuItem { MenuItemText = "Add Left-Nav Menu", MenuItemURL = "ManageLeftNav?add=1", IndentLevel = 1 });
}
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r2-content-nav-backend branch from c783cfc to d73c63f Compare July 17, 2026 08:13
@rlorenzo
rlorenzo requested a review from Copilot July 17, 2026 08:13

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

Comment on lines +101 to +105
// status: 1 = active only. A public display endpoint must never serve soft-deleted
// blocks (passing null would include DeletedOn != null rows).
var blocks = new Data.CMS(_context, _rapsContext, _sanitizerService)
.GetContentBlocksAllowed(null, friendlyName, null, null, null, null, null, 1)?.ToList();
if (blocks == null || blocks.Count == 0)

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Comment on lines +268 to +273
var existing = item.LeftNavItemToPermissions.ToList();
foreach (var p in existing.Where(p => !requested.Contains(p.Permission, StringComparer.OrdinalIgnoreCase)))
{
item.LeftNavItemToPermissions.Remove(p);
_context.Remove(p);
}
Comment on lines +603 to +608
var existing = block.ContentBlockToPermissions.ToList();
foreach (var cbp in existing.Where(cbp => !requested.Contains(cbp.Permission, StringComparer.OrdinalIgnoreCase)))
{
block.ContentBlockToPermissions.Remove(cbp);
_context.Remove(cbp);
}
Comment on lines +622 to +627
var existing = block.ContentBlockToFiles.ToList();
foreach (var cbf in existing.Where(cbf => !requested.Contains(cbf.FileGuid)))
{
block.ContentBlockToFiles.Remove(cbf);
_context.Remove(cbf);
}
rlorenzo added 4 commits July 17, 2026 01:43
…ns API


Slice 2/3 of the restacked CMS migration (tip file states). Content
blocks with version history, diffs, and the 409 stale-edit guard;
left-nav menu and item management with display-parity filtering;
link-collection fixes; and their unit tests. Stacks on the files
backend; the management SPA follows in slice 3.
…th a null URL

- Header items are persisted with a null Url, but NavMenuItem's
  LeftNavItem constructor passed that null straight through to the
  non-nullable MenuItemURL; the left-nav view's header/spacer branch
  checks MenuItemURL against "" specifically, so a null took the link
  branch instead and failed resolving a null URL
- Reject saving a non-header item with a blank Url too, since it would
  otherwise save successfully but render as an inert, non-clickable
  spacer row
- GetContentBlocksAllowed already returns a materialized list; the
  extra ToList() copied it again. FirstOrDefault replaces the
  null/Count/indexer dance with a single null check.
- The removal pass checked List.Contains inside a loop over existing
  permissions; the add pass right below it already builds a HashSet
  for the same kind of check. Do the same for removal.
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r2-content-nav-backend branch from c839741 to 62c4d96 Compare July 17, 2026 08:45
@rlorenzo
rlorenzo requested a review from Copilot July 17, 2026 08:45

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

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