refactor(html-embed): extract the admin-gate strip into one tested helper (#90)

The 4-step html-embed gate (feature-enabled AND role-allowed -> stripHtmlEmbedNodes)
was replicated across call-sites, pinned only by brittle source-regex tests. Add
stripHtmlEmbedIfNotAllowed(json, {featureEnabled, role, onStrip}) and migrate the
5 plain strip-all sites (collab handler, page create+duplicate, both import paths,
transclusion) to it, each keeping its own feature/role resolve + log via onStrip.
Left the 2 sites with different semantics: persistence.extension (#29 preserve-
admin) and share.service (feature-only kill-switch, no role gate). Real unit tests
replace the regex pins; behavior identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-21 03:49:52 +03:00
parent c486750b2a
commit a2ded7ecfb
7 changed files with 193 additions and 63 deletions

View File

@@ -32,10 +32,8 @@ import {
removeMarkTypeFromDoc,
} from '../../../common/helpers/prosemirror/utils';
import {
hasHtmlEmbedNode,
htmlEmbedAllowed,
isHtmlEmbedFeatureEnabled,
stripHtmlEmbedNodes,
stripHtmlEmbedIfNotAllowed,
} from '../../../common/helpers/prosemirror/html-embed.util';
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
import {
@@ -157,15 +155,14 @@ export class PageService {
const htmlEmbedEnabled = isHtmlEmbedFeatureEnabled(
(await this.workspaceRepo.findById(workspaceId))?.settings,
);
if (
!htmlEmbedAllowed(htmlEmbedEnabled, callerRole) &&
hasHtmlEmbedNode(prosemirrorJson)
) {
this.logger.warn(
`Stripping htmlEmbed node(s) from page creation by user ${userId} (space ${createPageDto.spaceId})`,
);
prosemirrorJson = stripHtmlEmbedNodes(prosemirrorJson);
}
prosemirrorJson = stripHtmlEmbedIfNotAllowed(prosemirrorJson, {
featureEnabled: htmlEmbedEnabled,
role: callerRole,
onStrip: () =>
this.logger.warn(
`Stripping htmlEmbed node(s) from page creation by user ${userId} (space ${createPageDto.spaceId})`,
),
});
content = prosemirrorJson;
textContent = jsonToText(prosemirrorJson);
@@ -782,15 +779,14 @@ export class PageService {
// that contains an embed into a new page authored by them. Strip every
// htmlEmbed node from each duplicated page when the duplicating user is
// not an admin, BEFORE computing textContent/ydoc/insert.
if (
!htmlEmbedAllowed(htmlEmbedEnabled, authUser.role) &&
hasHtmlEmbedNode(prosemirrorJson)
) {
this.logger.warn(
`Stripping htmlEmbed node(s) from page duplication by user ${authUser.id} (source page ${page.id})`,
);
prosemirrorJson = stripHtmlEmbedNodes(prosemirrorJson);
}
prosemirrorJson = stripHtmlEmbedIfNotAllowed(prosemirrorJson, {
featureEnabled: htmlEmbedEnabled,
role: authUser.role,
onStrip: () =>
this.logger.warn(
`Stripping htmlEmbed node(s) from page duplication by user ${authUser.id} (source page ${page.id})`,
),
});
// Add "Copy of " prefix to the root page title only for duplicates in same space
let title = page.title;

View File

@@ -34,10 +34,8 @@ import { jsonToNode } from '../../../collaboration/collaboration.util';
import { Page, User } from '@docmost/db/types/entity.types';
import { PageAccessService } from '../page-access/page-access.service';
import {
hasHtmlEmbedNode,
htmlEmbedAllowed,
isHtmlEmbedFeatureEnabled,
stripHtmlEmbedNodes,
stripHtmlEmbedIfNotAllowed,
} from '../../../common/helpers/prosemirror/html-embed.util';
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
@@ -774,12 +772,14 @@ export class TransclusionService {
const htmlEmbedEnabled = isHtmlEmbedFeatureEnabled(
(await this.workspaceRepo.findById(user.workspaceId))?.settings,
);
if (!htmlEmbedAllowed(htmlEmbedEnabled, user.role) && hasHtmlEmbedNode(content)) {
this.logger.warn(
`Stripping htmlEmbed node(s) from transclusion unsync by user ${user.id} (reference page ${referencePageId}, source page ${sourcePageId})`,
);
content = stripHtmlEmbedNodes(content);
}
content = stripHtmlEmbedIfNotAllowed(content, {
featureEnabled: htmlEmbedEnabled,
role: user.role,
onStrip: () =>
this.logger.warn(
`Stripping htmlEmbed node(s) from transclusion unsync by user ${user.id} (reference page ${referencePageId}, source page ${sourcePageId})`,
),
});
return { content };
}