From 251da997aad4e2e23fea563044d7c20fb05b4316 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 27 Jun 2024 13:09:50 +0200 Subject: [PATCH] fix: ensure published records are updated accordingly (#4180) --- .../src/operations/entry/index.ts | 64 +++++++++++++++---- .../src/operations/entry/index.ts | 16 +++++ ...tentEntriesOnByMetaFieldsOverrides.test.ts | 51 ++++++++++++--- 3 files changed, 109 insertions(+), 22 deletions(-) diff --git a/packages/api-headless-cms-ddb-es/src/operations/entry/index.ts b/packages/api-headless-cms-ddb-es/src/operations/entry/index.ts index f1339912489..a632da4d443 100644 --- a/packages/api-headless-cms-ddb-es/src/operations/entry/index.ts +++ b/packages/api-headless-cms-ddb-es/src/operations/entry/index.ts @@ -43,7 +43,7 @@ import { createElasticsearchBody } from "./elasticsearch/body"; import { createLatestRecordType, createPublishedRecordType, createRecordType } from "./recordType"; import { StorageOperationsCmsModelPlugin } from "@webiny/api-headless-cms"; import { WriteRequest } from "@webiny/aws-sdk/client-dynamodb"; -import { batchReadAll, BatchReadItem, put } from "@webiny/db-dynamodb"; +import { batchReadAll, BatchReadItem } from "@webiny/db-dynamodb"; import { createTransformer } from "./transformations"; import { convertEntryKeysFromStorage } from "./transformations/convertEntryKeys"; import { @@ -278,6 +278,18 @@ export const createEntriesStorageOperations = ( SK: createLatestSortKey() }; + const publishedKeys = { + PK: createPartitionKey({ + id: entry.id, + locale: model.locale, + tenant: model.tenant + }), + SK: createPublishedSortKey() + }; + + // We'll need this flag below. + const isPublished = entry.status === "published"; + const esLatestData = await transformer.getElasticsearchLatestEntryData(); const items = [ @@ -293,9 +305,16 @@ export const createEntriesStorageOperations = ( }) ]; - const { index } = configurations.es({ - model - }); + if (isPublished) { + items.push( + entity.putBatch({ + ...storageEntry, + TYPE: createPublishedRecordType(), + ...publishedKeys + }) + ); + } + try { await batchWriteAll({ table: entity.table, @@ -315,17 +334,34 @@ export const createEntriesStorageOperations = ( } ); } - /** - * Update the "latest" entry item in the Elasticsearch - */ + + const { index: esIndex } = configurations.es({ + model + }); + + const esItems: BatchWriteItem[] = [ + esEntity.putBatch({ + ...latestKeys, + index: esIndex, + data: esLatestData + }) + ]; + + if (isPublished) { + const esPublishedData = await transformer.getElasticsearchPublishedEntryData(); + esItems.push( + esEntity.putBatch({ + ...publishedKeys, + index: esIndex, + data: esPublishedData + }) + ); + } + try { - await put({ - entity: esEntity, - item: { - ...latestKeys, - index, - data: esLatestData - } + await batchWriteAll({ + table: esEntity.table, + items: esItems }); } catch (ex) { throw new WebinyError( diff --git a/packages/api-headless-cms-ddb/src/operations/entry/index.ts b/packages/api-headless-cms-ddb/src/operations/entry/index.ts index c1f123cf9f2..c9719b52a64 100644 --- a/packages/api-headless-cms-ddb/src/operations/entry/index.ts +++ b/packages/api-headless-cms-ddb/src/operations/entry/index.ts @@ -247,6 +247,7 @@ export const createEntriesStorageOperations = ( * We need to: * - create the main entry item * - update the last entry item to a current one + * - update the published entry item to a current one (if the entry is published) */ const items = [ entity.putBatch({ @@ -266,6 +267,21 @@ export const createEntriesStorageOperations = ( GSI1_SK: createGSISortKey(storageEntry) }) ]; + + const isPublished = entry.status === "published"; + if (isPublished) { + items.push( + entity.putBatch({ + ...storageEntry, + PK: partitionKey, + SK: createPublishedSortKey(), + TYPE: createPublishedType(), + GSI1_PK: createGSIPartitionKey(model, "P"), + GSI1_SK: createGSISortKey(storageEntry) + }) + ); + } + try { await batchWriteAll({ table: entity.table, diff --git a/packages/api-headless-cms/__tests__/contentAPI/contentEntriesOnByMetaFieldsOverrides.test.ts b/packages/api-headless-cms/__tests__/contentAPI/contentEntriesOnByMetaFieldsOverrides.test.ts index 0b6683a48f3..ee008997469 100644 --- a/packages/api-headless-cms/__tests__/contentAPI/contentEntriesOnByMetaFieldsOverrides.test.ts +++ b/packages/api-headless-cms/__tests__/contentAPI/contentEntriesOnByMetaFieldsOverrides.test.ts @@ -2,19 +2,19 @@ import { useTestModelHandler } from "~tests/testHelpers/useTestModelHandler"; import { identityA, identityB, identityC, identityD } from "./security/utils"; describe("Content entries - Entry Meta Fields Overrides", () => { - const { manage: managerIdentityA } = useTestModelHandler({ + const { read: readIdentityA, manage: manageIdentityA } = useTestModelHandler({ identity: identityA }); beforeEach(async () => { - await managerIdentityA.setup(); + await manageIdentityA.setup(); }); test("users should be able to create and immediately publish an entry with custom publishing-related values", async () => { // 1. Initially, all meta fields should be populated, except the "modified" ones. const testDate = new Date("2020-01-01T00:00:00.000Z").toISOString(); - const { data: rev } = await managerIdentityA.createTestEntry({ + const { data: rev } = await manageIdentityA.createTestEntry({ data: { status: "published", revisionFirstPublishedOn: testDate, @@ -50,7 +50,7 @@ describe("Content entries - Entry Meta Fields Overrides", () => { const testDate2 = new Date("2021-01-01T00:00:00.000Z").toISOString(); const testDate3 = new Date("2022-01-01T00:00:00.000Z").toISOString(); - const { data: rev } = await managerIdentityA.createTestEntry({ + const { data: rev } = await manageIdentityA.createTestEntry({ data: { status: "published", revisionFirstPublishedOn: testDate1, @@ -65,7 +65,7 @@ describe("Content entries - Entry Meta Fields Overrides", () => { }); const { data: publishedRevWithCustomLastPublishedValues } = - await managerIdentityA.createTestEntryFrom({ + await manageIdentityA.createTestEntryFrom({ revision: rev.id, data: { status: "published", @@ -98,8 +98,8 @@ describe("Content entries - Entry Meta Fields Overrides", () => { rev.revisionFirstPublishedOn ).toBeTrue(); - const { data: publishedRevWithAllCustomValues } = - await managerIdentityA.createTestEntryFrom({ + const { data: publishedRevWithAllCustomValues } = await manageIdentityA.createTestEntryFrom( + { revision: publishedRevWithCustomLastPublishedValues.id, data: { status: "published", @@ -112,7 +112,8 @@ describe("Content entries - Entry Meta Fields Overrides", () => { firstPublishedBy: identityD, lastPublishedBy: identityD } - }); + } + ); expect(publishedRevWithAllCustomValues).toMatchObject({ createdOn: expect.toBeDateString(), @@ -128,5 +129,39 @@ describe("Content entries - Entry Meta Fields Overrides", () => { firstPublishedBy: identityD, lastPublishedBy: identityD }); + + // Ensure that the new published revision is the one that is returned when listing or getting the entry. + + // 1. Manage API. + const { data: getEntryManage } = await manageIdentityA.getTestEntry({ + entryId: rev.entryId + }); + + expect(getEntryManage).toMatchObject({ + meta: { + status: "published", + version: 3 + } + }); + + const { data: listEntriesManage } = await manageIdentityA.listTestEntries(); + + expect(listEntriesManage).toMatchObject([ + { + meta: { + status: "published", + version: 3 + } + } + ]); + + // 2. Read API (here we can't get versions directly, so we're just inspecting the revision ID). + const { data: getEntryRead } = await readIdentityA.getTestEntry({ + where: { entryId: rev.entryId } + }); + expect(getEntryRead.id).toEndWith("#0003"); + + const { data: listEntriesRead } = await readIdentityA.listTestEntries(); + expect(listEntriesRead[0].id).toEndWith("#0003"); }); });