Skip to content

VPR-59 [4/4] CMS: delegated block editing via per-block edit permissions#255

Open
rlorenzo wants to merge 14 commits into
VPR-59-cms-r3-frontendfrom
feature/cms-delegated-editing
Open

VPR-59 [4/4] CMS: delegated block editing via per-block edit permissions#255
rlorenzo wants to merge 14 commits into
VPR-59-cms-r3-frontendfrom
feature/cms-delegated-editing

Conversation

@rlorenzo

@rlorenzo rlorenzo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Slice 4 of the CMS migration stack (stacks on #253; the diff shows only this feature). Adds delegated block editing: admins attach RAPS permissions to a block as its edit permissions, and holders of any of them can edit that block's content and attached files — without SVMSecure.CMS.ManageContentBlocks and without access to anything else.

⚠️ Manual schema change required (before deploying to each environment)

CREATE TABLE dbo.ContentBlockToEditPermission (
    ContentBlockEditPermissionID int IDENTITY(1,1) NOT NULL
        CONSTRAINT PK_ContentBlockToEditPermission PRIMARY KEY,
    ContentBlockID int NOT NULL
        CONSTRAINT FK_ContentBlockToEditPermission_ContentBlock REFERENCES dbo.ContentBlock (contentBlockID),
    permission varchar(500) NOT NULL
);

CREATE INDEX IX_ContentBlockToEditPermission_ContentBlockID
    ON dbo.ContentBlockToEditPermission (ContentBlockID) INCLUDE (permission);

Apply to dev → TEST → PROD ahead of the corresponding deploy (same manual-DDL process as VPR-143). Additive only; invisible to legacy ColdFusion (which reads only the view-permission table), so coexistence is unaffected.

Semantics

  • Edit access to block B = ManageContentBlocks or any of B's edit permissions (ANY-OF).
  • An empty edit list means manager-only — delegation is explicit (deliberately not the view list's "empty ⇒ all SVMSecure" rule).
  • Delegated editors CAN: edit content, attach existing managed files (capped, minimal-DTO search), upload new files into the block's folder (they inherit the block's view permissions, and the editor can attach them even without holding those permissions), detach files, view/load version history, save (history attribution + 409 stale-edit guard unchanged).
  • Delegated editors CANNOT: rename (title or friendly name), change system/section/page/order/public access, change either permission list, delete/restore, reach the manage list/history pages, or touch the file store beyond the block-scoped operations above.
  • Soft-deleted blocks are not editable by delegates.

Implementation

  • New ContentBlockToEditPermission entity/table + CanEditAsync authorization (single-resolve permission HashSet, fail-closed).
  • Content-only PATCH gains attachment deltas; server-side field whitelisting is the enforcement boundary (the delegated DTO simply has no settings fields).
  • New content-scoped endpoints so the editor has ONE code path for all users: GET /editable, GET /attachable-files, per-block files/check-name, POST {id}/files, rollback-only DELETE {id}/files/{guid} (owner + folder + not-attached-elsewhere constrained).
  • Attach guard: newly-attached files must pass the same download-access check as the search, with one exception — a file the current user uploaded into this block's own folder (uploader + folder match, mirroring the rollback-delete rule). This lets a delegate attach an inline upload whose inherited view permission they don't hold, while blocking a restricted file they uploaded for a different folder from being attached onto a broader-visibility block.
  • Editor renders a capability mode: read-only settings summary for delegates; managers additionally get the "Edit access" selector. Hub gains a "Blocks you can edit" card for delegates.
  • Public fn endpoint DTO unchanged (no edit-permission disclosure).
  • Tests: authorization matrix, field-scoping, attachment deltas, rollback-delete constraints, attach uploader/folder exception (in-folder allowed, cross-folder rejected), editable/attachable filtering, capability-mode rendering, hub card, PATCH conflict flow.

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 “delegated block editing” to the CMS migration stack by introducing per-block edit-permission lists (separate from view permissions) and a content-scoped editing/file workflow that allows non-managers to edit only content + attachments for blocks they’re delegated.

Changes:

  • Introduces ContentBlockToEditPermission (entity + EF mapping) and edit-authorization in CmsContentBlockService (CanEditAsync, editable-block listing, attachable-file search, rollback-delete eligibility).
  • Expands the CMS content API with content-scoped endpoints for delegated editors (editable blocks list, attachable-files search, block-scoped file check-name/upload/rollback-delete) and adds attachment deltas to the content-only PATCH.
  • Updates the Vue CMS editor and hub to support “capability mode” (manager vs delegated UI) and adds/updates frontend + backend tests for delegated scenarios.

Reviewed changes

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

Show a summary per file
File Description
web/Models/VIPER/ContentBlockToEditPermission.cs New EF entity representing a block → edit-permission row.
web/Models/VIPER/ContentBlock.cs Adds navigation collection for edit-permission rows.
web/Classes/SQLContext/VIPERContext.cs Registers DbSet + model configuration for the new table.
web/Areas/CMS/Services/CmsContentBlockService.cs Core authorization + delegated editing behaviors (content-only update with attachment deltas, editable listing, attachable-file search, rollback-delete checks).
web/Areas/CMS/Models/DTOs/ContentBlockDto.cs Adds EditPermissions to block DTO; adds minimal CmsAttachableFileDto.
web/Areas/CMS/Models/CmsContentBlockMapper.cs Maps edit-permission list into DTO.
web/Areas/CMS/Models/CMSBlockAddEdit.cs Adds EditPermissions to create/update request model.
web/Areas/CMS/Controllers/CMSContentController.cs Widens selected endpoints to SVMSecure + CanEditAsync gate; adds content-scoped endpoints for delegated file attach/upload/rollback-delete.
VueApp/src/CMS/types/index.ts Adds editPermissions, plus types for editable blocks and attachable file search results.
VueApp/src/CMS/router/routes.ts Broadens content-block edit route permission to SVMSecure (server is source of truth).
VueApp/src/CMS/pages/ContentBlockEdit.vue Implements manager vs delegated capability UI; uses PATCH for delegated saves; switches attach/search and rollback paths to content-scoped APIs.
VueApp/src/CMS/pages/CmsHome.vue Adds “Blocks you can edit” hub card (delegated editors).
VueApp/src/CMS/components/PermissionSelector.vue Makes hint configurable to reuse for edit-permissions selector.
VueApp/src/CMS/components/InlineFileUpload.vue Adds block-scoped upload mode (check-name/upload/rollback-delete) for delegated editors.
VueApp/src/CMS/tests/inline-file-upload.test.ts Tests block-scoped uploader behavior (no folder/permission fields; no use-existing; POST overwrite).
VueApp/src/CMS/tests/content-block-edit-save.test.ts Updates save payload expectations; tests edit-access selector; updates rollback URL expectations; updates attachable-files behavior.
VueApp/src/CMS/tests/content-block-edit-delegated.test.ts New delegated-mode editor tests (read-only settings, PATCH save, conflict handling).
VueApp/src/CMS/tests/cms-home.test.ts New tests for delegated editable-blocks hub card behavior.
test/CMS/CMSContentControllerTests.cs Extends controller tests for CanEditAsync gating + content-scoped file ops.
test/CMS/CmsContentBlockServiceTests.cs Adds service-level tests for delegated authorization, attachment deltas, editable listing, attachable search, and rollback-delete eligibility.

Comment thread VueApp/src/CMS/pages/ContentBlockEdit.vue Outdated
Comment thread web/Areas/CMS/Controllers/CMSContentController.cs Outdated
Comment thread VueApp/src/CMS/pages/ContentBlockEdit.vue Outdated
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Delegated CMS editors can discover assigned blocks, edit content without manager settings access, replace attachments, and use block-scoped file APIs. Backend permission storage, authorization, controller endpoints, frontend flows, and tests were updated accordingly.

Changes

Delegated CMS editing

Layer / File(s) Summary
Edit permissions and service authorization
web/Models/..., web/Areas/CMS/Services/..., web/Classes/SQLContext/...
Adds persisted edit permissions and service logic for delegated authorization, attachment validation, content-only updates, and rollback eligibility.
Editor API gates and block-scoped files
web/Areas/CMS/Controllers/...
Adds per-block authorization to content, history, discovery, attachment search, upload, and rollback endpoints.
Delegated editor UI and upload routing
VueApp/src/CMS/pages/..., VueApp/src/CMS/components/..., VueApp/src/CMS/types/..., VueApp/src/CMS/router/...
Adds editable-block discovery, delegated read-only settings, content-only saves, manager edit access, attachable-file search, and block-scoped uploads.
Backend authorization and file-operation coverage
test/CMS/...
Tests delegated authorization, status handling, attachment validation, editable-block discovery, attachable-file filtering, and rollback safety.
Delegated editing and upload coverage
VueApp/src/CMS/__tests__/*
Tests delegated discovery, controls, save payloads, conflicts, attachment search, and scoped upload behavior.

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

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.26% 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
Title check ✅ Passed The title clearly summarizes the main change: delegated CMS block editing via per-block edit permissions.
Description check ✅ Passed The description matches the changeset and accurately describes delegated block editing and the schema change.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/cms-delegated-editing

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.

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 20 out of 20 changed files in this pull request and generated 3 comments.

Comment thread web/Areas/CMS/Controllers/CMSContentController.cs Outdated
Comment thread web/Classes/SQLContext/VIPERContext.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 20 out of 20 changed files in this pull request and generated 2 comments.

Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs
Comment thread web/Classes/SQLContext/VIPERContext.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 20 out of 20 changed files in this pull request and generated 1 comment.

Comment thread web/Areas/CMS/Services/CmsContentBlockService.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 20 out of 20 changed files in this pull request and generated 1 comment.

Comment thread VueApp/src/CMS/router/routes.ts

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 20 out of 20 changed files in this pull request and generated 2 comments.

Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs
Comment thread web/Areas/CMS/Services/CmsContentBlockService.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 20 out of 20 changed files in this pull request and generated no new comments.

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 20 out of 20 changed files in this pull request and generated no new comments.

@rlorenzo rlorenzo changed the title [4/4] CMS: delegated block editing via per-block edit permissions VPR-59 [4/4] CMS: delegated block editing via per-block edit permissions Jul 6, 2026
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r3-frontend branch from f1ca704 to c7bd327 Compare July 10, 2026 07:57
@rlorenzo
rlorenzo force-pushed the feature/cms-delegated-editing branch from 26628ea to 45255eb Compare July 10, 2026 07:57
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r3-frontend branch from c7bd327 to bdacb1a Compare July 10, 2026 08:28

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 26 out of 26 changed files in this pull request and generated 4 comments.

Comment thread web/Areas/CMS/Services/CmsFileService.cs
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs Outdated
Comment thread web/Areas/CMS/Controllers/CMSContentController.cs Outdated
@rlorenzo
rlorenzo force-pushed the feature/cms-delegated-editing branch from 192b05e to cb5b979 Compare July 17, 2026 08:59
@rlorenzo
rlorenzo requested a review from Copilot July 17, 2026 09:00

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 26 out of 26 changed files in this pull request and generated 3 comments.

Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs
Comment thread web/Areas/CMS/Services/CmsContentBlockService.cs Outdated
Comment thread web/Areas/CMS/Controllers/CMSContentController.cs Outdated
@rlorenzo
rlorenzo force-pushed the feature/cms-delegated-editing branch from cb5b979 to 87cecc9 Compare July 17, 2026 09:15
@rlorenzo
rlorenzo requested a review from Copilot July 17, 2026 09:15

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 26 out of 26 changed files in this pull request and generated no new comments.

rlorenzo added 14 commits July 20, 2026 10:22
Slice 3/3 of the restacked CMS migration (tip file states). The Vue 3
management UI: files, content blocks with QEditor and version diffs,
left-nav editor, link collections, import and bulk-encrypt tools, the
redesigned hub with the recent-activity rail, shared sortable-list /
server-table / dialog components, mobile card mode, and the full
frontend test suite. Completes the stack; the tree now matches the
development branch tip exactly.
- extract getAuditActionColor to a shared composable so the CMS file
  audit and the Effort audit list (and any future audit surface) color
  the same verb classes identically, per PR #253 review feedback
- extract ChangeDetailDiff from AuditList's three layouts to remove the
  old/new diff markup duplication flagged by the JSCPD gate
feature/clinical-scheduler-audit-log merges to main first and reworked
the same regions of AuditList (filter card, search row, change-detail
markup). Adopt its version wholesale - including AuditChangeDetail,
byte-identical, extended to the desktop layout - and re-apply only the
shared audit palette, so the later main merge resolves cleanly.
ChangeDetailDiff is superseded and removed.
Adds a "view source" toolbar command so editors can inspect and edit
the underlying HTML directly (img alt text, a href/target) until
richer attribute dialogs exist.
…ady on the page

The "Add Left-Nav Menu" link used onMounted to check ?add=1, so
re-clicking it while already on the page did nothing (dialog didn't
open, query param wasn't stripped, nav highlight was wrong). Switched
to the same consume-the-query-flag watcher pattern Files.vue already
uses for ?upload=1.
…system selects

- File upload's "VIPER app (folder)" select now notes the folder can't
  be changed after upload, matching the pattern Content Blocks already
  uses for its section-path select
- Content block's "System" select now explains the Viper/Public choice
  (which site renders the block) instead of relying on the label alone
Deleted/all views gain a sortable "Purges" column (purgeOn = deletedOn
+ 30 days; sorts by the API's deletedOn for an identical order), a
matching card-mode field, and a warning badge when a file's purge is
within 7 days or overdue -- previously only visible per-file via the
"Deleted ... purges ..." row label.
… page

- Strip a leading "viper" path segment on the import page to match the
  legacy paste format, mirroring the backend's ResolveSource handling
- Add a "Diff vs current" row action alongside the existing
  diff-with-previous-version action, so reviewers can see what changed
  since a given saved version without it being superseded first
- Disable the action for rows whose block has since been deleted
…PA base path

- Both create-spa-router and bootstrap-spa stripped only a single
  trailing slash from VITE_VIPER_HOME, so a misconfigured value with
  more than one (e.g. "/2///") would leave a stray slash in the router
  base / area-path prefix check
- Mirrors the backend's new blank-URL rejection with the same
  client-side check the empty-text validation already uses, so the
  error surfaces immediately instead of after a round trip
Admins attach RAPS permissions to a content block as its EDIT
permissions; holders of any of them can edit that block without
SVMSecure.CMS.ManageContentBlocks.

- New ContentBlockToEditPermission entity/table (schema applied
  manually per environment; DDL in the PR description). An empty
  edit list means manager-only: delegation is explicit, deliberately
  not the view list "empty means all SVMSecure" rule
- CanEditAsync (manager OR edit-permission intersection, fail-closed,
  single-resolve HashSet) gates block load, history, diff, and the
  content-only PATCH, which now also carries attachment deltas; the
  delegated DTO has no settings fields, so field whitelisting is
  server-side by construction
- Content-scoped file endpoints (attachable search capped at 25 with
  a minimal DTO, per-block check-name/upload, rollback-only delete
  constrained to uploader+folder+not-attached-elsewhere) give the
  editor one code path for managers and delegates alike
- Editor renders a capability mode: read-only settings summary for
  delegates, full editor plus a new "Edit access" selector for
  managers; the hub gains a "Blocks you can edit" card; the editor
  route relies on server enforcement
- Delegates can view and load version history (restoring is a content
  save); soft-deleted blocks are not delegate-editable; the anonymous
  fn endpoint discloses nothing new
- 48 new tests (authorization matrix, field scoping, attachment
  deltas, rollback constraints, capability-mode rendering, hub card)
…of the default hint

- PermissionSelector's hint prop now accepts null to suppress the
  default hint text entirely (no reserved bottom space), instead of
  every consumer being stuck with "Users need any one of the selected
  permissions"
- Left nav edit hoists that copy to a single explanatory line above the
  item list instead of repeating it under every item's selector
…nt attach

- RollbackDeleteFileAsync's "not attached elsewhere" recheck and the
  soft-delete it guards were two separate statements with no
  transactional isolation, leaving a race window where a concurrent
  attach to a different block could land in the gap and get stranded
  by the delete anyway
- Wrap both in a Serializable transaction via a shared TransactionHelper
  (also usable by other services with the same needs); tests override
  the new virtual ExecuteInTransactionAsync hook to bypass transactions,
  since the EF in-memory provider doesn't support them
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r3-frontend branch from faa3eb1 to f91a9e1 Compare July 20, 2026 18:23
@rlorenzo
rlorenzo force-pushed the feature/cms-delegated-editing branch from 87cecc9 to f41adea Compare July 20, 2026 18:23
rlorenzo added a commit that referenced this pull request Jul 20, 2026
- Reconciles Development with the CMS stack (PRs #251-#255) rebased onto main e029c14
- Content already present from prior TEST-deploy merges; uses -s ours to keep the tree unchanged
- Brings the rebased commit SHAs into Development's ancestry so future merges stay clean
@rlorenzo
rlorenzo force-pushed the VPR-59-cms-r3-frontend branch 2 times, most recently from c73f33b to 055a9a5 Compare July 23, 2026 07:10
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.

3 participants