VPR-59 [2/4] CMS migration: content blocks, left nav, link collections (backend)#252
VPR-59 [2/4] CMS migration: content blocks, left nav, link collections (backend)#252rlorenzo wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
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+ rebuiltCMSContentControllerwith 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. |
📝 WalkthroughWalkthroughCMS 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. ChangesContent Block Service and Controller
Estimated code review effort: 4 (Complex) | ~75 minutes Left-Nav Menu Service, Controller, and Permission Filtering
Estimated code review effort: 4 (Complex) | ~60 minutes Link Collection Controller Updates
Estimated code review effort: 3 (Moderate) | ~30 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
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 winPermission strings still hardcoded — inconsistent with sibling controller.
CMSLinkCollectionController.cswas updated to useCmsPermissions.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 winGuard each
LinkIdlookup.updateDtocan still carry an unknown or duplicateLinkIdwith the same item count, solinks.First(...)can throw or update the wrong row. UseFirstOrDefault(or a lookup) and returnBadRequestwhen 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
📒 Files selected for processing (19)
test/CMS/CMSContentControllerTests.cstest/CMS/CMSLeftNavControllerTests.cstest/CMS/CMSLinkCollectionControllerTests.cstest/CMS/CMSLinkCollectionLinksTests.cstest/CMS/CmsContentBlockServiceTests.cstest/CMS/CmsLeftNavDisplayTests.cstest/CMS/CmsLeftNavServiceTests.csweb/Areas/CMS/Controllers/CMSContentController.csweb/Areas/CMS/Controllers/CMSLeftNavController.csweb/Areas/CMS/Controllers/CMSLinkCollectionController.csweb/Areas/CMS/Controllers/CMSLinkCollectionLinks.csweb/Areas/CMS/Data/LeftNavMenu.csweb/Areas/CMS/Models/CMSBlockAddEdit.csweb/Areas/CMS/Models/CmsContentBlockMapper.csweb/Areas/CMS/Models/DTOs/ContentBlockDto.csweb/Areas/CMS/Models/DTOs/LeftNavDtos.csweb/Areas/CMS/Services/CmsContentBlockService.csweb/Areas/CMS/Services/CmsLeftNavService.csweb/Areas/CMS/Services/CmsNavMenu.cs
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
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 (2)
web/Areas/CMS/Services/CmsContentBlockService.cs (1)
160-188: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueRemove the unused
AllowPublicAccessprojection.ContentBlockFileDtoonly carriesFileGuid,FriendlyName, andUrl, so this field is fetched and then dropped.🤖 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/Services/CmsContentBlockService.cs` around lines 160 - 188, The query in CmsContentBlockService is projecting AllowPublicAccess from ContentBlockToFiles but the value is never used when building ContentBlockDto. Remove that unused field from the Files projection in the CmsContentBlockService mapping, keeping only the properties needed by CmsContentBlockMapper.ToFileDto and ContentBlockFileDto.web/Areas/CMS/Controllers/CMSLinkCollectionLinks.cs (1)
99-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMigrate hardcoded permission strings to
CmsPermissions.ManageContentBlocks.
CMSLinkCollectionController.cswas updated in this PR to use theCmsPermissions.ManageContentBlocksconstant, but this file still hardcodes"SVMSecure.CMS.ManageContentBlocks"in five places. Using the constant here too avoids future permission-string drift between the two controllers guarding the same resource tree.♻️ Proposed fix
+using Viper.Areas.CMS.Constants; ... - [Permission(Allow = "SVMSecure.CMS.ManageContentBlocks")] + [Permission(Allow = CmsPermissions.ManageContentBlocks)]Apply to all five occurrences (lines 99, 132, 157, 179, 214).
Also applies to: 132-132, 157-157, 179-179, 214-214
🤖 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, The permission attribute in CMSLinkCollectionLinks still hardcodes the manage-content-blocks string in multiple actions, while CMSLinkCollectionController already uses the shared CmsPermissions.ManageContentBlocks constant. Update all five [Permission] usages in CMSLinkCollectionLinks to reference CmsPermissions.ManageContentBlocks so both controllers stay aligned and avoid permission-string drift.
🤖 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/CMSLinkCollectionLinksTests.cs`:
- Around line 307-324: Add a test in CMSLinkCollectionLinksTests for
UpdateLinkOrder that mirrors the existing tag-category duplicate-id coverage:
create two updates that both use the same LinkId while still matching the total
count, call CMSLinkCollectionController.UpdateLinkOrder, and assert
BadRequestObjectResult. Use SeedCollectionAsync, SeedLinkAsync, and the
UpdateLinkOrderDto list to target the duplicate-id guard in the UpdateLinkOrder
path.
In `@web/Areas/CMS/Services/CmsLeftNavService.cs`:
- Around line 233-249: The AssertNotStale logic in CmsLeftNavService is
duplicated from CmsContentBlockService, so extract the shared concurrency checks
into a common helper and have both services call it instead of maintaining two
copies. Move the identical AssertNotStale behavior (including the missing-stamp
ArgumentException and stale-data CmsConcurrencyException checks) into a shared
static utility such as CmsConcurrencyHelpers, and do the same for the duplicated
CleanList implementation so both services reuse the same helper methods. Keep
the service-specific call sites in CmsLeftNavService and CmsContentBlockService
wired to the shared helpers to avoid future drift.
- Line 103: SaveItemsAsync currently mutates left-nav items without a stale-copy
check, so concurrent batch saves can overwrite changes. Update the
SaveItemsAsync flow in CmsLeftNavService to accept and pass through the menu’s
LastModifiedOn stamp alongside the existing List<LeftNavItemEdit> payload, then
call AssertNotStale before any item mutations. Use the existing request/handler
path that already maps InvalidOperationException to 409 so the new guard is
enforced consistently.
---
Outside diff comments:
In `@web/Areas/CMS/Controllers/CMSLinkCollectionLinks.cs`:
- Line 99: The permission attribute in CMSLinkCollectionLinks still hardcodes
the manage-content-blocks string in multiple actions, while
CMSLinkCollectionController already uses the shared
CmsPermissions.ManageContentBlocks constant. Update all five [Permission] usages
in CMSLinkCollectionLinks to reference CmsPermissions.ManageContentBlocks so
both controllers stay aligned and avoid permission-string drift.
In `@web/Areas/CMS/Services/CmsContentBlockService.cs`:
- Around line 160-188: The query in CmsContentBlockService is projecting
AllowPublicAccess from ContentBlockToFiles but the value is never used when
building ContentBlockDto. Remove that unused field from the Files projection in
the CmsContentBlockService mapping, keeping only the properties needed by
CmsContentBlockMapper.ToFileDto and ContentBlockFileDto.
🪄 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: cf817478-6923-4f38-a1c9-9bab24725fff
📒 Files selected for processing (11)
test/CMS/CMSLinkCollectionControllerTests.cstest/CMS/CMSLinkCollectionLinksTests.cstest/CMS/CmsLeftNavDisplayTests.cstest/CMS/CmsLeftNavServiceTests.csweb/Areas/CMS/Controllers/CMSLeftNavController.csweb/Areas/CMS/Controllers/CMSLinkCollectionController.csweb/Areas/CMS/Controllers/CMSLinkCollectionLinks.csweb/Areas/CMS/Models/CmsContentBlockMapper.csweb/Areas/CMS/Models/DTOs/LeftNavDtos.csweb/Areas/CMS/Services/CmsContentBlockService.csweb/Areas/CMS/Services/CmsLeftNavService.cs
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
web/Areas/CMS/Services/CmsContentBlockService.cs (1)
125-141: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winAdd a unique tiebreaker to the paged sort. Paging this split query with a non-deterministic
ORDER BYcan return unstable pages and mismatch relatedPermissions/Files. AppendThenBy(b => b.ContentBlockId)to every branch beforeSkip/Take.🤖 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/Services/CmsContentBlockService.cs` around lines 125 - 141, The paged sort in CmsContentBlockService is not fully deterministic, which can cause unstable pagination and mismatched related data when the query is split. Update the sort expression used before Skip/Take so every branch in the sort switch appends a unique tiebreaker with ThenBy(b => b.ContentBlockId), including the descending cases, to keep page ordering stable.web/Areas/CMS/Services/CmsLeftNavService.cs (2)
217-219: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRedundant reload after
SaveItemsAsyncsave.
return await GetMenuAsync(leftNavMenuId, ct)re-queries the menu with items/permissions from scratch. The already-loaded, mutatedmenu(adds/removes/updates applied in-memory, and EF populates new item IDs onSaveChangesAsync) can be mapped directly, same asUpdateMenuAsyncdoes at Line 112 (return ToDto(menu)).⚡ Proposed fix
await _context.SaveChangesAsync(ct); - return await GetMenuAsync(leftNavMenuId, ct); + return ToDto(menu);🤖 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/Services/CmsLeftNavService.cs` around lines 217 - 219, The SaveItemsAsync flow in CmsLeftNavService is doing a redundant reload by calling GetMenuAsync after SaveChangesAsync, even though the in-memory menu entity has already been mutated and EF has populated new IDs; update the method to return the mapped menu directly, matching the existing UpdateMenuAsync pattern by using ToDto(menu) instead of re-querying with GetMenuAsync.
224-241: 🗄️ Data Integrity & Integration | 🔵 TrivialFriendly-name uniqueness check is check-then-act, no DB backstop.
AssertFriendlyNameUniqueAsyncguards Create/Update in application code only; without a unique index/constraint onFriendlyName, two concurrent creates/renames could both pass the check and persist duplicate names, whichLayoutController'sFirstOrDefaultresolution (per the file's own comment) depends on being unique.Given the bounded, admin-only nature of this table this is low-probability, but a unique index would close the gap cheaply.
🤖 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/Services/CmsLeftNavService.cs` around lines 224 - 241, The friendly-name validation in AssertFriendlyNameUniqueAsync is only an application-level check, so concurrent Create/Update requests can still persist duplicates. Add a database-level unique constraint/index on the FriendlyName column (while preserving the null-handling behavior used by CmsLeftNavService), and keep the existing pre-check as a user-friendly early failure. Ensure the fix is applied alongside the LeftNavMenus model/migration so LayoutController’s FirstOrDefault lookup can safely rely on uniqueness.
🤖 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.
Outside diff comments:
In `@web/Areas/CMS/Services/CmsContentBlockService.cs`:
- Around line 125-141: The paged sort in CmsContentBlockService is not fully
deterministic, which can cause unstable pagination and mismatched related data
when the query is split. Update the sort expression used before Skip/Take so
every branch in the sort switch appends a unique tiebreaker with ThenBy(b =>
b.ContentBlockId), including the descending cases, to keep page ordering stable.
In `@web/Areas/CMS/Services/CmsLeftNavService.cs`:
- Around line 217-219: The SaveItemsAsync flow in CmsLeftNavService is doing a
redundant reload by calling GetMenuAsync after SaveChangesAsync, even though the
in-memory menu entity has already been mutated and EF has populated new IDs;
update the method to return the mapped menu directly, matching the existing
UpdateMenuAsync pattern by using ToDto(menu) instead of re-querying with
GetMenuAsync.
- Around line 224-241: The friendly-name validation in
AssertFriendlyNameUniqueAsync is only an application-level check, so concurrent
Create/Update requests can still persist duplicates. Add a database-level unique
constraint/index on the FriendlyName column (while preserving the null-handling
behavior used by CmsLeftNavService), and keep the existing pre-check as a
user-friendly early failure. Ensure the fix is applied alongside the
LeftNavMenus model/migration so LayoutController’s FirstOrDefault lookup can
safely rely on uniqueness.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c3d731ff-c099-4a96-b046-f0af41c2af08
📒 Files selected for processing (9)
test/CMS/CMSContentControllerTests.cstest/CMS/CMSLeftNavControllerTests.cstest/CMS/CMSLinkCollectionLinksTests.cstest/CMS/CmsLeftNavServiceTests.csweb/Areas/CMS/Controllers/CMSContentController.csweb/Areas/CMS/Controllers/CMSLeftNavController.csweb/Areas/CMS/Models/DTOs/LeftNavDtos.csweb/Areas/CMS/Services/CmsContentBlockService.csweb/Areas/CMS/Services/CmsLeftNavService.cs
1b1df26 to
1ab4737
Compare
5b15989 to
8b2cee3
Compare
1ab4737 to
0ff3092
Compare
…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.
0ff3092 to
1e7597e
Compare
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
CmsContentBlockService+ rebuiltCMSContentController— 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 anonymouscontent/fn/{name}display endpoint (no editor login ids, permission names, placement metadata, or file keys).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).SafeUrlvalidation.Stack: #251 (files backend) → this PR → management SPA.