Auditable blog post hide/remove for staff (two-tier moderation + immutable audit trail) #476

Closed
opened 2026-07-11 07:01:46 +00:00 by spikerj · 1 comment
Owner

Staff/admin need the ability to take down a blog post in a way that is fully auditable for history/legal purposes.

Design (approved)

  • Two-tier takedown:
    • Hide — reversible; any staff member can hide (required reason) and unhide. Post status becomes Hidden; pre-hide status is stored and restored on unhide.
    • Remove — soft delete (IsDeleted = true); staff can remove (required reason), only an admin can restore. Nothing is ever hard-deleted.
  • Authors keep seeing their own hidden/removed posts, marked with the staff reason. The public does not see them at all.
  • Immutable audit trail: every hide/unhide/remove/restore/author-delete writes a BlogModerationAuditEntry (new blog-moderation-audit Mongo collection): post id, action, actor id/name/roles, reason, previous/new status, title snapshot, author id, IP, user agent, timestamp. Append-only — no update/delete code paths.

Also fixed as part of this work

  • DELETE /api/blog/posts/{id} had no [Authorize] and performed a hard delete. Now requires auth, only the author or staff/admin may delete, and it soft-deletes with an AuthorDelete audit entry.
  • Blog endpoints now use the shared AdminOrStaff/AdminOnly policies (#298) instead of only inline role strings.

Backend (spikersoft-backend)

  • BlogPostStatus.Hidden; BlogPost fields ModeratedBy/ModeratedAt/ModerationReason/PreviousStatus/DeletedBy/DeletionReason.
  • Commands: HideBlogPost, UnhideBlogPost, RemoveBlogPost, RestoreBlogPost (+ rewritten DeleteBlogPost), all with cache invalidation + activity tracking + audit writes.
  • Endpoints: POST posts/{id}/hide|unhide|remove (AdminOrStaff), POST posts/{id}/restore (AdminOnly), GET posts/{id}/moderation-history (AdminOrStaff).
  • Visibility: staff see everything incl. soft-deleted; authors see their own moderated posts; public sees only Published + not-deleted. DTO moderation fields are stripped for other viewers.

Angular (spikersoft-angular)

  • hidden status + moderation fields in blog.model.ts; hidePost/unhidePost/removePost/restorePost/getModerationHistory in BlogService.
  • Staff-only moderation menu on blog entries with a required-reason dialog; hidden/removed banner (with reason) visible to the author and staff.
  • /admin/blog-moderation: new "Moderated Posts" section with Unhide / Restore (admin) and per-post moderation history view.
  • New lib-local role helper blog-roles.ts (same pattern as art-studio-roles.ts).

Deploy-independent: old Angular simply doesn't show the new buttons.

Staff/admin need the ability to take down a blog post in a way that is fully auditable for history/legal purposes. ## Design (approved) - **Two-tier takedown:** - **Hide** — reversible; any staff member can hide (required reason) and unhide. Post status becomes `Hidden`; pre-hide status is stored and restored on unhide. - **Remove** — soft delete (`IsDeleted = true`); staff can remove (required reason), **only an admin can restore**. Nothing is ever hard-deleted. - **Authors keep seeing their own hidden/removed posts**, marked with the staff reason. The public does not see them at all. - **Immutable audit trail:** every hide/unhide/remove/restore/author-delete writes a `BlogModerationAuditEntry` (new `blog-moderation-audit` Mongo collection): post id, action, actor id/name/roles, reason, previous/new status, title snapshot, author id, IP, user agent, timestamp. Append-only — no update/delete code paths. ## Also fixed as part of this work - `DELETE /api/blog/posts/{id}` had **no `[Authorize]`** and performed a **hard delete**. Now requires auth, only the author or staff/admin may delete, and it soft-deletes with an `AuthorDelete` audit entry. - Blog endpoints now use the shared `AdminOrStaff`/`AdminOnly` policies (#298) instead of only inline role strings. ## Backend (spikersoft-backend) - `BlogPostStatus.Hidden`; `BlogPost` fields `ModeratedBy/ModeratedAt/ModerationReason/PreviousStatus/DeletedBy/DeletionReason`. - Commands: `HideBlogPost`, `UnhideBlogPost`, `RemoveBlogPost`, `RestoreBlogPost` (+ rewritten `DeleteBlogPost`), all with cache invalidation + activity tracking + audit writes. - Endpoints: `POST posts/{id}/hide|unhide|remove` (AdminOrStaff), `POST posts/{id}/restore` (AdminOnly), `GET posts/{id}/moderation-history` (AdminOrStaff). - Visibility: staff see everything incl. soft-deleted; authors see their own moderated posts; public sees only Published + not-deleted. DTO moderation fields are stripped for other viewers. ## Angular (spikersoft-angular) - `hidden` status + moderation fields in `blog.model.ts`; `hidePost/unhidePost/removePost/restorePost/getModerationHistory` in `BlogService`. - Staff-only moderation menu on blog entries with a required-reason dialog; hidden/removed banner (with reason) visible to the author and staff. - `/admin/blog-moderation`: new "Moderated Posts" section with Unhide / Restore (admin) and per-post moderation history view. - New lib-local role helper `blog-roles.ts` (same pattern as `art-studio-roles.ts`). Deploy-independent: old Angular simply doesn't show the new buttons.
Author
Owner

Resolved: spikersoft-backend PR #203 (auditable staff hide/remove with immutable audit trail) and spikersoft-angular PR #158 (staff moderation UI — hide/remove with reasons, banners, admin view) + #159 (lint follow-up), all merged to master 2026-07-11. Two-tier hide/remove, append-only BlogModerationAuditEntry collection, and author-visible reasons shipped per the approved design. Closing.

Resolved: spikersoft-backend PR #203 (auditable staff hide/remove with immutable audit trail) and spikersoft-angular PR #158 (staff moderation UI — hide/remove with reasons, banners, admin view) + #159 (lint follow-up), all merged to master 2026-07-11. Two-tier hide/remove, append-only BlogModerationAuditEntry collection, and author-visible reasons shipped per the approved design. Closing.
Sign in to join this conversation.