fix(review): address PR #230 review — payload type, breadcrumb helper, tests

Review follow-ups for the combined QA-UI fixes (#216/#206/#204/#218/#192):

- export/utils: correct the misleading getInternalLinkPageName comment — a
  bare `v1.2` loses its last dot-segment (`v1`); dots survive only in
  multi-segment names like `v1.2.md` -> `v1.2`.
- share: extract toPublicSharePayload(page, share): PublicSharePayload, an
  explicit allowlist type+mapper replacing the inline literal in the
  /shares/page-info anonymous path (#218). Add share.controller.spec.ts that
  stubs getSharedPage returning internal fields and asserts the response key
  set EXACTLY equals the whitelist (page + share), so any `...shareData`
  regression or new leaking field fails. Also key-tests the extracted mapper.
- breadcrumb: extract pure resolveBreadcrumbNodes(treeData, ancestors, pageId)
  (tree-hit -> tree; tree-miss -> map ancestors via canonical pageToTreeNode,
  dropping the as-any casts; else null) and unit-test all three branches.
- share-modal: RTL test asserting enabling a share calls mutateAsync with
  includeSubPages: false (#216 security default).
- share.service: one-line note at getSharedPage on the deferred consolidation
  of the ancestor-aware match into resolveReadableSharePage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
a
2026-06-27 20:09:48 +03:00
parent 2d36641f28
commit c9d252cf2a
9 changed files with 474 additions and 50 deletions
+5 -21
View File
@@ -36,6 +36,7 @@ import {
IAuditService,
} from '../../integrations/audit/audit.service';
import { AiSettingsService } from '../../integrations/ai/ai-settings.service';
import { toPublicSharePayload } from './share-public-payload';
@UseGuards(JwtAuthGuard)
@Controller('shares')
@@ -93,30 +94,13 @@ export class ShareController {
? await this.aiSettings.resolvePublicShareAssistantName(workspace.id)
: null;
// Trim the public payload to what the anonymous renderer actually needs
// (#218). Internal metadata — creatorId/lastUpdatedById/contributorIds,
// spaceId/workspaceId, AI/source bookkeeping, lock/template flags,
// parent/position, raw timestamps — must not leak to anonymous viewers.
// Trim the public payload to the explicit allowlist the anonymous renderer
// needs (#218); the PublicSharePayload type + mapper guarantee internal
// metadata can never leak to anonymous viewers (see share-public-payload.ts).
const { page, share } = shareData;
const publicPage = {
id: page.id,
slugId: page.slugId,
title: page.title,
icon: page.icon,
content: page.content,
};
const publicShare = {
id: share.id,
key: share.key,
includeSubPages: share.includeSubPages,
searchIndexing: share.searchIndexing,
level: share.level,
sharedPage: share.sharedPage,
};
return {
page: publicPage,
share: publicShare,
...toPublicSharePayload(page, share),
aiAssistant,
aiAssistantName,
features: this.licenseCheckService.resolveFeatures(