diff --git a/core-web/libs/sdk/client/src/lib/client/page/page-api.spec.ts b/core-web/libs/sdk/client/src/lib/client/page/page-api.spec.ts index 097ec6c8c41a..7dc4d9c53486 100644 --- a/core-web/libs/sdk/client/src/lib/client/page/page-api.spec.ts +++ b/core-web/libs/sdk/client/src/lib/client/page/page-api.spec.ts @@ -37,6 +37,7 @@ describe('PageClient', () => { const mockGraphQLResponse = { data: { page: { + identifier: 'test-page-id', title: 'GraphQL Page', url: '/graphql-page', layout: { @@ -82,7 +83,13 @@ describe('PageClient', () => { }) as Partial as FetchHttpClient ); - mockRequest.mockResolvedValue(mockGraphQLResponse); + mockRequest.mockImplementation((url: string) => { + if (url.includes('/contenttype-schema')) { + return Promise.resolve({ entity: [] }); + } + + return Promise.resolve(mockGraphQLResponse); + }); }); afterEach(() => { @@ -120,6 +127,17 @@ describe('PageClient', () => { body: expect.stringContaining(`... on Banner`) }); + expect(mockRequest).toHaveBeenCalledWith( + 'https://demo.dotcms.com/api/v1/page/test-page-id/contenttype-schema', + expect.objectContaining({ + method: 'GET', + headers: expect.objectContaining({ + Authorization: 'Bearer test-token', + Accept: 'application/json' + }) + }) + ); + expect(result).toEqual({ pageAsset: { layout: { @@ -146,6 +164,7 @@ describe('PageClient', () => { } }, page: { + identifier: 'test-page-id', title: 'GraphQL Page', url: '/graphql-page' }, diff --git a/core-web/libs/sdk/client/src/lib/client/page/page-api.ts b/core-web/libs/sdk/client/src/lib/client/page/page-api.ts index 884b9c4f2eb6..f7108dd83a48 100644 --- a/core-web/libs/sdk/client/src/lib/client/page/page-api.ts +++ b/core-web/libs/sdk/client/src/lib/client/page/page-api.ts @@ -19,319 +19,46 @@ import { graphqlToPageEntity } from '../../utils'; import { BaseApiClient } from '../base/api/base-api'; /** - * Fetches style editor schemas for the given page URL. - * - * TODO: Replace mock with real endpoint call like: - * GET /api/v1/style-editor/schemas?pageUrl= + * Loads style editor schemas from {@code GET /api/v1/page/{pageId}/contenttype-schema} (schemas in {@code entity}). + * Requires EDIT on the page; failures are ignored so {@link PageClient.get} still works without auth. * * @internal */ async function fetchStyleEditorSchemas( - _url: string, - _config: DotCMSClientConfig, - _requestOptions: DotRequestOptions, - _httpClient: DotHttpClient + pageId: string | undefined, + config: DotCMSClientConfig, + requestOptions: DotRequestOptions, + httpClient: DotHttpClient ): Promise { - // TODO: Replace mock with real endpoint call like: - // GET /api/v1/style-editor/schemas?pageUrl= - return Promise.resolve([ - { - contentType: 'Activity', - sections: [ - { - title: 'Typography', - fields: [ - { - type: 'dropdown', - label: 'Title Size', - id: 'title-size', - config: { - options: [ - { - label: 'Small', - value: 'text-lg' - }, - { - label: 'Medium', - value: 'text-xl' - }, - { - label: 'Large', - value: 'text-2xl' - }, - { - label: 'Extra Large', - value: 'text-3xl' - } - ] - } - }, - { - type: 'dropdown', - label: 'Description Size', - id: 'description-size', - config: { - options: [ - { - label: 'Small', - value: 'text-sm' - }, - { - label: 'Medium', - value: 'text-base' - }, - { - label: 'Large', - value: 'text-lg' - } - ] - } - }, - { - type: 'checkboxGroup', - label: 'Title Style', - id: 'title-style', - config: { - options: [ - { - label: 'Bold', - value: 'bold' - }, - { - label: 'Italic', - value: 'italic' - }, - { - label: 'Underline', - value: 'underline' - } - ] - } - } - ] - }, - { - title: 'Layout', - fields: [ - { - type: 'radio', - label: 'Layout', - id: 'layout', - config: { - options: [ - { - label: 'Left', - value: 'left', - imageURL: - 'https://i.ibb.co/cXv3tfYd/Screenshot-2025-12-23-at-11-58-32-AM.png' - }, - { - label: 'Right', - value: 'right', - imageURL: - 'https://i.ibb.co/v4cJxyLZ/Screenshot-2025-12-23-at-11-59-01-AM.png' - }, - { - label: 'Center', - value: 'center', - imageURL: - 'https://i.ibb.co/kVntSyzn/Screenshot-2025-12-23-at-11-58-50-AM.png' - }, - { - label: 'Overlap', - value: 'overlap', - imageURL: - 'https://i.ibb.co/43Y5KLY/placeholder-icon-design-free-vector.jpg' - } - ], - columns: 2 - } - }, - { - type: 'dropdown', - label: 'Image Height', - id: 'image-height', - config: { - options: [ - { - label: 'Small', - value: 'h-40' - }, - { - label: 'Medium', - value: 'h-56' - }, - { - label: 'Large', - value: 'h-72' - }, - { - label: 'Extra Large', - value: 'h-96' - } - ] - } - } - ] - }, - { - title: 'Card Style', - fields: [ - { - type: 'radio', - label: 'Card Background', - id: 'card-background', - config: { - options: [ - { - label: 'White', - value: 'white' - }, - { - label: 'Gray', - value: 'gray' - }, - { - label: 'Light Blue', - value: 'light-blue' - }, - { - label: 'Light Green', - value: 'light-green' - } - ], - columns: 2 - } - }, - { - type: 'radio', - label: 'Border Radius', - id: 'border-radius', - config: { - options: [ - { - label: 'None', - value: 'none' - }, - { - label: 'Small', - value: 'small' - }, - { - label: 'Medium', - value: 'medium' - }, - { - label: 'Large', - value: 'large' - } - ], - columns: 2 - } - }, - { - type: 'checkboxGroup', - label: 'Card Effects', - id: 'card-effects', - config: { - options: [ - { - label: 'Shadow', - value: 'shadow' - }, - { - label: 'Border', - value: 'border' - } - ] - } - } - ] - }, - { - title: 'Button', - fields: [ - { - type: 'radio', - label: 'Button Color', - id: 'button-color', - config: { - options: [ - { - label: 'Blue', - value: 'blue' - }, - { - label: 'Green', - value: 'green' - }, - { - label: 'Red', - value: 'red' - }, - { - label: 'Purple', - value: 'purple' - }, - { - label: 'Orange', - value: 'orange' - }, - { - label: 'Teal', - value: 'teal' - } - ], - columns: 2 - } - }, - { - type: 'dropdown', - label: 'Button Size', - id: 'button-size', - config: { - options: [ - { - label: 'Small', - value: 'small' - }, - { - label: 'Medium', - value: 'medium' - }, - { - label: 'Large', - value: 'large' - } - ] - } - }, - { - type: 'checkboxGroup', - label: 'Button Style', - id: 'button-style', - config: { - options: [ - { - label: 'Rounded', - value: 'rounded' - }, - { - label: 'Full Rounded', - value: 'full-rounded' - }, - { - label: 'Shadow', - value: 'shadow' - } - ] - } - } - ] - } - ] + if (!pageId) { + return []; + } + + try { + // todo: move to another class to call the endpoint + const url = new URL(config.dotcmsUrl); + url.pathname = `/api/v1/page/${encodeURIComponent(pageId)}/contenttype-schema`; + + const data = await httpClient.request<{ entity: StyleEditorFormSchema[] }>(url.toString(), { + ...requestOptions, + method: 'GET', + headers: { + Accept: 'application/json', + ...requestOptions.headers + } + }); + + const { entity } = data ?? {}; + if (!Array.isArray(entity)) { + return []; } - ]); + + return entity as StyleEditorFormSchema[]; + } catch (error) { + consola.debug('[DotCMS PageClient]: Skipping style editor schemas:', error); + + return []; + } } /** @@ -461,17 +188,12 @@ export class PageClient extends BaseApiClient { const requestBody = JSON.stringify({ query: completeQuery, variables: requestVariables }); try { - const [response, styleEditorSchemas] = await Promise.all([ - fetchGraphQL({ - baseURL: this.dotcmsUrl, - body: requestBody, - headers: requestHeaders, - httpClient: this.httpClient - }), - fetchStyleEditorSchemas(url, this.config, this.requestOptions, this.httpClient) - ]); - - console.log('styleEditorSchemas', styleEditorSchemas); + const response = await fetchGraphQL({ + baseURL: this.dotcmsUrl, + body: requestBody, + headers: requestHeaders, + httpClient: this.httpClient + }); // The GQL endpoint can return errors and data, we need to handle both if (response.errors) { @@ -506,6 +228,14 @@ export class PageClient extends BaseApiClient { }); } + const styleEditorSchemas = await fetchStyleEditorSchemas( + pageResponse.page.identifier, + this.config, + this.requestOptions, + this.httpClient + ); + console.log('styleEditorSchemas', styleEditorSchemas); + const contentResponse = mapContentResponse(response.data, Object.keys(content)); return { @@ -515,7 +245,7 @@ export class PageClient extends BaseApiClient { query: completeQuery, variables: requestVariables }, - ...(styleEditorSchemas && { styleEditorSchemas }) + ...(styleEditorSchemas.length > 0 && { styleEditorSchemas }) }; } catch (error) { // Handle DotHttpError instances diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java index 2c2a2784aa8e..159ed9acbe9f 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResource.java @@ -52,7 +52,6 @@ import com.dotcms.rest.ResponseEntityBooleanView; import com.dotcms.rest.ResponseEntityMapView; import com.dotcms.rest.ResponseEntityPaginatedDataView; -import com.dotcms.rest.ResponseEntityStringView; import com.dotcms.rest.ResponseEntityView; import com.dotcms.rest.WebResource; import com.dotcms.rest.annotation.NoCache; @@ -1910,4 +1909,62 @@ class ContainerStylesData { .collect(Collectors.toList()); } + /** + * Returns the {@code DOT_STYLE_EDITOR_SCHEMA} metadata for each content type that is currently + * present on the specified page. Only content types that actually have a + * {@code DOT_STYLE_EDITOR_SCHEMA} entry in their metadata are included in the response. Returns + * an empty list when no such schemas are found. + * + *

Example: + *

GET /api/v1/page/{pageId}/contenttype-schema
+ * + * @param request The current {@link HttpServletRequest}. + * @param response The current {@link HttpServletResponse}. + * @param pageId Identifier of the HTML Page whose content type schemas are requested. + * @return List of {@code { contentType, schema }} entries - empty when none are found. + */ + @Operation( + operationId = "getPageContentTypeSchemas", + summary = "Get style editor schemas for content types on a page", + description = + "Returns the DOT_STYLE_EDITOR_SCHEMA metadata for each distinct content type " + + "present on the specified page. Content types without a DOT_STYLE_EDITOR_SCHEMA entry " + + "are excluded. Returns an empty object when no schemas are found." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Content type schemas retrieved successfully", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ResponseEntityContentTypeSchemaView.class))), + @ApiResponse(responseCode = "401", description = "Authentication required"), + @ApiResponse(responseCode = "403", description = "User does not have READ permission on the page"), + @ApiResponse(responseCode = "404", description = "Page not found"), + @ApiResponse(responseCode = "500", description = "Error retrieving schema data") + }) + @GET + @Path("/{pageId}/contenttype-schema") + @NoCache + @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) + public Response getPageContentTypeSchemas( + @Context final HttpServletRequest request, + @Context final HttpServletResponse response, + @Parameter(description = "Identifier of the HTML Page", required = true) + @PathParam("pageId") final String pageId) + throws DotDataException, DotSecurityException { + + Logger.debug(this, () -> "Getting content type schemas for page: " + pageId); + + final User user = new WebResource.InitBuilder(webResource) + .requestAndResponse(request, response) + .rejectWhenNoUser(true) + .init() + .getUser(); + + final IHTMLPage page = pageResourceHelper.getPage(user, pageId, request); + + APILocator.getPermissionAPI().checkPermission(page, PermissionLevel.READ, user); + + return Response.ok(new ResponseEntityContentTypeSchemaView( + pageResourceHelper.getStyleEditorSchemasInPage(pageId))).build(); + } + } // E:O:F:PageResource diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java index 0e2da43e2cb2..b03511860a86 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.java @@ -4,10 +4,12 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -18,6 +20,9 @@ import org.apache.velocity.exception.ResourceNotFoundException; import com.dotcms.api.web.HttpServletRequestThreadLocal; +import com.dotcms.contenttype.model.type.ContentType; +import com.dotcms.rest.api.v1.DotObjectMapperProvider; +import com.fasterxml.jackson.databind.ObjectMapper; import com.dotcms.business.WrapInTransaction; import com.dotcms.exception.ExceptionUtil; import com.dotcms.mock.request.CachedParameterDecorator; @@ -833,4 +838,72 @@ private String buildMultiTreeLookupKey(final String container, final String inst variantId != null ? variantId : VariantAPI.DEFAULT_VARIANT.name()); } + /** + * Returns the parsed {@code DOT_STYLE_EDITOR_SCHEMA} entries for every distinct content type + * present on the given page. Contentlets are loaded from the page's multi-tree relationships, + * deduplicated by content type variable, and then filtered to those whose content type carries + * a {@code DOT_STYLE_EDITOR_SCHEMA} metadata entry. + *

+ * Returns an empty list when the page has no contentlets or none of the content types define + * a style editor schema. + * + * @param pageId Identifier of the HTML Page to inspect. + * @return Parsed schema objects, one per distinct content type that has a schema. + * @throws DotDataException If a database error occurs while retrieving the multi-tree data. + */ + public List getStyleEditorSchemasInPage(final String pageId) throws DotDataException { + final List multiTrees = APILocator.getMultiTreeAPI().getMultiTreesByPage(pageId); + + if (multiTrees == null || multiTrees.isEmpty()) { + return Collections.emptyList(); + } + + // gets the contentlets present in the page without duplicates + final List contentlets = multiTrees.stream() + .map(MultiTree::getContentlet) + .filter(UtilMethods::isSet) + .distinct() + .map(id -> Try.of(() -> APILocator.getContentletAPI() + .findContentletByIdentifierAnyLanguageAnyVariant(id)) + .onFailure(e -> Logger.warn(this, "Could not load contentlet '" + id + + "' for page '" + pageId + "': " + e.getMessage())) + .getOrNull()) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + return getStyleEditorSchemas(contentlets); + } + + /** + * Extracts and parses the {@code DOT_STYLE_EDITOR_SCHEMA} metadata entry from each distinct + * content type found in the given contentlet list. Content types are deduplicated by variable + * name, and those without a {@code DOT_STYLE_EDITOR_SCHEMA} entry are excluded from the result. + * Individual parse failures are logged as warnings and skipped. + *

+ * This method is shared by {@link PageResource} (REST response) and + * {@link com.dotmarketing.portlets.htmlpageasset.business.render.page.HTMLPageAssetRenderedBuilder} + * (UVE script injection). + * + * @param contentlets Contentlets whose content types will be inspected for style editor schemas. + * @return Parsed schema objects, one per distinct content type that defines a schema; never + * {@code null}, may be empty. + */ + public static List getStyleEditorSchemas(final List contentlets) { + final ObjectMapper mapper = DotObjectMapperProvider.getInstance().getDefaultObjectMapper(); + return contentlets.stream() + .map(contentlet -> Try.of(contentlet::getContentType).getOrNull()) + .filter(contentType -> contentType != null && UtilMethods.isSet(contentType.variable())) + .collect(Collectors.toMap(ContentType::variable, ct -> ct, + (existing, replacement) -> existing)) + .values().stream() + .map(ct -> Optional.ofNullable(ct.metadata()) + .map(meta -> Try.of(() -> { + final String schemaStr = (String) meta.get("DOT_STYLE_EDITOR_SCHEMA"); + return (Object) mapper.readTree(schemaStr); + }).getOrNull()) + .orElse(null)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + } diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ResponseEntityContentTypeSchemaView.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ResponseEntityContentTypeSchemaView.java new file mode 100644 index 000000000000..5325be9e4545 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/page/ResponseEntityContentTypeSchemaView.java @@ -0,0 +1,15 @@ +package com.dotcms.rest.api.v1.page; + +import com.dotcms.rest.ResponseEntityView; +import java.util.List; + +/** + * Entity View for content type style editor schema responses. + * Contains the {@code DOT_STYLE_EDITOR_SCHEMA} metadata entries keyed by content type variable, + * for each content type present on a given page. + */ +public class ResponseEntityContentTypeSchemaView extends ResponseEntityView> { + public ResponseEntityContentTypeSchemaView(final List styleEditorSchemas) { + super(styleEditorSchemas); + } +} \ No newline at end of file diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/HTMLPageAssetRenderedBuilder.java b/dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/HTMLPageAssetRenderedBuilder.java index 8710bd34c293..bccb542c6157 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/HTMLPageAssetRenderedBuilder.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/HTMLPageAssetRenderedBuilder.java @@ -1,10 +1,10 @@ package com.dotmarketing.portlets.htmlpageasset.business.render.page; import com.dotcms.business.CloseDBIfOpened; -import com.dotcms.contenttype.model.type.ContentType; import com.dotcms.enterprise.license.LicenseManager; import com.dotcms.experiments.model.Experiment; import com.dotcms.rest.api.v1.DotObjectMapperProvider; +import com.dotcms.rest.api.v1.page.PageResourceHelper; import com.dotcms.rendering.velocity.directive.RenderParams; import com.dotcms.rendering.velocity.services.PageRenderUtil; import com.dotcms.rendering.velocity.servlet.VelocityModeHandler; @@ -37,7 +37,6 @@ import com.dotmarketing.util.UtilMethods; import com.dotmarketing.util.VelocityUtil; import com.dotmarketing.util.WebKeys; -import com.fasterxml.jackson.databind.ObjectMapper; import com.liferay.portal.language.LanguageUtil; import com.liferay.portal.model.User; import io.vavr.control.Try; @@ -45,7 +44,6 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; @@ -429,26 +427,14 @@ private String injectUVEScript(final String html, final Collection buildUVEStyleEditorScripts(final Collection containers) { - final ObjectMapper mapper = DotObjectMapperProvider.getInstance().getDefaultObjectMapper(); - final List schemas = containers.stream() + // gets the contentlets present in the page without duplicates + final List contentlets = containers.stream() .flatMap(container -> container.getContentlets().values().stream()) .flatMap(List::stream) - .map(contentlet -> Try.of(contentlet::getContentType).getOrNull()) - .filter(contentType -> contentType != null && UtilMethods.isSet(contentType.variable())) - .collect(Collectors.toMap( - ContentType::variable, - ct -> ct, - (existing, replacement) -> existing)) - .values().stream() - .map(ct -> Optional.ofNullable(ct.metadata()) - .map(meta -> Try.of(() -> { - final String schemaStr = (String) meta.get("DOT_STYLE_EDITOR_SCHEMA"); - return mapper.readTree(schemaStr); - }).getOrNull()) - .orElse(null)) - .filter(Objects::nonNull) .collect(Collectors.toList()); + final List schemas = PageResourceHelper.getStyleEditorSchemas(contentlets); + if (schemas.isEmpty()) { return Optional.empty(); } diff --git a/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml b/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml index 5a9f7e500570..008a4e637017 100644 --- a/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml +++ b/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml @@ -12294,6 +12294,37 @@ paths: summary: Get the multi-tree content structure of a page tags: - Page + /v1/page/{pageId}/contenttype-schema: + get: + description: Returns the DOT_STYLE_EDITOR_SCHEMA metadata for each distinct + content type present on the specified page. Content types without a DOT_STYLE_EDITOR_SCHEMA + entry are excluded. Returns an empty object when no schemas are found. + operationId: getPageContentTypeSchemas + parameters: + - description: Identifier of the HTML Page + in: path + name: pageId + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/ResponseEntityContentTypeSchemaView" + description: Content type schemas retrieved successfully + "401": + description: Authentication required + "403": + description: User does not have READ permission on the page + "404": + description: Page not found + "500": + description: Error retrieving schema data + summary: Get style editor schemas for content types on a page + tags: + - Page /v1/page/{pageId}/languages: get: deprecated: true @@ -29701,6 +29732,31 @@ components: type: array items: type: string + ResponseEntityContentTypeSchemaView: + type: object + properties: + entity: + type: array + items: + type: object + errors: + type: array + items: + $ref: "#/components/schemas/ErrorEntity" + i18nMessagesMap: + type: object + additionalProperties: + type: string + messages: + type: array + items: + $ref: "#/components/schemas/MessageEntity" + pagination: + $ref: "#/components/schemas/Pagination" + permissions: + type: array + items: + type: string ResponseEntityContentView: type: object properties: diff --git a/dotcms-postman/src/main/resources/postman/Define_Contentlets_StyleProperties.postman_collection.json b/dotcms-postman/src/main/resources/postman/Define_Contentlets_StyleProperties.postman_collection.json index 55dae40bda7c..00ffcdcff50a 100644 --- a/dotcms-postman/src/main/resources/postman/Define_Contentlets_StyleProperties.postman_collection.json +++ b/dotcms-postman/src/main/resources/postman/Define_Contentlets_StyleProperties.postman_collection.json @@ -2897,7 +2897,58 @@ "response": [] }, { - "name": "Check Content Type Schema", + "name": "Check Content Type Schema by endpoint", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const contentTypeName = pm.collectionVariables.get(\"contentTypeName\");", + "const contentTypeInfo = pm.collectionVariables.get(\"contentTypeInfo\");", + "", + "pm.test(\"Validate page has the SCHEMA of the Content Type 1 \", function () {", + " const schemas = pm.response.json().entity;", + " pm.expect(schemas).to.exist;", + "", + " const contentType1Schema = schemas.find(item => item.contentType === contentTypeName);", + " pm.expect(contentType1Schema).to.exist;", + " pm.expect(contentType1Schema).to.have.property(\"sections\");", + "});", + "", + "pm.test(\"Should have the correct content type schema\", function () {", + " const schemaStr = JSON.parse(contentTypeInfo).metadata[\"DOT_STYLE_EDITOR_SCHEMA\"];", + " const schema = JSON.parse(schemaStr);", + "", + " pm.expect(schema.contentType).to.eql(contentTypeName);", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/page/{{firstPageIdentifier}}/contenttype-schema", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "{{firstPageIdentifier}}", + "contenttype-schema" + ] + } + }, + "response": [] + }, + { + "name": "Check Content Type Schema Within Script", "event": [ { "listen": "test", @@ -3301,7 +3352,7 @@ "response": [] }, { - "name": "Save Contentlet_1 & Contentlet_3 in FirstPage Copy", + "name": "Save Contentlet_1 & Contentlet_3 in FirstPage", "event": [ { "listen": "test", @@ -3378,7 +3429,7 @@ "response": [] }, { - "name": "Check Schema per Content Type", + "name": "Check Schema per Content Type Within Script", "event": [ { "listen": "test", @@ -3450,6 +3501,189 @@ } }, "response": [] + }, + { + "name": "Check multiple Content Type Schemas by endpoint", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const contentTypeName1 = pm.collectionVariables.get(\"contentTypeName\");", + "const contentTypeName2 = pm.collectionVariables.get(\"contentTypeName_2\");", + "", + "// SCHEMAs", + "const contentTypeInfo1 = pm.collectionVariables.get(\"contentTypeInfo\");", + "const contentTypeInfo2 = pm.collectionVariables.get(\"contentTypeInfo_2\");", + "", + "const schemas = pm.response.json().entity;", + "pm.expect(schemas).to.exist;", + "", + "pm.test(\"Validate page has the SCHEMA of the Content Type 1\", function () {", + " const contentTypeSchema = schemas.find(item => item.contentType === contentTypeName1);", + " pm.expect(contentTypeSchema).to.exist;", + " pm.expect(contentTypeSchema).to.have.property(\"sections\");", + " ", + " const layout = contentTypeSchema.sections[0].title;", + " pm.expect(layout).to.eql(\"Layout\");", + "});", + "", + "pm.test(\"Should have the correct content type schema for the content type 1\", function () {", + " const schemaStr = JSON.parse(contentTypeInfo1).metadata[\"DOT_STYLE_EDITOR_SCHEMA\"];", + " const schema = JSON.parse(schemaStr);", + "", + " pm.expect(schema.contentType).to.eql(contentTypeName1);", + "});", + "", + "pm.test(\"Validate page has the SCHEMA of the Content Type 2\", function () {", + " const contentTypeSchema = schemas.find(item => item.contentType === contentTypeName2);", + " pm.expect(contentTypeSchema).to.exist;", + " pm.expect(contentTypeSchema).to.have.property(\"sections\");", + "", + " const typography = contentTypeSchema.sections[0].title;", + " pm.expect(typography).to.eql(\"Typography\");", + "});", + "", + "pm.test(\"Should have the correct content type schema for the content type 2\", function () {", + " const schemaStr = JSON.parse(contentTypeInfo2).metadata[\"DOT_STYLE_EDITOR_SCHEMA\"];", + " const schema = JSON.parse(schemaStr);", + "", + " pm.expect(schema.contentType).to.eql(contentTypeName2);", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/page/{{firstPageIdentifier}}/contenttype-schema", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "{{firstPageIdentifier}}", + "contenttype-schema" + ] + } + }, + "response": [] + }, + { + "name": "Delete Content of the Page", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const contentlet1 = pm.collectionVariables.get(\"Contentlet_1\");", + "const contentlet3 = pm.collectionVariables.get(\"Contentlet_3\");", + "", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "// Parse JSON", + "const json = pm.response.json();", + "", + "pm.test(\"Entity array exists\", function () {", + " pm.expect(json.entity).to.be.an(\"array\").that.is.empty;", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\n {\n \"identifier\": \"SYSTEM_CONTAINER\",\n \"uuid\": \"1\",\n \"contentletsId\": []\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/page/{{firstPageIdentifier}}/content", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "{{firstPageIdentifier}}", + "content" + ] + }, + "description": "Add Content into the Page: Container and Contentlets." + }, + "response": [] + }, + { + "name": "Check No Content Type Schemas by endpoint", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Validate no SCHEMAs are present in the Page\", function () {", + " const schemas = pm.response.json().entity;", + " pm.expect(schemas).to.exist;", + " pm.expect(schemas.length).to.equal(0);", + "});" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/page/{{firstPageIdentifier}}/contenttype-schema", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "page", + "{{firstPageIdentifier}}", + "contenttype-schema" + ] + } + }, + "response": [] } ], "description": "## Traditional Style Editor\n\nThis folder covers the end-to-end workflow for creating and managing content types SCHEMAS, and associating contentlets with pages using the traditional style editor approach.\n\n- Build and Save Contetn Type Schema\n \n- Save content in a Page using the same Cotent Type\n \n- Check `registerStyleEditorSchemas` only has 1 Content Type Schema.\n \n- Create a new Content Type and it's schema; use it in a new Contentlet\n \n- Save content in a Page with two different Content Types\n \n- Check `registerStyleEditorSchemas` has 2 Content Type Schemas.\n \n\nAlso covers that Traditional Style properties are applied at the page level and verified through rendered output, allowing developers to validate that `data-dot-style-properties` are correctly reflected in the page response.\n\n- Validates that `data-dot-style-properties` is present only for Contentlets that have Style properties defined."