From 8fa74c9732833e5332627d339a6db74e15f44aa1 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Thu, 19 Dec 2024 13:26:14 -0500 Subject: [PATCH 1/4] contentstack init --- .../actions/create-entry/create-entry.mjs | 49 +++++ .../actions/publish-entry/publish-entry.mjs | 30 ++++ .../actions/update-entry/update-entry.mjs | 53 ++++++ components/contentstack/contentstack.app.mjs | 167 ++++++++++++++++- components/contentstack/package.json | 2 +- .../new-asset-instant/new-asset-instant.mjs | 167 +++++++++++++++++ .../new-entry-instant/new-entry-instant.mjs | 149 +++++++++++++++ .../publish-entry-instant.mjs | 170 ++++++++++++++++++ 8 files changed, 783 insertions(+), 4 deletions(-) create mode 100644 components/contentstack/actions/create-entry/create-entry.mjs create mode 100644 components/contentstack/actions/publish-entry/publish-entry.mjs create mode 100644 components/contentstack/actions/update-entry/update-entry.mjs create mode 100644 components/contentstack/sources/new-asset-instant/new-asset-instant.mjs create mode 100644 components/contentstack/sources/new-entry-instant/new-entry-instant.mjs create mode 100644 components/contentstack/sources/publish-entry-instant/publish-entry-instant.mjs diff --git a/components/contentstack/actions/create-entry/create-entry.mjs b/components/contentstack/actions/create-entry/create-entry.mjs new file mode 100644 index 0000000000000..63ded977979fe --- /dev/null +++ b/components/contentstack/actions/create-entry/create-entry.mjs @@ -0,0 +1,49 @@ +import contentstack from "../../contentstack.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "contentstack-create-entry", + name: "Create Entry", + description: "Creates a new entry in Contentstack. [See the documentation]().", + version: "0.0.{{ts}}", + type: "action", + props: { + contentstack, + stackId: { + propDefinition: [ + contentstack, + "stackId", + ], + }, + contentTypeUid: { + propDefinition: [ + contentstack, + "contentTypeUid", + ], + }, + entryTitle: { + propDefinition: [ + contentstack, + "entryTitle", + ], + }, + content: { + propDefinition: [ + contentstack, + "content", + ], + }, + metadata: { + propDefinition: [ + contentstack, + "metadata", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.contentstack.createEntry(); + $.export("$summary", `Created entry "${response.title}" with UID ${response.uid}`); + return response; + }, +}; diff --git a/components/contentstack/actions/publish-entry/publish-entry.mjs b/components/contentstack/actions/publish-entry/publish-entry.mjs new file mode 100644 index 0000000000000..e19790199fb37 --- /dev/null +++ b/components/contentstack/actions/publish-entry/publish-entry.mjs @@ -0,0 +1,30 @@ +import contentstack from "../../contentstack.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "contentstack-publish-entry", + name: "Publish Entry", + description: "Publishes a specific entry using its UID. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + contentstack, + stackId: { + propDefinition: [ + contentstack, + "stackId", + ], + }, + entryUid: { + propDefinition: [ + contentstack, + "entryUid", + ], + }, + }, + async run({ $ }) { + const response = await this.contentstack.publishEntry(); + $.export("$summary", `Successfully published entry with UID ${this.entryUid}`); + return response; + }, +}; diff --git a/components/contentstack/actions/update-entry/update-entry.mjs b/components/contentstack/actions/update-entry/update-entry.mjs new file mode 100644 index 0000000000000..41d0d094c3cfd --- /dev/null +++ b/components/contentstack/actions/update-entry/update-entry.mjs @@ -0,0 +1,53 @@ +import contentstack from "../../contentstack.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "contentstack-update-entry", + name: "Update Entry", + description: "Updates an existing Contentstack entry. [See the documentation](https://www.contentstack.com/docs/developers/apis/content-management-api#update-an-entry).", + version: "0.0.{{ts}}", + type: "action", + props: { + contentstack, + stackId: { + propDefinition: [ + "contentstack", + "stackId", + ], + }, + contentTypeUid: { + propDefinition: [ + "contentstack", + "contentTypeUid", + ], + }, + entryUid: { + propDefinition: [ + "contentstack", + "entryUid", + ], + }, + fieldsToUpdate: { + propDefinition: [ + "contentstack", + "fieldsToUpdate", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.contentstack.updateEntry({ + data: this.fieldsToUpdate + ? this.fieldsToUpdate.reduce((acc, field) => { + const parsedField = JSON.parse(field); + return { + ...acc, + ...parsedField, + }; + }, {}) + : {}, + }); + $.export("$summary", `Entry ${this.entryUid} updated successfully`); + return response; + }, +}; diff --git a/components/contentstack/contentstack.app.mjs b/components/contentstack/contentstack.app.mjs index a308ec363e46d..27ceda5a5c5b7 100644 --- a/components/contentstack/contentstack.app.mjs +++ b/components/contentstack/contentstack.app.mjs @@ -1,11 +1,172 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "contentstack", - propDefinitions: {}, + version: "0.0.{{ts}}", + propDefinitions: { + stackId: { + type: "string", + label: "Stack ID", + description: "The ID of the Contentstack stack", + }, + assetId: { + type: "string", + label: "Asset ID", + description: "The ID of the asset", + }, + contentTypeUid: { + type: "string", + label: "Content Type UID", + description: "The UID of the content type for creating and listing entries", + }, + entryId: { + type: "string", + label: "Entry ID", + description: "The ID of the entry relevant to the published content", + }, + entryUid: { + type: "string", + label: "Entry UID", + description: "The UID of the entry to publish or update", + }, + entryTitle: { + type: "string", + label: "Entry Title", + description: "The title of the new entry", + }, + content: { + type: "string", + label: "Content", + description: "The content of the new entry", + }, + metadata: { + type: "string[]", + label: "Metadata", + description: "Array of metadata objects in JSON format", + optional: true, + }, + fieldsToUpdate: { + type: "string[]", + label: "Fields to Update", + description: "Array of fields to update with new values in JSON format", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.contentstack.com/v3"; + }, + async _makeRequest(opts = {}) { + const { + $, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + "api_key": this.$auth.api_key, + "access_token": this.$auth.access_token, + "Content-Type": "application/json", + }, + }); + }, + async listAssets(opts = {}) { + return this._makeRequest({ + path: `/stacks/${this.stackId}/assets`, + ...opts, + }); + }, + async getAsset(opts = {}) { + return this._makeRequest({ + path: `/stacks/${this.stackId}/assets/${this.assetId}`, + ...opts, + }); + }, + async listEntries(opts = {}) { + return this._makeRequest({ + path: `/stacks/${this.stackId}/content_types/${this.contentTypeUid}/entries`, + ...opts, + }); + }, + async getEntry(opts = {}) { + return this._makeRequest({ + path: `/stacks/${this.stackId}/entries/${this.entryId}`, + ...opts, + }); + }, + async createEntry(opts = {}) { + const data = { + title: this.entryTitle, + content: this.content, + ...(this.metadata + ? { + metadata: this.metadata.map(JSON.parse), + } + : {}), + ...opts.data, + }; + return this._makeRequest({ + method: "POST", + path: `/stacks/${this.stackId}/content_types/${this.contentTypeUid}/entries`, + data, + ...opts, + }); + }, + async updateEntry(opts = {}) { + const data = { + ...(this.fieldsToUpdate + ? this.fieldsToUpdate.reduce((acc, field) => { + const parsedField = JSON.parse(field); + return { + ...acc, + ...parsedField, + }; + }, {}) + : {}), + ...opts.data, + }; + return this._makeRequest({ + method: "PUT", + path: `/stacks/${this.stackId}/entries/${this.entryUid}`, + data, + ...opts, + }); + }, + async publishEntry(opts = {}) { + return this._makeRequest({ + method: "POST", + path: `/stacks/${this.stackId}/entries/${this.entryUid}/publish`, + ...opts, + }); + }, + async paginate(fn, ...opts) { + let allResults = []; + let currentPage = 1; + let hasMore = true; + while (hasMore) { + const response = await fn({ + page: currentPage, + ...opts, + }); + if (response.items && response.items.length > 0) { + allResults = [ + ...allResults, + ...response.items, + ]; + } + if (response.pagination && response.pagination.next_page) { + currentPage += 1; + } else { + hasMore = false; + } + } + return allResults; + }, }, -}; \ No newline at end of file +}; diff --git a/components/contentstack/package.json b/components/contentstack/package.json index 0bf37fc3a8828..5dd30623d80a7 100644 --- a/components/contentstack/package.json +++ b/components/contentstack/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/contentstack/sources/new-asset-instant/new-asset-instant.mjs b/components/contentstack/sources/new-asset-instant/new-asset-instant.mjs new file mode 100644 index 0000000000000..24eb2400e9a69 --- /dev/null +++ b/components/contentstack/sources/new-asset-instant/new-asset-instant.mjs @@ -0,0 +1,167 @@ +import contentstack from "../../contentstack.app.mjs"; +import crypto from "crypto"; +import { axios } from "@pipedream/platform"; + +export default { + key: "contentstack-new-asset-instant", + name: "New Asset Created", + description: "Emit a new event when a new asset is created in Contentstack. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + contentstack: { + type: "app", + app: "contentstack", + }, + stackId: { + propDefinition: [ + contentstack, + "stackId", + ], + }, + assetId: { + propDefinition: [ + contentstack, + "assetId", + ], + }, + secretKey: { + type: "string", + label: "Secret Key", + description: "Secret key for validating webhook signatures", + secret: true, + }, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + methods: { + _getWebhookId() { + return this.db.get("webhookId"); + }, + _setWebhookId(id) { + this.db.set("webhookId", id); + }, + async _createWebhook() { + const webhookData = { + name: "Pipedream Webhook for New Asset Creation", + url: this.http.endpoint, + events: [ + "asset.create", + ], + headers: { + "Content-Type": "application/json", + }, + }; + + const response = await this.contentstack._makeRequest({ + method: "POST", + path: `/stacks/${this.stackId}/webhooks`, + data: webhookData, + }); + + return response.uid; + }, + async _deleteWebhook(webhookId) { + await this.contentstack._makeRequest({ + method: "DELETE", + path: `/stacks/${this.stackId}/webhooks/${webhookId}`, + }); + }, + async _listRecentAssets() { + const assets = await this.contentstack.paginate(this.contentstack.listAssets, { + include_total_count: true, + limit: 50, + }); + return assets.slice(-50); + }, + }, + hooks: { + async activate() { + const webhookId = await this._createWebhook(); + this._setWebhookId(webhookId); + }, + async deactivate() { + const webhookId = this._getWebhookId(); + if (webhookId) { + await this._deleteWebhook(webhookId); + await this.db.delete("webhookId"); + } + }, + async deploy() { + const recentAssets = await this._listRecentAssets(); + for (const asset of recentAssets) { + this.$emit( + { + stackId: this.stackId, + assetId: asset.uid, + }, + { + id: `${this.stackId}-${asset.uid}`, + summary: `New asset created with ID: ${asset.uid}`, + ts: new Date(asset.created_at), + }, + ); + } + }, + }, + async run(event) { + const signature = event.headers["x-contentstack-signature"]; + const rawBody = JSON.stringify(event.body); + const computedSignature = crypto + .createHmac("sha256", this.secretKey) + .update(rawBody) + .digest("hex"); + + if (computedSignature !== signature) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + + const asset = event.body; + const assetId = asset.uid || asset.asset_uid || asset.asset_id; + const stackId = this.stackId; + + if (!assetId) { + this.$emit( + { + stackId, + assetId: "unknown", + }, + { + id: `${stackId}-unknown`, + summary: "New asset created with unknown ID", + ts: new Date(), + }, + ); + this.http.respond({ + status: 200, + body: "OK", + }); + return; + } + + this.$emit( + { + stackId, + assetId, + }, + { + id: `${stackId}-${assetId}`, + summary: `New asset created with ID: ${assetId}`, + ts: new Date(asset.created_at), + }, + ); + + this.http.respond({ + status: 200, + body: "OK", + }); + }, +}; diff --git a/components/contentstack/sources/new-entry-instant/new-entry-instant.mjs b/components/contentstack/sources/new-entry-instant/new-entry-instant.mjs new file mode 100644 index 0000000000000..a86a20223c4cb --- /dev/null +++ b/components/contentstack/sources/new-entry-instant/new-entry-instant.mjs @@ -0,0 +1,149 @@ +import contentstack from "../../contentstack.app.mjs"; +import crypto from "crypto"; +import { axios } from "@pipedream/platform"; + +export default { + key: "contentstack-new-entry-instant", + name: "New Contentstack Entry", + description: "Emit new event when a new entry is created in Contentstack. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + contentstack: { + type: "app", + app: "contentstack", + }, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + stackId: { + propDefinition: [ + contentstack, + "stackId", + ], + }, + contentTypeUid: { + propDefinition: [ + contentstack, + "contentTypeUid", + ], + }, + }, + methods: { + _getWebhookId() { + return this.db.get("webhookId"); + }, + _setWebhookId(id) { + this.db.set("webhookId", id); + }, + _getWebhookSecret() { + return this.db.get("webhookSecret"); + }, + _setWebhookSecret(secret) { + this.db.set("webhookSecret", secret); + }, + async createWebhook() { + const secret = crypto.randomBytes(32).toString("hex"); + const webhookData = { + name: "Pipedream Webhook", + url: this.http.url, + events: [ + "entry.create", + ], + secret: secret, + }; + const response = await this.contentstack._makeRequest({ + method: "POST", + path: `/stacks/${this.stackId}/hooks`, + data: webhookData, + }); + await this._setWebhookSecret(secret); + return response.uid; + }, + async deleteWebhook(webhookId) { + await this.contentstack._makeRequest({ + method: "DELETE", + path: `/stacks/${this.stackId}/hooks/${webhookId}`, + }); + }, + async fetchRecentEntries() { + const entries = await this.contentstack.paginate(this.contentstack.listEntries, { + contentTypeUid: this.contentTypeUid, + limit: 50, + sort: "-created_at", + }); + return entries; + }, + }, + hooks: { + async deploy() { + const entries = await this.fetchRecentEntries(); + for (const entry of entries) { + this.$emit( + { + stackId: entry.stack.uid, + entryId: entry.uid, + }, + { + id: entry.uid, + summary: `New entry created: ${entry.title}`, + ts: Date.parse(entry.created_at), + }, + ); + } + }, + async activate() { + const webhookId = await this.createWebhook(); + this._setWebhookId(webhookId); + }, + async deactivate() { + const webhookId = this._getWebhookId(); + if (webhookId) { + await this.deleteWebhook(webhookId); + this.db.delete("webhookId"); + this.db.delete("webhookSecret"); + } + }, + }, + async run(event) { + const secret = await this._getWebhookSecret(); + const signature = event.headers["x-contentstack-signature"]; + const computedSignature = crypto + .createHmac("sha256", secret) + .update(event.rawBody) + .digest("hex"); + + if (computedSignature !== signature) { + await this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + + const entry = event.body.entry; + const stackId = entry.stack.uid; + const entryId = entry.uid; + const timestamp = Date.parse(entry.created_at) || Date.now(); + + this.$emit( + { + stackId, + entryId, + }, + { + id: entryId, + summary: `New entry created: ${entry.title}`, + ts: timestamp, + }, + ); + + await this.http.respond({ + status: 200, + body: "OK", + }); + }, +}; diff --git a/components/contentstack/sources/publish-entry-instant/publish-entry-instant.mjs b/components/contentstack/sources/publish-entry-instant/publish-entry-instant.mjs new file mode 100644 index 0000000000000..2527a67030997 --- /dev/null +++ b/components/contentstack/sources/publish-entry-instant/publish-entry-instant.mjs @@ -0,0 +1,170 @@ +import crypto from "crypto"; +import contentstack from "../../contentstack.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "contentstack-publish-entry-instant", + name: "Contentstack - Entry Published", + description: "Emit new event when content is live on your website. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + contentstack, + stackId: { + propDefinition: [ + contentstack, + "stackId", + ], + }, + entryId: { + propDefinition: [ + contentstack, + "entryId", + ], + }, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + secret: { + type: "string", + label: "Webhook Secret", + description: "The secret used to validate webhook signatures", + secret: true, + optional: true, + }, + }, + methods: { + _getWebhookId() { + return this.db.get("webhookId"); + }, + _setWebhookId(id) { + this.db.set("webhookId", id); + }, + async createWebhook() { + const webhookName = `Pipedream Webhook - ${Date.now()}`; + const webhookUrl = this.http.endpoint; + + const webhookData = { + name: webhookName, + url: webhookUrl, + events: [ + "entry.publish", + ], + ...(this.secret + ? { + secret: this.secret, + } + : {}), + stack_uid: this.stackId, + }; + + const response = await this.contentstack._makeRequest({ + method: "POST", + path: `/stacks/${this.stackId}/webhooks`, + data: webhookData, + }); + + const webhookId = response.uid; + this._setWebhookId(webhookId); + }, + async deleteWebhook() { + const webhookId = this._getWebhookId(); + + if (webhookId) { + await this.contentstack._makeRequest({ + method: "DELETE", + path: `/stacks/${this.stackId}/webhooks/${webhookId}`, + }); + } + }, + async listRecentEntries() { + const query = JSON.stringify({ + uid: this.entryId, + locale: "*", + }); + + const entries = await this.contentstack.paginate(this.contentstack.listEntries, { + query, + }); + + return entries; + }, + validateSignature(payload, signature) { + if (!this.secret) return true; + const hash = crypto.createHmac("sha256", this.secret).update(payload) + .digest("base64"); + return hash === signature; + }, + }, + hooks: { + async deploy() { + const entries = await this.listRecentEntries(); + + const recentEntries = entries.slice(-50); + for (const entry of recentEntries) { + this.$emit( + { + stackId: this.stackId, + entryId: entry.uid, + }, + { + id: entry.uid, + summary: `Entry published: ${entry.title}`, + ts: Date.parse(entry.publish_details.livesite_at) || Date.now(), + }, + ); + } + }, + async activate() { + await this.createWebhook(); + }, + async deactivate() { + await this.deleteWebhook(); + }, + }, + async run(event) { + const signature = event.headers["x-contentstack-signature"]; + const rawBody = JSON.stringify(event.body); + + if (this.secret) { + const isValid = this.validateSignature(rawBody, signature); + if (!isValid) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + } + + const entry = event.body.entry; + + if (entry.uid !== this.entryId) { + this.http.respond({ + status: 200, + body: "OK", + }); + return; + } + + this.$emit( + { + stackId: this.stackId, + entryId: entry.uid, + }, + { + id: entry.uid, + summary: `Entry published in stack ${this.stackId}`, + ts: Date.parse(entry.publish_details.livesite_at) || Date.now(), + }, + ); + + this.http.respond({ + status: 200, + body: "OK", + }); + }, +}; From 67192f5ced8061f25601bd3edc3cc593428abace Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Fri, 20 Dec 2024 14:59:38 -0500 Subject: [PATCH 2/4] new components --- .../contentstack/actions/common/entries.mjs | 127 +++++ .../actions/create-entry/create-entry.mjs | 54 +- .../actions/publish-entry/publish-entry.mjs | 61 ++- .../actions/update-entry/update-entry.mjs | 61 +-- components/contentstack/common/utils.mjs | 41 ++ components/contentstack/contentstack.app.mjs | 255 +++++---- components/contentstack/package.json | 5 +- .../contentstack/sources/common/base.mjs | 76 +++ .../new-asset-instant/new-asset-instant.mjs | 176 +------ .../sources/new-asset-instant/test-event.mjs | 30 ++ .../new-entry-instant/new-entry-instant.mjs | 158 +----- .../sources/new-entry-instant/test-event.mjs | 496 ++++++++++++++++++ .../publish-entry-instant.mjs | 179 +------ .../publish-entry-instant/test-event.mjs | 452 ++++++++++++++++ 14 files changed, 1525 insertions(+), 646 deletions(-) create mode 100644 components/contentstack/actions/common/entries.mjs create mode 100644 components/contentstack/common/utils.mjs create mode 100644 components/contentstack/sources/common/base.mjs create mode 100644 components/contentstack/sources/new-asset-instant/test-event.mjs create mode 100644 components/contentstack/sources/new-entry-instant/test-event.mjs create mode 100644 components/contentstack/sources/publish-entry-instant/test-event.mjs diff --git a/components/contentstack/actions/common/entries.mjs b/components/contentstack/actions/common/entries.mjs new file mode 100644 index 0000000000000..275315575ffa1 --- /dev/null +++ b/components/contentstack/actions/common/entries.mjs @@ -0,0 +1,127 @@ +import contentstack from "../../contentstack.app.mjs"; +import { + parseArray, parseEntry, +} from "../../common/utils.mjs"; + +const createDocLink = "https://www.contentstack.com/docs/developers/apis/content-management-api#create-an-entry"; +const updateDocLink = "https://www.contentstack.com/docs/developers/apis/content-management-api#update-an-entry"; + +export default { + props: { + contentstack, + contentType: { + propDefinition: [ + contentstack, + "contentType", + ], + reloadProps: true, + }, + }, + async additionalProps() { + if (!this.contentType) { + return {}; + } + try { + return await this.buildFieldProps(this.contentType); + } catch { + return { + entryObj: { + type: "object", + label: "Entry", + description: `Enter the entry object as JSON. [See the documentation](${this.isUpdate() + ? updateDocLink + : createDocLink}) for more information.`, + }, + }; + } + }, + methods: { + getType(field) { + if (field.data_type === "boolean") { + return "boolean"; + } + if (field.data_type === "json") { + return "object"; + } + return field.multiple + ? "string[]" + : "string"; + }, + isUpdate() { + return false; + }, + async getOptions(field) { + if (field.data_type === "reference") { + const referenceContentType = field.reference_to[0]; + const { entries } = await this.contentstack.listEntries({ + contentType: referenceContentType, + }); + return entries?.map(({ + uid: value, title: label, + }) => ({ + value, + label: label ?? value, + })) || []; + } + if (field.data_type === "file") { + const { assets } = await this.contentstack.listAssets(); + return assets?.map(({ + uid: value, title: label, + }) => ({ + value, + label: label ?? value, + })) || []; + } + return undefined; + }, + async buildFieldProps(contentType) { + const props = {}; + const { content_type: { schema } } = await this.contentstack.getContentType({ + contentType, + }); + for (const field of schema) { + props[field.uid] = { + type: this.getType(field), + label: field.display_name ?? field.uid, + description: `Value of field ${field.display_name}. Field type: \`${field.data_type}\``, + optional: this.isUpdate() + ? true + : !field.mandatory, + options: await this.getOptions(field), + }; + } + return props; + }, + async buildEntry() { + if (this.entryObj) { + return parseEntry(this.entryObj); + } + const { content_type: { schema } } = await this.contentstack.getContentType({ + contentType: this.contentType, + }); + const entry = {}; + for (const field of schema) { + if (!this[field.uid]) { + continue; + } + if (field.data_type === "reference") { + if (field.multiple) { + const referenceField = parseArray(this[field.uid]); + entry[field.uid] = referenceField?.map((value) => ({ + uid: value, + _content_type_uid: field.reference_to[0], + })); + } else { + entry[field.uid] = { + uid: this[field.uid], + _content_type_uid: field.reference_to[0], + }; + } + continue; + } + entry[field.uid] = this[field.uid]; + } + return parseEntry(entry); + }, + }, +}; diff --git a/components/contentstack/actions/create-entry/create-entry.mjs b/components/contentstack/actions/create-entry/create-entry.mjs index 63ded977979fe..3baa5263bc97c 100644 --- a/components/contentstack/actions/create-entry/create-entry.mjs +++ b/components/contentstack/actions/create-entry/create-entry.mjs @@ -1,49 +1,33 @@ -import contentstack from "../../contentstack.app.mjs"; -import { axios } from "@pipedream/platform"; +import common from "../common/entries.mjs"; export default { + ...common, key: "contentstack-create-entry", name: "Create Entry", - description: "Creates a new entry in Contentstack. [See the documentation]().", - version: "0.0.{{ts}}", + description: "Creates a new entry in Contentstack. [See the documentation](https://www.contentstack.com/docs/developers/apis/content-management-api#create-an-entry).", + version: "0.0.1", type: "action", props: { - contentstack, - stackId: { + ...common.props, + locale: { propDefinition: [ - contentstack, - "stackId", + common.props.contentstack, + "locale", ], }, - contentTypeUid: { - propDefinition: [ - contentstack, - "contentTypeUid", - ], - }, - entryTitle: { - propDefinition: [ - contentstack, - "entryTitle", - ], - }, - content: { - propDefinition: [ - contentstack, - "content", - ], - }, - metadata: { - propDefinition: [ - contentstack, - "metadata", - ], - optional: true, - }, }, async run({ $ }) { - const response = await this.contentstack.createEntry(); - $.export("$summary", `Created entry "${response.title}" with UID ${response.uid}`); + const response = await this.contentstack.createEntry({ + $, + contentType: this.contentType, + params: { + locale: this.locale, + }, + data: { + entry: await this.buildEntry(), + }, + }); + $.export("$summary", `Created entry "${response.entry.title}" with UID ${response.entry.uid}`); return response; }, }; diff --git a/components/contentstack/actions/publish-entry/publish-entry.mjs b/components/contentstack/actions/publish-entry/publish-entry.mjs index e19790199fb37..cb4d954e8e100 100644 --- a/components/contentstack/actions/publish-entry/publish-entry.mjs +++ b/components/contentstack/actions/publish-entry/publish-entry.mjs @@ -1,30 +1,73 @@ import contentstack from "../../contentstack.app.mjs"; -import { axios } from "@pipedream/platform"; +import { parseArray } from "../../common/utils.mjs"; export default { key: "contentstack-publish-entry", name: "Publish Entry", - description: "Publishes a specific entry using its UID. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Publishes a specific entry using its UID. [See the documentation](https://www.contentstack.com/docs/developers/apis/content-management-api#publish-entry)", + version: "0.0.1", type: "action", props: { contentstack, - stackId: { + contentType: { propDefinition: [ contentstack, - "stackId", + "contentType", ], }, - entryUid: { + entryId: { propDefinition: [ contentstack, - "entryUid", + "entryId", + (c) => ({ + contentType: c.contentType, + }), ], }, + environments: { + propDefinition: [ + contentstack, + "environments", + ], + }, + locales: { + propDefinition: [ + contentstack, + "locale", + ], + type: "string[]", + label: "Locale", + description: "The code of the language in which you want your entry to be localized in", + }, + scheduledAt: { + type: "string", + label: "Scheduled At", + description: "The date/time in the ISO format to publish the entry. Example: `2016-10-07T12:34:36.000Z`", + optional: true, + }, }, async run({ $ }) { - const response = await this.contentstack.publishEntry(); - $.export("$summary", `Successfully published entry with UID ${this.entryUid}`); + const { entry } = await this.contentstack.getEntry({ + $, + contentType: this.contentType, + entryId: this.entryId, + }); + + const response = await this.contentstack.publishEntry({ + $, + contentType: this.contentType, + entryId: this.entryId, + data: { + entry: { + environments: parseArray(this.environments), + locales: parseArray(this.locales), + }, + locale: entry.locale, + version: entry._version, + scheduled_at: this.scheduledAt, + }, + }); + $.export("$summary", `Successfully published entry with UID ${this.entryId}`); return response; }, }; diff --git a/components/contentstack/actions/update-entry/update-entry.mjs b/components/contentstack/actions/update-entry/update-entry.mjs index 41d0d094c3cfd..66c3e50450e9d 100644 --- a/components/contentstack/actions/update-entry/update-entry.mjs +++ b/components/contentstack/actions/update-entry/update-entry.mjs @@ -1,53 +1,50 @@ -import contentstack from "../../contentstack.app.mjs"; -import { axios } from "@pipedream/platform"; +import common from "../common/entries.mjs"; export default { + ...common, key: "contentstack-update-entry", name: "Update Entry", description: "Updates an existing Contentstack entry. [See the documentation](https://www.contentstack.com/docs/developers/apis/content-management-api#update-an-entry).", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { - contentstack, - stackId: { + ...common.props, + entryId: { propDefinition: [ - "contentstack", - "stackId", + common.props.contentstack, + "entryId", + (c) => ({ + contentType: c.contentType, + }), ], }, - contentTypeUid: { + locale: { propDefinition: [ - "contentstack", - "contentTypeUid", - ], - }, - entryUid: { - propDefinition: [ - "contentstack", - "entryUid", - ], - }, - fieldsToUpdate: { - propDefinition: [ - "contentstack", - "fieldsToUpdate", + common.props.contentstack, + "locale", ], optional: true, }, }, + methods: { + ...common.methods, + isUpdate() { + return true; + }, + }, async run({ $ }) { const response = await this.contentstack.updateEntry({ - data: this.fieldsToUpdate - ? this.fieldsToUpdate.reduce((acc, field) => { - const parsedField = JSON.parse(field); - return { - ...acc, - ...parsedField, - }; - }, {}) - : {}, + $, + contentType: this.contentType, + entryId: this.entryId, + params: { + locale: this.locale, + }, + data: { + entry: await this.buildEntry(), + }, }); - $.export("$summary", `Entry ${this.entryUid} updated successfully`); + $.export("$summary", `Entry ${this.entryId} updated successfully`); return response; }, }; diff --git a/components/contentstack/common/utils.mjs b/components/contentstack/common/utils.mjs new file mode 100644 index 0000000000000..269cef5aea399 --- /dev/null +++ b/components/contentstack/common/utils.mjs @@ -0,0 +1,41 @@ +export function parseArray(value) { + if (!value) { + return undefined; + } + if (typeof value === "string") { + try { + return JSON.parse(value); + } catch { + throw new Error(`Could not parse as array: ${value}`); + } + } + return value; +} + +export function parseEntry(entry) { + if (!entry) { + return undefined; + } + if (typeof entry === "string") { + try { + return JSON.parse(entry); + } catch { + throw new Error("Could not parse entry as JSON"); + } + } + const parsedEntry = {}; + for (const [ + key, + value, + ] of Object.entries(entry)) { + if (typeof value === "string") { + try { + parsedEntry[key] = JSON.parse(value); + } catch { + parsedEntry[key] = value; + } + } + parsedEntry[key] = value; + } + return parsedEntry; +} diff --git a/components/contentstack/contentstack.app.mjs b/components/contentstack/contentstack.app.mjs index 27ceda5a5c5b7..cda7523cc87ae 100644 --- a/components/contentstack/contentstack.app.mjs +++ b/components/contentstack/contentstack.app.mjs @@ -1,172 +1,201 @@ import { axios } from "@pipedream/platform"; +const DEFAULT_LIMIT = 20; export default { type: "app", app: "contentstack", - version: "0.0.{{ts}}", propDefinitions: { - stackId: { - type: "string", - label: "Stack ID", - description: "The ID of the Contentstack stack", - }, - assetId: { - type: "string", - label: "Asset ID", - description: "The ID of the asset", + branchIds: { + type: "string[]", + label: "Branches", + description: "An array of branch identifiers", + async options({ page }) { + const { branches } = await this.listBranches({ + params: { + limit: DEFAULT_LIMIT, + skip: page * DEFAULT_LIMIT, + }, + }); + return branches?.map(({ uid }) => uid) || []; + }, }, - contentTypeUid: { + contentType: { type: "string", - label: "Content Type UID", + label: "Content Type", description: "The UID of the content type for creating and listing entries", + async options() { + const { content_types: contentTypes } = await this.listContentTypes(); + return contentTypes?.map(({ + uid: value, title: label, + }) => ({ + value, + label, + })) || []; + }, }, entryId: { type: "string", label: "Entry ID", - description: "The ID of the entry relevant to the published content", - }, - entryUid: { - type: "string", - label: "Entry UID", description: "The UID of the entry to publish or update", - }, - entryTitle: { - type: "string", - label: "Entry Title", - description: "The title of the new entry", - }, - content: { - type: "string", - label: "Content", - description: "The content of the new entry", - }, - metadata: { - type: "string[]", - label: "Metadata", - description: "Array of metadata objects in JSON format", - optional: true, - }, - fieldsToUpdate: { + async options({ contentType }) { + const { entries } = await this.listEntries({ + contentType, + }); + return entries?.map(({ + uid: value, title: label, + }) => ({ + value, + label, + })) || []; + }, + }, + environments: { type: "string[]", - label: "Fields to Update", - description: "Array of fields to update with new values in JSON format", - optional: true, + label: "Environments", + description: "The UIDs of the environments to which you want to publish the entry", + async options() { + const { environments } = await this.listEnvironments(); + return environments?.map(({ name }) => name ) || []; + }, + }, + locale: { + type: "string", + label: "Locale", + description: "The code of the language in which you want your entry to be localized in", + async options() { + const { locales } = await this.listLocales(); + return locales?.map(({ + code: value, name: label, + }) => ({ + value, + label, + })) || []; + }, }, }, methods: { - authKeys() { - console.log(Object.keys(this.$auth)); - }, _baseUrl() { - return "https://api.contentstack.com/v3"; - }, - async _makeRequest(opts = {}) { - const { - $, method = "GET", path = "/", headers, ...otherOpts - } = opts; + return `${this.$auth.region}api.contentstack.${this.$auth.region === "https://" + ? "io" + : "com"}/v3`; + }, + _makeRequest({ + $ = this, + path, + ...otherOpts + }) { return axios($, { ...otherOpts, - method, - url: this._baseUrl() + path, + url: `${this._baseUrl()}${path}`, headers: { - ...headers, - "api_key": this.$auth.api_key, - "access_token": this.$auth.access_token, - "Content-Type": "application/json", + "api_key": `${this.$auth.stack_api_key}`, + "authorization": `${this.$auth.management_token}`, + "content-type": "application/json", }, }); }, - async listAssets(opts = {}) { + createWebhook(opts = {}) { return this._makeRequest({ - path: `/stacks/${this.stackId}/assets`, + method: "POST", + path: "/webhooks", ...opts, }); }, - async getAsset(opts = {}) { + deleteWebhook({ + webhookId, ...opts + }) { return this._makeRequest({ - path: `/stacks/${this.stackId}/assets/${this.assetId}`, + method: "DELETE", + path: `/webhooks/${webhookId}`, + ...opts, + }); + }, + listBranches(opts = {}) { + return this._makeRequest({ + path: "/stacks/branches", ...opts, }); }, - async listEntries(opts = {}) { + listContentTypes(opts = {}) { return this._makeRequest({ - path: `/stacks/${this.stackId}/content_types/${this.contentTypeUid}/entries`, + path: "/content_types", ...opts, }); }, - async getEntry(opts = {}) { + listEntries({ + contentType, ...opts + }) { return this._makeRequest({ - path: `/stacks/${this.stackId}/entries/${this.entryId}`, + path: `/content_types/${contentType}/entries`, ...opts, }); }, - async createEntry(opts = {}) { - const data = { - title: this.entryTitle, - content: this.content, - ...(this.metadata - ? { - metadata: this.metadata.map(JSON.parse), - } - : {}), - ...opts.data, - }; + listEnvironments(opts = {}) { + return this._makeRequest({ + path: "/environments", + ...opts, + }); + }, + listLocales(opts = {}) { + return this._makeRequest({ + path: "/locales", + ...opts, + }); + }, + getEntry({ + contentType, entryId, ...opts + }) { + return this._makeRequest({ + path: `/content_types/${contentType}/entries/${entryId}`, + ...opts, + }); + }, + getContentType({ + contentType, ...opts + }) { + return this._makeRequest({ + path: `/content_types/${contentType}`, + ...opts, + }); + }, + createEntry({ + contentType, ...opts + }) { return this._makeRequest({ method: "POST", - path: `/stacks/${this.stackId}/content_types/${this.contentTypeUid}/entries`, - data, + path: `/content_types/${contentType}/entries`, ...opts, }); }, - async updateEntry(opts = {}) { - const data = { - ...(this.fieldsToUpdate - ? this.fieldsToUpdate.reduce((acc, field) => { - const parsedField = JSON.parse(field); - return { - ...acc, - ...parsedField, - }; - }, {}) - : {}), - ...opts.data, - }; + updateEntry({ + contentType, entryId, ...opts + }) { return this._makeRequest({ method: "PUT", - path: `/stacks/${this.stackId}/entries/${this.entryUid}`, - data, + path: `/content_types/${contentType}/entries/${entryId}`, ...opts, }); }, - async publishEntry(opts = {}) { + publishEntry({ + contentType, entryId, ...opts + }) { return this._makeRequest({ method: "POST", - path: `/stacks/${this.stackId}/entries/${this.entryUid}/publish`, + path: `/content_types/${contentType}/entries/${entryId}/publish`, ...opts, }); }, - async paginate(fn, ...opts) { - let allResults = []; - let currentPage = 1; - let hasMore = true; - while (hasMore) { - const response = await fn({ - page: currentPage, - ...opts, - }); - if (response.items && response.items.length > 0) { - allResults = [ - ...allResults, - ...response.items, - ]; - } - if (response.pagination && response.pagination.next_page) { - currentPage += 1; - } else { - hasMore = false; - } - } - return allResults; + listAssets(opts = {}) { + return this._makeRequest({ + path: "/assets", + ...opts, + }); + }, + async getAsset(opts = {}) { + return this._makeRequest({ + path: `/stacks/${this.stackId}/assets/${this.assetId}`, + ...opts, + }); }, }, }; diff --git a/components/contentstack/package.json b/components/contentstack/package.json index 5dd30623d80a7..38810b88c51d5 100644 --- a/components/contentstack/package.json +++ b/components/contentstack/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/contentstack", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Contentstack Components", "main": "contentstack.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/contentstack/sources/common/base.mjs b/components/contentstack/sources/common/base.mjs new file mode 100644 index 0000000000000..a683ebae1c8c7 --- /dev/null +++ b/components/contentstack/sources/common/base.mjs @@ -0,0 +1,76 @@ +import contentstack from "../../contentstack.app.mjs"; + +export default { + props: { + contentstack, + db: "$.service.db", + http: "$.interface.http", + name: { + type: "string", + label: "Webhook Name", + description: "Name of the webhook", + }, + branchIds: { + propDefinition: [ + contentstack, + "branchIds", + ], + }, + }, + hooks: { + async activate() { + const { + webhook, notice, + } = await this.contentstack.createWebhook({ + data: { + webhook: { + name: this.name, + destinations: [ + { + target_url: this.http.endpoint, + authentication_type: "None", + }, + ], + channels: this.getChannels(), + branches: this.branchIds, + disabled: false, + concise_payload: false, + retry_policy: "automatic", + }, + }, + }); + console.log(notice); + this._setHookId(webhook.uid); + }, + async deactivate() { + const webhookId = this._getHookId(); + if (webhookId) { + await this.contentstack.deleteWebhook({ + webhookId, + }); + } + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + getChannels() { + throw new Error("getChannels is not implemented"); + }, + generateMeta() { + throw new Error("generateMeta is not implemented"); + }, + }, + async run(event) { + const { body } = event; + if (!body) { + return; + } + const meta = this.generateMeta(body); + this.$emit(body, meta); + }, +}; diff --git a/components/contentstack/sources/new-asset-instant/new-asset-instant.mjs b/components/contentstack/sources/new-asset-instant/new-asset-instant.mjs index 24eb2400e9a69..e95f8e0063fc3 100644 --- a/components/contentstack/sources/new-asset-instant/new-asset-instant.mjs +++ b/components/contentstack/sources/new-asset-instant/new-asset-instant.mjs @@ -1,167 +1,29 @@ -import contentstack from "../../contentstack.app.mjs"; -import crypto from "crypto"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "contentstack-new-asset-instant", - name: "New Asset Created", - description: "Emit a new event when a new asset is created in Contentstack. [See the documentation]()", - version: "0.0.{{ts}}", + name: "New Asset Created (Instant)", + description: "Emit new event when a new asset is created in ContentStack.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - contentstack: { - type: "app", - app: "contentstack", - }, - stackId: { - propDefinition: [ - contentstack, - "stackId", - ], - }, - assetId: { - propDefinition: [ - contentstack, - "assetId", - ], - }, - secretKey: { - type: "string", - label: "Secret Key", - description: "Secret key for validating webhook signatures", - secret: true, - }, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - }, methods: { - _getWebhookId() { - return this.db.get("webhookId"); - }, - _setWebhookId(id) { - this.db.set("webhookId", id); - }, - async _createWebhook() { - const webhookData = { - name: "Pipedream Webhook for New Asset Creation", - url: this.http.endpoint, - events: [ - "asset.create", - ], - headers: { - "Content-Type": "application/json", - }, + ...common.methods, + getChannels() { + return [ + "assets.create", + ]; + }, + generateMeta(event) { + const id = event.data.asset.uid; + return { + id, + summary: `New asset created with ID: ${id}`, + ts: Date.parse(event.triggered_at), }; - - const response = await this.contentstack._makeRequest({ - method: "POST", - path: `/stacks/${this.stackId}/webhooks`, - data: webhookData, - }); - - return response.uid; - }, - async _deleteWebhook(webhookId) { - await this.contentstack._makeRequest({ - method: "DELETE", - path: `/stacks/${this.stackId}/webhooks/${webhookId}`, - }); }, - async _listRecentAssets() { - const assets = await this.contentstack.paginate(this.contentstack.listAssets, { - include_total_count: true, - limit: 50, - }); - return assets.slice(-50); - }, - }, - hooks: { - async activate() { - const webhookId = await this._createWebhook(); - this._setWebhookId(webhookId); - }, - async deactivate() { - const webhookId = this._getWebhookId(); - if (webhookId) { - await this._deleteWebhook(webhookId); - await this.db.delete("webhookId"); - } - }, - async deploy() { - const recentAssets = await this._listRecentAssets(); - for (const asset of recentAssets) { - this.$emit( - { - stackId: this.stackId, - assetId: asset.uid, - }, - { - id: `${this.stackId}-${asset.uid}`, - summary: `New asset created with ID: ${asset.uid}`, - ts: new Date(asset.created_at), - }, - ); - } - }, - }, - async run(event) { - const signature = event.headers["x-contentstack-signature"]; - const rawBody = JSON.stringify(event.body); - const computedSignature = crypto - .createHmac("sha256", this.secretKey) - .update(rawBody) - .digest("hex"); - - if (computedSignature !== signature) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - - const asset = event.body; - const assetId = asset.uid || asset.asset_uid || asset.asset_id; - const stackId = this.stackId; - - if (!assetId) { - this.$emit( - { - stackId, - assetId: "unknown", - }, - { - id: `${stackId}-unknown`, - summary: "New asset created with unknown ID", - ts: new Date(), - }, - ); - this.http.respond({ - status: 200, - body: "OK", - }); - return; - } - - this.$emit( - { - stackId, - assetId, - }, - { - id: `${stackId}-${assetId}`, - summary: `New asset created with ID: ${assetId}`, - ts: new Date(asset.created_at), - }, - ); - - this.http.respond({ - status: 200, - body: "OK", - }); }, + sampleEmit, }; diff --git a/components/contentstack/sources/new-asset-instant/test-event.mjs b/components/contentstack/sources/new-asset-instant/test-event.mjs new file mode 100644 index 0000000000000..e69a7a20a3f15 --- /dev/null +++ b/components/contentstack/sources/new-asset-instant/test-event.mjs @@ -0,0 +1,30 @@ +export default { + "module": "asset", + "api_key": "blt6e39cdfaac74824c", + "data": { + "asset": { + "uid": "blt81f45c9d5c3103de", + "created_at": "2024-12-19T20:55:53.701Z", + "updated_at": "2024-12-19T20:55:53.701Z", + "created_by": "cs52122cc4ac22f20e", + "updated_by": "cs52122cc4ac22f20e", + "content_type": "image/jpeg", + "file_size": "463713", + "tags": [], + "filename": "george.jpg", + "url": "https://images.contentstack.io/v3/assets/blt6e39cdfaac74824c/blt81f45c9d5c3103de/67648859989c5367eed4ffdb/george.jpg", + "ACL": {}, + "is_dir": false, + "parent_uid": null, + "_version": 1, + "title": "george.jpg" + }, + "branch": { + "uid": "main", + "source": "", + "alias": [] + } + }, + "event": "create", + "triggered_at": "2024-12-19T20:55:53.977Z" +} \ No newline at end of file diff --git a/components/contentstack/sources/new-entry-instant/new-entry-instant.mjs b/components/contentstack/sources/new-entry-instant/new-entry-instant.mjs index a86a20223c4cb..64cda26665589 100644 --- a/components/contentstack/sources/new-entry-instant/new-entry-instant.mjs +++ b/components/contentstack/sources/new-entry-instant/new-entry-instant.mjs @@ -1,149 +1,29 @@ -import contentstack from "../../contentstack.app.mjs"; -import crypto from "crypto"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "contentstack-new-entry-instant", - name: "New Contentstack Entry", - description: "Emit new event when a new entry is created in Contentstack. [See the documentation]()", - version: "0.0.{{ts}}", + name: "New Entry Created (Instant)", + description: "Emit new event when a new entry is created in ContentStack.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - contentstack: { - type: "app", - app: "contentstack", - }, - db: "$.service.db", - http: { - type: "$.interface.http", - customResponse: true, - }, - stackId: { - propDefinition: [ - contentstack, - "stackId", - ], - }, - contentTypeUid: { - propDefinition: [ - contentstack, - "contentTypeUid", - ], - }, - }, methods: { - _getWebhookId() { - return this.db.get("webhookId"); - }, - _setWebhookId(id) { - this.db.set("webhookId", id); - }, - _getWebhookSecret() { - return this.db.get("webhookSecret"); - }, - _setWebhookSecret(secret) { - this.db.set("webhookSecret", secret); - }, - async createWebhook() { - const secret = crypto.randomBytes(32).toString("hex"); - const webhookData = { - name: "Pipedream Webhook", - url: this.http.url, - events: [ - "entry.create", - ], - secret: secret, + ...common.methods, + getChannels() { + return [ + "content_types.entries.create", + ]; + }, + generateMeta(event) { + const id = event.data.entry.uid; + return { + id, + summary: `New entity created with ID: ${id}`, + ts: Date.parse(event.triggered_at), }; - const response = await this.contentstack._makeRequest({ - method: "POST", - path: `/stacks/${this.stackId}/hooks`, - data: webhookData, - }); - await this._setWebhookSecret(secret); - return response.uid; - }, - async deleteWebhook(webhookId) { - await this.contentstack._makeRequest({ - method: "DELETE", - path: `/stacks/${this.stackId}/hooks/${webhookId}`, - }); }, - async fetchRecentEntries() { - const entries = await this.contentstack.paginate(this.contentstack.listEntries, { - contentTypeUid: this.contentTypeUid, - limit: 50, - sort: "-created_at", - }); - return entries; - }, - }, - hooks: { - async deploy() { - const entries = await this.fetchRecentEntries(); - for (const entry of entries) { - this.$emit( - { - stackId: entry.stack.uid, - entryId: entry.uid, - }, - { - id: entry.uid, - summary: `New entry created: ${entry.title}`, - ts: Date.parse(entry.created_at), - }, - ); - } - }, - async activate() { - const webhookId = await this.createWebhook(); - this._setWebhookId(webhookId); - }, - async deactivate() { - const webhookId = this._getWebhookId(); - if (webhookId) { - await this.deleteWebhook(webhookId); - this.db.delete("webhookId"); - this.db.delete("webhookSecret"); - } - }, - }, - async run(event) { - const secret = await this._getWebhookSecret(); - const signature = event.headers["x-contentstack-signature"]; - const computedSignature = crypto - .createHmac("sha256", secret) - .update(event.rawBody) - .digest("hex"); - - if (computedSignature !== signature) { - await this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - - const entry = event.body.entry; - const stackId = entry.stack.uid; - const entryId = entry.uid; - const timestamp = Date.parse(entry.created_at) || Date.now(); - - this.$emit( - { - stackId, - entryId, - }, - { - id: entryId, - summary: `New entry created: ${entry.title}`, - ts: timestamp, - }, - ); - - await this.http.respond({ - status: 200, - body: "OK", - }); }, + sampleEmit, }; diff --git a/components/contentstack/sources/new-entry-instant/test-event.mjs b/components/contentstack/sources/new-entry-instant/test-event.mjs new file mode 100644 index 0000000000000..4db3414817b50 --- /dev/null +++ b/components/contentstack/sources/new-entry-instant/test-event.mjs @@ -0,0 +1,496 @@ +export default { + "module": "entry", + "api_key": "blt6e39cdfaac74824c", + "data": { + "entry": { + "title": "test article", + "url": "/article/test-article", + "cover_image": { + "uid": "blt81f45c9d5c3103de", + "created_at": "2024-12-19T20:55:53.701Z", + "updated_at": "2024-12-19T20:55:53.701Z", + "created_by": "cs52122cc4ac22f20e", + "updated_by": "cs52122cc4ac22f20e", + "content_type": "image/jpeg", + "file_size": "463713", + "tags": [], + "filename": "george.jpg", + "url": "https://images.contentstack.io/v3/assets/blt6e39cdfaac74824c/blt81f45c9d5c3103de/67648859989c5367eed4ffdb/george.jpg", + "ACL": [], + "is_dir": false, + "parent_uid": null, + "_version": 1, + "title": "george.jpg" + }, + "summary": "Summary", + "taxonomies": [], + "content": { + "type": "doc", + "attrs": {}, + "uid": "d05b26fe64214bca883e5e31eb11090c", + "children": [ + { + "type": "p", + "attrs": {}, + "uid": "5a4f78e3731748d284c83d93ecd794a3", + "children": [ + { + "text": "" + } + ] + } + ], + "_version": 1 + }, + "show_related_links": false, + "related_links": { + "text": "Related Links" + }, + "show_related_articles": false, + "related_articles": { + "heading": "Related Headline", + "sub_heading": "Related Subhead", + "number_of_articles": 6 + }, + "seo": { + "title": "Title", + "description": "Description", + "canonical_url": "/", + "no_index": true, + "no_follow": true + }, + "tags": [], + "locale": "en", + "uid": "blt6b0da76c39f34851", + "created_by": "cs52122cc4ac22f20e", + "updated_by": "cs52122cc4ac22f20e", + "created_at": "2024-12-19T21:29:49.303Z", + "updated_at": "2024-12-19T21:29:49.303Z", + "ACL": {}, + "_version": 1, + "_in_progress": false + }, + "content_type": { + "created_at": "2024-11-30T03:52:14.855Z", + "created_by": "bltce9401ba486d3c23", + "updated_at": "2024-11-30T03:54:29.605Z", + "updated_by": "bltce9401ba486d3c23", + "title": "Article", + "uid": "article", + "description": "", + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "placeholder": "Title", + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "multiple": false, + "non_localizable": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "text", + "display_name": "URL", + "uid": "url", + "field_metadata": { + "_default": true, + "version": 3 + }, + "multiple": false, + "unique": false, + "mandatory": false, + "non_localizable": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "file", + "display_name": "Cover image", + "uid": "cover_image", + "field_metadata": { + "description": "", + "rich_text_type": "standard", + "image": true + }, + "mandatory": true, + "multiple": false, + "non_localizable": false, + "unique": false, + "dimension": { + "width": { + "min": null, + "max": null + }, + "height": { + "min": null, + "max": null + } + }, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "text", + "display_name": "Summary", + "uid": "summary", + "field_metadata": { + "description": "", + "default_value": "Summary", + "multiline": true, + "placeholder": "Summary", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "taxonomy", + "display_name": "Taxonomy", + "uid": "taxonomies", + "taxonomies": [ + { + "taxonomy_uid": "region", + "mandatory": false, + "multiple": true, + "non_localizable": false + }, + { + "taxonomy_uid": "topic", + "mandatory": false, + "multiple": true, + "non_localizable": false + } + ], + "field_metadata": { + "description": "", + "default_value": "" + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": true, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "json", + "display_name": "Content", + "uid": "content", + "field_metadata": { + "allow_json_rte": true, + "embed_entry": false, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "reference_to": [ + "sys_assets" + ], + "multiple": false, + "non_localizable": false, + "unique": false, + "mandatory": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "boolean", + "display_name": "Show Related Links", + "uid": "show_related_links", + "field_metadata": { + "description": "", + "default_value": false + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "group", + "display_name": "Related Links", + "field_metadata": { + "description": "", + "instruction": "" + }, + "schema": [ + { + "data_type": "text", + "display_name": "Text", + "uid": "text", + "field_metadata": { + "description": "", + "default_value": "Related Links", + "placeholder": "Text", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + } + ], + "uid": "related_links", + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "boolean", + "display_name": "Show related articles", + "uid": "show_related_articles", + "field_metadata": { + "description": "", + "default_value": false + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "group", + "display_name": "Related articles", + "field_metadata": { + "description": "", + "instruction": "" + }, + "schema": [ + { + "data_type": "text", + "display_name": "Heading", + "uid": "heading", + "field_metadata": { + "description": "", + "default_value": "Related Headline", + "placeholder": "Related Articles", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "text", + "display_name": "Sub heading", + "uid": "sub_heading", + "field_metadata": { + "description": "", + "default_value": "Related Subhead", + "multiline": true, + "placeholder": "Related Subheading", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "number", + "display_name": "Number of articles", + "uid": "number_of_articles", + "field_metadata": { + "description": "", + "default_value": 6, + "placeholder": "Number of articles" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "min": 1, + "max": 6, + "indexed": false, + "inbuilt_model": false + } + ], + "uid": "related_articles", + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "global_field", + "display_name": "SEO", + "reference_to": "seo", + "field_metadata": { + "description": "" + }, + "uid": "seo", + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false, + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "uid": "title", + "field_metadata": { + "description": "Please add the SEO title of the page.", + "default_value": "Title", + "placeholder": "Title", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "text", + "display_name": "Description", + "uid": "description", + "field_metadata": { + "description": "Please enter the SEO description of the page.", + "default_value": "Description", + "multiline": true, + "placeholder": "Description", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "text", + "display_name": "Canonical URL", + "uid": "canonical_url", + "field_metadata": { + "description": "Please add the canonical url of the page.", + "default_value": "/", + "placeholder": "Canonical URL", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "boolean", + "display_name": "No index", + "uid": "no_index", + "field_metadata": { + "description": "Please check if the value is no index", + "default_value": true + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "boolean", + "display_name": "No follow", + "uid": "no_follow", + "field_metadata": { + "description": "Please check if the value is no follow.", + "default_value": true + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + } + ] + } + ], + "options": { + "is_page": true, + "singleton": false, + "sub_title": [], + "title": "title", + "url_pattern": "/:title", + "url_prefix": "/article/" + } + }, + "branch": { + "uid": "main", + "source": "", + "alias": [] + } + }, + "event": "create", + "triggered_at": "2024-12-19T21:29:49.441Z" +} \ No newline at end of file diff --git a/components/contentstack/sources/publish-entry-instant/publish-entry-instant.mjs b/components/contentstack/sources/publish-entry-instant/publish-entry-instant.mjs index 2527a67030997..16529f65edeb1 100644 --- a/components/contentstack/sources/publish-entry-instant/publish-entry-instant.mjs +++ b/components/contentstack/sources/publish-entry-instant/publish-entry-instant.mjs @@ -1,170 +1,29 @@ -import crypto from "crypto"; -import contentstack from "../../contentstack.app.mjs"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "contentstack-publish-entry-instant", - name: "Contentstack - Entry Published", - description: "Emit new event when content is live on your website. [See the documentation]()", - version: "0.0.{{ts}}", + name: "New Entry Published (Instant)", + description: "Emit new event when an entry is published in ContentStack.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - contentstack, - stackId: { - propDefinition: [ - contentstack, - "stackId", - ], - }, - entryId: { - propDefinition: [ - contentstack, - "entryId", - ], - }, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - secret: { - type: "string", - label: "Webhook Secret", - description: "The secret used to validate webhook signatures", - secret: true, - optional: true, - }, - }, methods: { - _getWebhookId() { - return this.db.get("webhookId"); - }, - _setWebhookId(id) { - this.db.set("webhookId", id); - }, - async createWebhook() { - const webhookName = `Pipedream Webhook - ${Date.now()}`; - const webhookUrl = this.http.endpoint; - - const webhookData = { - name: webhookName, - url: webhookUrl, - events: [ - "entry.publish", - ], - ...(this.secret - ? { - secret: this.secret, - } - : {}), - stack_uid: this.stackId, + ...common.methods, + getChannels() { + return [ + "content_types.entries.publish", + ]; + }, + generateMeta(event) { + const id = event.data.entry.uid; + return { + id, + summary: `New entry published with ID: ${id}`, + ts: Date.parse(event.triggered_at), }; - - const response = await this.contentstack._makeRequest({ - method: "POST", - path: `/stacks/${this.stackId}/webhooks`, - data: webhookData, - }); - - const webhookId = response.uid; - this._setWebhookId(webhookId); }, - async deleteWebhook() { - const webhookId = this._getWebhookId(); - - if (webhookId) { - await this.contentstack._makeRequest({ - method: "DELETE", - path: `/stacks/${this.stackId}/webhooks/${webhookId}`, - }); - } - }, - async listRecentEntries() { - const query = JSON.stringify({ - uid: this.entryId, - locale: "*", - }); - - const entries = await this.contentstack.paginate(this.contentstack.listEntries, { - query, - }); - - return entries; - }, - validateSignature(payload, signature) { - if (!this.secret) return true; - const hash = crypto.createHmac("sha256", this.secret).update(payload) - .digest("base64"); - return hash === signature; - }, - }, - hooks: { - async deploy() { - const entries = await this.listRecentEntries(); - - const recentEntries = entries.slice(-50); - for (const entry of recentEntries) { - this.$emit( - { - stackId: this.stackId, - entryId: entry.uid, - }, - { - id: entry.uid, - summary: `Entry published: ${entry.title}`, - ts: Date.parse(entry.publish_details.livesite_at) || Date.now(), - }, - ); - } - }, - async activate() { - await this.createWebhook(); - }, - async deactivate() { - await this.deleteWebhook(); - }, - }, - async run(event) { - const signature = event.headers["x-contentstack-signature"]; - const rawBody = JSON.stringify(event.body); - - if (this.secret) { - const isValid = this.validateSignature(rawBody, signature); - if (!isValid) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - } - - const entry = event.body.entry; - - if (entry.uid !== this.entryId) { - this.http.respond({ - status: 200, - body: "OK", - }); - return; - } - - this.$emit( - { - stackId: this.stackId, - entryId: entry.uid, - }, - { - id: entry.uid, - summary: `Entry published in stack ${this.stackId}`, - ts: Date.parse(entry.publish_details.livesite_at) || Date.now(), - }, - ); - - this.http.respond({ - status: 200, - body: "OK", - }); }, + sampleEmit, }; diff --git a/components/contentstack/sources/publish-entry-instant/test-event.mjs b/components/contentstack/sources/publish-entry-instant/test-event.mjs new file mode 100644 index 0000000000000..ad7581313d029 --- /dev/null +++ b/components/contentstack/sources/publish-entry-instant/test-event.mjs @@ -0,0 +1,452 @@ +export default { + "module": "entry", + "api_key": "blt6e39cdfaac74824c", + "event": "publish", + "bulk": true, + "data": { + "locale": "en", + "status": "success", + "action": "publish", + "entry": { + "_version": 1, + "deleted_at": false, + "locale": "en", + "uid": "blt6b0da76c39f34851", + "_in_progress": false, + "content": { + "type": "doc", + "attrs": {}, + "uid": "d05b26fe64214bca883e5e31eb11090c", + "children": [ + { + "type": "p", + "attrs": {}, + "uid": "5a4f78e3731748d284c83d93ecd794a3", + "children": [ + { + "text": "" + } + ] + } + ], + "_version": 1 + }, + "cover_image": { + "parent_uid": null, + "title": "george.jpg", + "uid": "blt81f45c9d5c3103de", + "created_by": "cs52122cc4ac22f20e", + "updated_by": "cs52122cc4ac22f20e", + "created_at": "2024-12-19T20:55:53.701Z", + "updated_at": "2024-12-19T20:55:53.701Z", + "deleted_at": false, + "content_type": "image/jpeg", + "file_size": "463713", + "filename": "george.jpg", + "dimension": { + "height": 1548, + "width": 1353 + }, + "_version": 1, + "is_dir": false, + "tags": [], + "url": "https://images.contentstack.io/v3/assets/blt6e39cdfaac74824c/blt81f45c9d5c3103de/67648859989c5367eed4ffdb/george.jpg" + }, + "created_at": "2024-12-19T21:29:49.303Z", + "created_by": "cs52122cc4ac22f20e", + "related_articles": { + "heading": "Related Headline", + "sub_heading": "Related Subhead", + "number_of_articles": 6 + }, + "related_links": { + "text": "Related Links" + }, + "seo": { + "title": "Title", + "description": "Description", + "canonical_url": "/", + "no_index": true, + "no_follow": true + }, + "show_related_articles": false, + "show_related_links": false, + "summary": "Summary", + "tags": [], + "taxonomies": [], + "title": "test article", + "updated_at": "2024-12-19T21:29:49.303Z", + "updated_by": "cs52122cc4ac22f20e", + "url": "/article/test-article", + "publish_details": { + "environment": "blt78bb9fab298c6744", + "locale": "en", + "time": "2024-12-19T21:30:01.252Z", + "user": "cs52122cc4ac22f20e" + } + }, + "content_type": { + "title": "Article", + "uid": "article", + "schema": [ + { + "data_type": "text", + "display_name": "Title", + "field_metadata": { + "_default": true, + "placeholder": "Title", + "version": 3 + }, + "mandatory": true, + "uid": "title", + "unique": true, + "multiple": false, + "non_localizable": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "text", + "display_name": "URL", + "uid": "url", + "field_metadata": { + "_default": true, + "version": 3 + }, + "multiple": false, + "unique": false, + "mandatory": false, + "non_localizable": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "file", + "display_name": "Cover image", + "uid": "cover_image", + "field_metadata": { + "description": "", + "rich_text_type": "standard", + "image": true + }, + "mandatory": true, + "multiple": false, + "non_localizable": false, + "unique": false, + "dimension": { + "width": { + "min": null, + "max": null + }, + "height": { + "min": null, + "max": null + } + }, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "text", + "display_name": "Summary", + "uid": "summary", + "field_metadata": { + "description": "", + "default_value": "Summary", + "multiline": true, + "placeholder": "Summary", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "taxonomy", + "display_name": "Taxonomy", + "uid": "taxonomies", + "taxonomies": [ + { + "taxonomy_uid": "region", + "mandatory": false, + "multiple": true, + "non_localizable": false + }, + { + "taxonomy_uid": "topic", + "mandatory": false, + "multiple": true, + "non_localizable": false + } + ], + "field_metadata": { + "description": "", + "default_value": "" + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": true, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "json", + "display_name": "Content", + "uid": "content", + "field_metadata": { + "allow_json_rte": true, + "embed_entry": false, + "description": "", + "default_value": "", + "multiline": false, + "rich_text_type": "advanced", + "options": [] + }, + "format": "", + "error_messages": { + "format": "" + }, + "reference_to": [ + "sys_assets" + ], + "multiple": false, + "non_localizable": false, + "unique": false, + "mandatory": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "boolean", + "display_name": "Show Related Links", + "uid": "show_related_links", + "field_metadata": { + "description": "", + "default_value": false + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "group", + "display_name": "Related Links", + "field_metadata": { + "description": "", + "instruction": "" + }, + "schema": [ + { + "data_type": "text", + "display_name": "Text", + "uid": "text", + "field_metadata": { + "description": "", + "default_value": "Related Links", + "placeholder": "Text", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + } + ], + "uid": "related_links", + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "boolean", + "display_name": "Show related articles", + "uid": "show_related_articles", + "field_metadata": { + "description": "", + "default_value": false + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "group", + "display_name": "Related articles", + "field_metadata": { + "description": "", + "instruction": "" + }, + "schema": [ + { + "data_type": "text", + "display_name": "Heading", + "uid": "heading", + "field_metadata": { + "description": "", + "default_value": "Related Headline", + "placeholder": "Related Articles", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "text", + "display_name": "Sub heading", + "uid": "sub_heading", + "field_metadata": { + "description": "", + "default_value": "Related Subhead", + "multiline": true, + "placeholder": "Related Subheading", + "version": 3 + }, + "format": "", + "error_messages": { + "format": "" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "number", + "display_name": "Number of articles", + "uid": "number_of_articles", + "field_metadata": { + "description": "", + "default_value": 6, + "placeholder": "Number of articles" + }, + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "min": 1, + "max": 6, + "indexed": false, + "inbuilt_model": false + } + ], + "uid": "related_articles", + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + }, + { + "data_type": "global_field", + "display_name": "SEO", + "reference_to": "seo", + "field_metadata": { + "description": "" + }, + "uid": "seo", + "mandatory": false, + "multiple": false, + "non_localizable": false, + "unique": false, + "indexed": false, + "inbuilt_model": false + } + ], + "options": { + "is_page": true, + "singleton": false, + "sub_title": [], + "title": "title", + "url_pattern": "/:title", + "url_prefix": "/article/" + }, + "created_by": "bltce9401ba486d3c23", + "updated_by": "bltce9401ba486d3c23", + "created_at": "2024-11-30T03:52:14.855Z", + "updated_at": "2024-11-30T03:54:29.605Z", + "deleted_at": false, + "description": "", + "_version": 5, + "field_rules": [] + }, + "environment": { + "urls": [ + { + "locale": "en", + "url": "https://compass-starter---blt6e39cdfaac-production.contentstackapps.com/en" + }, + { + "locale": "fr", + "url": "https://compass-starter---blt6e39cdfaac-production.contentstackapps.com/fr" + }, + { + "locale": "de", + "url": "https://compass-starter---blt6e39cdfaac-production.contentstackapps.com/de" + }, + { + "locale": "es", + "url": "https://compass-starter---blt6e39cdfaac-production.contentstackapps.com/es" + } + ], + "name": "production", + "_version": 2, + "api_key": "blt6e39cdfaac74824c", + "org_uid": "blte5beeb2edad1d61a", + "uid": "blt78bb9fab298c6744", + "created_by": "bltce9401ba486d3c23", + "updated_by": "bltce9401ba486d3c23", + "created_at": "2024-11-30T03:50:51.656Z", + "updated_at": "2024-11-30T03:58:41.754Z" + }, + "branch": { + "api_key": "blt6e39cdfaac74824c", + "org_uid": "blte5beeb2edad1d61a", + "uid": "main", + "source": "", + "created_by": "bltce9401ba486d3c23", + "updated_by": "bltce9401ba486d3c23", + "created_at": "2024-11-30T03:50:41.195Z", + "updated_at": "2024-11-30T03:50:41.195Z", + "alias": [] + } + }, + "triggered_at": "2024-12-19T21:30:01.521Z" +} \ No newline at end of file From baf5c664267d19f9849e9d949ce18f0dc705a458 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Fri, 20 Dec 2024 15:01:30 -0500 Subject: [PATCH 3/4] pnpm-lock.yaml --- pnpm-lock.yaml | 48 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9b53bc91529b..3ef268d95752c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2192,7 +2192,11 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/contentstack: {} + components/contentstack: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/control_d: dependencies: @@ -12193,10 +12197,10 @@ importers: version: 14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nextra: specifier: latest - version: 3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + version: 3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) nextra-theme-docs: specifier: latest - version: 3.2.5(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 3.3.0(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -22396,16 +22400,16 @@ packages: sass: optional: true - nextra-theme-docs@3.2.5: - resolution: {integrity: sha512-eF0j1VNNS1rFjZOfYqlrXISaCU3+MhZ9hhXY+TUydRlfELrFWpGzrpW6MiL7hq4JvUR7OBtHHs8+A+8AYcETBQ==} + nextra-theme-docs@3.3.0: + resolution: {integrity: sha512-4JSbDmsbtaYa2eKHsNymWy6So4/fAAXuNPSkjgQ3S+aLRiC730mR9djdkTd1iRca4+czetzBWaqxu+HwTVSSCA==} peerDependencies: next: '>=13' - nextra: 3.2.5 + nextra: 3.3.0 react: '>=18' react-dom: '>=18' - nextra@3.2.5: - resolution: {integrity: sha512-n665DRpI/brjHXM83G5LdlbYA2nOtjaLcWJs7mZS3gkuRDmEXpJj4XJ860xrhkYZW2iYoUMu32zzhPuFByU7VA==} + nextra@3.3.0: + resolution: {integrity: sha512-//+bQW3oKrpLrrIFD5HJow310+YcNYhGIgdM4y+EjYuIXScJcgp6Q4Upsq8c2s2fQKEJjeuf+hXKusx9fvH/2w==} engines: {node: '>=18'} peerDependencies: next: '>=13' @@ -23457,6 +23461,12 @@ packages: '@types/react': '>=18' react: '>=18' + react-medium-image-zoom@5.2.12: + resolution: {integrity: sha512-BbQ9jLBFxu6z+viH5tzQzAGqHOJQoYUM7iT1KUkamWKOO6vR1pC33os7LGLrHvOcyySMw74rUdoUCXFdeglwCQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-select@5.8.3: resolution: {integrity: sha512-lVswnIq8/iTj1db7XCG74M/3fbGB6ZaluCzvwPGT5ZOjCdL/k0CLWhEK0vCBLuU5bHTEf6Gj8jtSvi+3v+tO1w==} peerDependencies: @@ -24544,22 +24554,22 @@ packages: superagent@3.8.1: resolution: {integrity: sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==} engines: {node: '>= 4.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@4.1.0: resolution: {integrity: sha512-FT3QLMasz0YyCd4uIi5HNe+3t/onxMyEho7C3PSqmti3Twgy2rXT4fmkTz6wRL6bTF4uzPcfkUCa8u4JWHw8Ag==} engines: {node: '>= 6.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@5.3.1: resolution: {integrity: sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==} engines: {node: '>= 7.0.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please downgrade to v7.1.5 if you need IE/ActiveXObject support OR upgrade to v8.0.0 as we no longer support IE and published an incorrect patch version (see https://github.com/visionmedia/superagent/issues/1731) supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} @@ -30718,6 +30728,8 @@ snapshots: '@putout/operator-filesystem': 5.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3)) '@putout/operator-json': 2.2.0 putout: 36.13.1(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color '@putout/operator-regexp@1.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3))': dependencies: @@ -39728,7 +39740,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@3.2.5(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@3.3.0(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) clsx: 2.1.1 @@ -39736,13 +39748,13 @@ snapshots: flexsearch: 0.7.43 next: 14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: 0.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nextra: 3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + nextra: 3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.23.8 - nextra@3.2.5(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): + nextra@3.3.0(@types/react@18.3.12)(acorn@8.14.0)(next@14.2.19(@babel/core@8.0.0-alpha.13)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3): dependencies: '@formatjs/intl-localematcher': 0.5.8 '@headlessui/react': 2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -39769,6 +39781,7 @@ snapshots: p-limit: 6.1.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-medium-image-zoom: 5.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rehype-katex: 7.0.1 rehype-pretty-code: 0.14.0(shiki@1.24.0) rehype-raw: 7.0.0 @@ -41206,6 +41219,11 @@ snapshots: transitivePeerDependencies: - supports-color + react-medium-image-zoom@5.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-select@5.8.3(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 From df2ec8db395479e4be562764fd8c01901659d95b Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Fri, 20 Dec 2024 15:13:58 -0500 Subject: [PATCH 4/4] update --- components/contentstack/common/utils.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/contentstack/common/utils.mjs b/components/contentstack/common/utils.mjs index 269cef5aea399..079fd29439720 100644 --- a/components/contentstack/common/utils.mjs +++ b/components/contentstack/common/utils.mjs @@ -34,8 +34,9 @@ export function parseEntry(entry) { } catch { parsedEntry[key] = value; } + } else { + parsedEntry[key] = value; } - parsedEntry[key] = value; } return parsedEntry; }