Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,27 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { getBlogPost } from '../api';
import { cmsAuth } from '../auth';
export const getBlogPostAction = createAction({
name: 'get_blog_post',
auth: cmsAuth,
displayName: 'Get Blog Post',
description: 'Get a blog post from Total CMS',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to retrieve',
required: true,
}),
permalink: Property.ShortText({
displayName: 'Permalink',
description: 'The permalink of the post to retrieve',
required: true,
}),
},
async run(context) {
const slug = context.propsValue.slug;
const permalink = context.propsValue.permalink;
return await getBlogPost(context.auth, slug, permalink);
},
});

View File

@@ -0,0 +1,43 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { getContent } from '../api';
import { cmsAuth } from '../auth';
export const getContentAction = createAction({
name: 'get_content',
auth: cmsAuth,
displayName: 'Get Content',
description: 'Get content from your Total CMS website',
props: {
type: Property.StaticDropdown({
displayName: 'Data Type',
description: 'The type of data to return',
required: true,
options: {
options: [
{ label: 'Blog', value: 'blog' },
{ label: 'Datastore', value: 'datastore' },
{ label: 'Date', value: 'date' },
{ label: 'Depot', value: 'depot' },
{ label: 'Feed', value: 'feed' },
{ label: 'File', value: 'file' },
{ label: 'Gallery', value: 'gallery' },
{ label: 'Image', value: 'image' },
{ label: 'Ratings', value: 'ratings' },
{ label: 'Text', value: 'text' },
{ label: 'Toggle', value: 'toggle' },
{ label: 'Video', value: 'video' },
],
},
}),
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to retrieve',
required: true,
}),
},
async run(context) {
const type = context.propsValue.type;
const slug = context.propsValue.slug;
return await getContent(context.auth, type, slug);
},
});

View File

@@ -0,0 +1,127 @@
import {
createAction,
Property,
} from '@activepieces/pieces-framework';
import { saveBlogGallery } from '../api';
import { cmsAuth } from '../auth';
import { z } from 'zod';
import { propsValidation } from '@activepieces/pieces-common';
export const saveBlogGalleryAction = createAction({
name: 'save_blog_gallery',
auth: cmsAuth,
displayName: 'Save Blog Post Gallery Image',
description: 'Save image to Total CMS blog post gallery',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the blog to save',
required: true,
}),
permalink: Property.ShortText({
displayName: 'Permalink',
description: 'The permalink of the blog post to save',
required: true,
}),
image: Property.File({
displayName: 'Image',
description: 'The image to save',
required: true,
}),
alt: Property.ShortText({
displayName: 'Alt Text',
description: 'The alt text for the image',
required: true,
}),
quality: Property.Number({
displayName: 'Thumbnail Quality',
description: 'The quality of the thumbnail',
required: true,
defaultValue: 85,
}),
scaleTh: Property.Number({
displayName: 'Thumbnail Scale',
description: 'The scale of the thumbnail',
required: true,
defaultValue: 400,
}),
scaleSq: Property.Number({
displayName: 'Thumbnail Square Scale',
description: 'The scale of the square thumbnail',
required: true,
defaultValue: 400,
}),
resize: Property.StaticDropdown({
displayName: 'Thumbnail Resize Method',
description: 'The method to use when resizing the thumbnail',
required: true,
defaultValue: 'auto',
options: {
options: [
{ label: 'Auto', value: 'auto' },
{ label: 'Landscape', value: 'landscape' },
{ label: 'Portrait', value: 'portrait' },
],
},
}),
lcrop: Property.StaticDropdown({
displayName: 'Thumbnail Landscape Crop',
description:
'The method to use when cropping the landscape thumbnail for the square thumbnail',
required: true,
defaultValue: 'center',
options: {
options: [
{ label: 'Left', value: 'left' },
{ label: 'Center', value: 'center' },
{ label: 'Right', value: 'right' },
],
},
}),
pcrop: Property.StaticDropdown({
displayName: 'Thumbnail Landscape Crop',
description:
'The method to use when cropping the landscape thumbnail for the square thumbnail',
required: true,
defaultValue: 'middle',
options: {
options: [
{ label: 'Top', value: 'top' },
{ label: 'Middle', value: 'middle' },
{ label: 'Bottom', value: 'bottom' },
],
},
}),
altMeta: Property.Checkbox({
displayName: 'Pull Alt Text from Meta Data',
description:
'Pull the alt text from the meta data of the image. If set, place placeholder text in the alt text field above.',
required: true,
}),
},
async run(context) {
await propsValidation.validateZod(context.propsValue, {
quality: z.number().min(1).max(100),
scaleTh: z.number().min(1),
scaleSq: z.number().min(1),
});
const slug = context.propsValue.slug;
const image = {
filename: context.propsValue.image.filename,
base64: context.propsValue.image.base64,
};
return await saveBlogGallery(context.auth, slug, image, {
permalink: context.propsValue.permalink,
thumbs: 1,
optimize: 1,
alttype: context.propsValue.altMeta ? 'meta' : 'user',
alt: context.propsValue.alt,
quality: context.propsValue.quality,
scale_th: context.propsValue.scaleTh,
scale_sq: context.propsValue.scaleSq,
resize: context.propsValue.resize,
lcrop: context.propsValue.lcrop,
pcrop: context.propsValue.pcrop,
});
},
});

View File

@@ -0,0 +1,131 @@
import {
createAction,
Property,
} from '@activepieces/pieces-framework';
import { z } from 'zod';
import { propsValidation } from '@activepieces/pieces-common';
import { saveBlogImage } from '../api';
import { cmsAuth } from '../auth';
export const saveBlogImageAction = createAction({
name: 'save_blog_image',
auth: cmsAuth,
displayName: 'Save Blog Post Image',
description: 'Save image to Total CMS blog post',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to save',
required: true,
}),
permalink: Property.ShortText({
displayName: 'Permalink',
description: 'The permalink of the blog post to save',
required: true,
}),
image: Property.File({
displayName: 'Image',
description: 'The image to save',
required: true,
}),
alt: Property.ShortText({
displayName: 'Alt Text',
description: 'The alt text for the image',
required: true,
}),
quality: Property.Number({
displayName: 'Thumbnail Quality',
description: 'The quality of the thumbnail',
required: true,
defaultValue: 85,
}),
scaleTh: Property.Number({
displayName: 'Thumbnail Scale',
description: 'The scale of the thumbnail',
required: true,
defaultValue: 400,
}),
scaleSq: Property.Number({
displayName: 'Thumbnail Square Scale',
description: 'The scale of the square thumbnail',
required: true,
defaultValue: 400,
}),
resize: Property.StaticDropdown({
displayName: 'Thumbnail Resize Method',
description: 'The method to use when resizing the thumbnail',
required: true,
defaultValue: 'auto',
options: {
options: [
{ label: 'Auto', value: 'auto' },
{ label: 'Landscape', value: 'landscape' },
{ label: 'Portrait', value: 'portrait' },
],
},
}),
lcrop: Property.StaticDropdown({
displayName: 'Thumbnail Landscape Crop',
description:
'The method to use when cropping the landscape thumbnail for the square thumbnail',
required: true,
defaultValue: 'center',
options: {
options: [
{ label: 'Left', value: 'left' },
{ label: 'Center', value: 'center' },
{ label: 'Right', value: 'right' },
],
},
}),
pcrop: Property.StaticDropdown({
displayName: 'Thumbnail Landscape Crop',
description:
'The method to use when cropping the landscape thumbnail for the square thumbnail',
required: true,
defaultValue: 'middle',
options: {
options: [
{ label: 'Top', value: 'top' },
{ label: 'Middle', value: 'middle' },
{ label: 'Bottom', value: 'bottom' },
],
},
}),
altMeta: Property.Checkbox({
displayName: 'Pull Alt Text from Meta Data',
description:
'Pull the alt text from the meta data of the image. If set, place placeholder text in the alt text field above.',
required: true,
}),
},
async run(context) {
await propsValidation.validateZod(context.propsValue, {
quality: z.number().min(1).max(100),
scaleTh: z.number().min(1),
scaleSq: z.number().min(1),
});
const slug = context.propsValue.slug;
const image = {
filename: context.propsValue.image.filename,
base64: context.propsValue.image.base64,
};
return await saveBlogImage(context.auth, slug, image, {
permalink: context.propsValue.permalink,
thumbs: 1,
optimize: 1,
alttype: context.propsValue.altMeta ? 'meta' : 'user',
alt: context.propsValue.alt,
quality: context.propsValue.quality,
scale_th: context.propsValue.scaleTh,
scale_sq: context.propsValue.scaleSq,
resize: context.propsValue.resize,
lcrop: context.propsValue.lcrop,
pcrop: context.propsValue.pcrop,
// Cannot add support for ext option with how this API is built.
// it would break the saving of the blog post since it would try
// to save the blog post JSON file with the extension of the ext option
});
},
});

View File

@@ -0,0 +1,132 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { saveContent } from '../api';
import { cmsAuth } from '../auth';
export const saveBlogPostAction = createAction({
name: 'save_blog_post',
auth: cmsAuth,
displayName: 'Save Blog Post',
description: 'Save blog content to Total CMS',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to save',
required: true,
}),
permalink: Property.ShortText({
displayName: 'Permalink',
description:
'The permalink of the blog post. Ensure this is unique or it will overwrite the existing post.',
required: true,
}),
title: Property.ShortText({
displayName: 'Title',
description: 'The title of the blog post',
required: false,
}),
timestamp: Property.Number({
displayName: 'Date (Unix Timestamp)',
description: 'The date in unix timestamp format',
required: false,
}),
summary: Property.LongText({
displayName: 'Summary',
description: 'The summary of the blog post',
required: false,
}),
content: Property.LongText({
displayName: 'Content',
description: 'The content of the blog post',
required: false,
}),
extra: Property.LongText({
displayName: 'Extra Content',
description: 'The extra content of the blog post',
required: false,
}),
extra2: Property.LongText({
displayName: 'Extra Content 2',
description: 'The extra content 2 of the blog post',
required: false,
}),
media: Property.ShortText({
displayName: 'Media',
description: 'The media of the blog post',
required: false,
}),
rssTitle: Property.ShortText({
displayName: 'RSS Title',
description: 'The RSS title of the blog post',
required: false,
}),
rssDescription: Property.ShortText({
displayName: 'RSS Description',
description: 'The RSS description of the blog post',
required: false,
}),
author: Property.ShortText({
displayName: 'Author',
description: 'The author of the blog post',
required: false,
}),
genre: Property.ShortText({
displayName: 'Genre',
description: 'The genre of the blog post',
required: false,
}),
categories: Property.ShortText({
displayName: 'Categories',
description: 'A comma separated list of categories for the blog post',
required: false,
}),
tags: Property.ShortText({
displayName: 'Tags',
description: 'A comma separated list of tags for the blog post',
required: false,
}),
labels: Property.ShortText({
displayName: 'Labels',
description: 'A comma separated list of labels for the blog post',
required: false,
}),
draft: Property.Checkbox({
displayName: 'Draft',
description: 'Set to true to save as a draft',
required: false,
}),
featured: Property.Checkbox({
displayName: 'Featured',
description: 'Set to true to save as a featured post',
required: false,
}),
archived: Property.Checkbox({
displayName: 'Archived',
description: 'Set to true to save as an archived post',
required: false,
}),
},
async run(context) {
const slug = context.propsValue.slug;
return await saveContent(context.auth, 'blog', slug, {
nodecode: true,
permalink: context.propsValue.permalink,
title: context.propsValue.title,
timestamp: context.propsValue.timestamp?.toString(),
summary: context.propsValue.summary,
content: context.propsValue.content,
extra: context.propsValue.extra,
extra2: context.propsValue.extra2,
media: context.propsValue.media,
rss_title: context.propsValue.rssTitle,
rss_description: context.propsValue.rssDescription,
author: context.propsValue.author,
genre: context.propsValue.genre,
categories: context.propsValue.categories,
tags: context.propsValue.tags,
labels: context.propsValue.labels,
draft: context.propsValue.draft ? 'true' : 'false',
featured: context.propsValue.featured ? 'true' : 'false',
archived: context.propsValue.archived ? 'true' : 'false',
});
},
});

View File

@@ -0,0 +1,30 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { saveContent } from '../api';
import { cmsAuth } from '../auth';
export const saveDateAction = createAction({
name: 'save_date',
auth: cmsAuth,
displayName: 'Save Date Content',
description: 'Save date content to Total CMS',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to save',
required: true,
}),
timestamp: Property.Number({
displayName: 'Unix Timestamp',
description: 'The unix timestamp to save',
required: true,
}),
},
async run(context) {
const slug = context.propsValue.slug;
const timestamp = context.propsValue.timestamp;
return await saveContent(context.auth, 'date', slug, {
nodecode: true,
timestamp: timestamp.toString(),
});
},
});

View File

@@ -0,0 +1,30 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { saveDepot } from '../api';
import { cmsAuth } from '../auth';
export const saveDepotAction = createAction({
name: 'save_depot',
auth: cmsAuth,
displayName: 'Save Depot',
description: 'Save file to Total CMS depot',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the depot to save',
required: true,
}),
file: Property.File({
displayName: 'File',
description: 'The file to save',
required: true,
}),
},
async run(context) {
const slug = context.propsValue.slug;
const file = {
filename: context.propsValue.file.filename,
base64: context.propsValue.file.base64,
};
return await saveDepot(context.auth, slug, file);
},
});

View File

@@ -0,0 +1,37 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { saveFile } from '../api';
import { cmsAuth } from '../auth';
export const saveFileAction = createAction({
name: 'save_file',
auth: cmsAuth,
displayName: 'Save File',
description: 'Save file to Total CMS',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the file to save',
required: true,
}),
ext: Property.ShortText({
displayName: 'File Extension',
description: 'The file extension of the file',
required: true,
}),
file: Property.File({
displayName: 'File',
description: 'The file to save',
required: true,
}),
},
async run(context) {
const slug = context.propsValue.slug;
const file = {
filename: context.propsValue.file.filename,
base64: context.propsValue.file.base64,
};
return await saveFile(context.auth, slug, file, {
ext: context.propsValue.ext,
});
},
});

View File

@@ -0,0 +1,122 @@
import {
createAction,
Property,
} from '@activepieces/pieces-framework';
import { saveGallery } from '../api';
import { cmsAuth } from '../auth';
import { z } from 'zod';
import { propsValidation } from '@activepieces/pieces-common';
export const saveGalleryAction = createAction({
name: 'save_gallery',
auth: cmsAuth,
displayName: 'Save Gallery Image',
description: 'Save image to Total CMS gallery',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the gallery to save',
required: true,
}),
image: Property.File({
displayName: 'Image',
description: 'The image to save',
required: true,
}),
alt: Property.ShortText({
displayName: 'Alt Text',
description: 'The alt text for the image',
required: true,
}),
quality: Property.Number({
displayName: 'Thumbnail Quality',
description: 'The quality of the thumbnail',
required: true,
defaultValue: 85,
}),
scaleTh: Property.Number({
displayName: 'Thumbnail Scale',
description: 'The scale of the thumbnail',
required: true,
defaultValue: 400,
}),
scaleSq: Property.Number({
displayName: 'Thumbnail Square Scale',
description: 'The scale of the square thumbnail',
required: true,
defaultValue: 400,
}),
resize: Property.StaticDropdown({
displayName: 'Thumbnail Resize Method',
description: 'The method to use when resizing the thumbnail',
required: true,
defaultValue: 'auto',
options: {
options: [
{ label: 'Auto', value: 'auto' },
{ label: 'Landscape', value: 'landscape' },
{ label: 'Portrait', value: 'portrait' },
],
},
}),
lcrop: Property.StaticDropdown({
displayName: 'Thumbnail Landscape Crop',
description:
'The method to use when cropping the landscape thumbnail for the square thumbnail',
required: true,
defaultValue: 'center',
options: {
options: [
{ label: 'Left', value: 'left' },
{ label: 'Center', value: 'center' },
{ label: 'Right', value: 'right' },
],
},
}),
pcrop: Property.StaticDropdown({
displayName: 'Thumbnail Landscape Crop',
description:
'The method to use when cropping the landscape thumbnail for the square thumbnail',
required: true,
defaultValue: 'middle',
options: {
options: [
{ label: 'Top', value: 'top' },
{ label: 'Middle', value: 'middle' },
{ label: 'Bottom', value: 'bottom' },
],
},
}),
altMeta: Property.Checkbox({
displayName: 'Pull Alt Text from Meta Data',
description:
'Pull the alt text from the meta data of the image. If set, place placeholder text in the alt text field above.',
required: true,
}),
},
async run(context) {
await propsValidation.validateZod(context.propsValue, {
quality: z.number().min(1).max(100),
scaleTh: z.number().min(1),
scaleSq: z.number().min(1),
});
const slug = context.propsValue.slug;
const image = {
filename: context.propsValue.image.filename,
base64: context.propsValue.image.base64,
};
return await saveGallery(context.auth, slug, image, {
thumbs: 1,
optimize: 1,
alttype: context.propsValue.altMeta ? 'meta' : 'user',
alt: context.propsValue.alt,
quality: context.propsValue.quality,
scale_th: context.propsValue.scaleTh,
scale_sq: context.propsValue.scaleSq,
resize: context.propsValue.resize,
lcrop: context.propsValue.lcrop,
pcrop: context.propsValue.pcrop,
});
},
});

View File

@@ -0,0 +1,134 @@
import {
createAction,
Property,
} from '@activepieces/pieces-framework';
import { saveImage } from '../api';
import { cmsAuth } from '../auth';
import { z } from 'zod';
import { propsValidation } from '@activepieces/pieces-common';
export const saveImageAction = createAction({
name: 'save_image',
auth: cmsAuth,
displayName: 'Save Image',
description: 'Save image to Total CMS',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to save',
required: true,
}),
image: Property.File({
displayName: 'Image',
description: 'The image to save',
required: true,
}),
alt: Property.ShortText({
displayName: 'Alt Text',
description: 'The alt text for the image',
required: true,
}),
ext: Property.StaticDropdown({
displayName: 'Extension',
description: 'The extension of the image',
required: true,
defaultValue: 'jpg',
options: {
options: [
{ label: 'jpg', value: 'jpg' },
{ label: 'png', value: 'png' },
],
},
}),
quality: Property.Number({
displayName: 'Thumbnail Quality',
description: 'The quality of the thumbnail',
required: true,
defaultValue: 85,
}),
scaleTh: Property.Number({
displayName: 'Thumbnail Scale',
description: 'The scale of the thumbnail',
required: true,
defaultValue: 400,
}),
scaleSq: Property.Number({
displayName: 'Thumbnail Square Scale',
description: 'The scale of the square thumbnail',
required: true,
defaultValue: 400,
}),
resize: Property.StaticDropdown({
displayName: 'Thumbnail Resize Method',
description: 'The method to use when resizing the thumbnail',
required: true,
defaultValue: 'auto',
options: {
options: [
{ label: 'Auto', value: 'auto' },
{ label: 'Landscape', value: 'landscape' },
{ label: 'Portrait', value: 'portrait' },
],
},
}),
lcrop: Property.StaticDropdown({
displayName: 'Thumbnail Landscape Crop',
description:
'The method to use when cropping the landscape thumbnail for the square thumbnail',
required: true,
defaultValue: 'center',
options: {
options: [
{ label: 'Left', value: 'left' },
{ label: 'Center', value: 'center' },
{ label: 'Right', value: 'right' },
],
},
}),
pcrop: Property.StaticDropdown({
displayName: 'Thumbnail Landscape Crop',
description:
'The method to use when cropping the landscape thumbnail for the square thumbnail',
required: true,
defaultValue: 'middle',
options: {
options: [
{ label: 'Top', value: 'top' },
{ label: 'Middle', value: 'middle' },
{ label: 'Bottom', value: 'bottom' },
],
},
}),
altMeta: Property.Checkbox({
displayName: 'Pull Alt Text from Meta Data',
description:
'Pull the alt text from the meta data of the image. If set, place placeholder text in the alt text field above.',
required: true,
}),
},
async run(context) {
await propsValidation.validateZod(context.propsValue, {
quality: z.number().min(1).max(100),
scaleTh: z.number().min(1),
scaleSq: z.number().min(1),
});
const slug = context.propsValue.slug;
const image = {
filename: context.propsValue.image.filename,
base64: context.propsValue.image.base64,
};
return await saveImage(context.auth, slug, image, {
thumbs: 1,
optimize: 1,
alttype: context.propsValue.altMeta ? 'meta' : 'user',
alt: context.propsValue.alt,
ext: context.propsValue.ext,
quality: context.propsValue.quality,
scale_th: context.propsValue.scaleTh,
scale_sq: context.propsValue.scaleSq,
resize: context.propsValue.resize,
lcrop: context.propsValue.lcrop,
pcrop: context.propsValue.pcrop,
});
},
});

View File

@@ -0,0 +1,30 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { saveContent } from '../api';
import { cmsAuth } from '../auth';
export const saveTextAction = createAction({
name: 'save_text',
auth: cmsAuth,
displayName: 'Save Text Content',
description: 'Save text content to Total CMS',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to save',
required: true,
}),
text: Property.LongText({
displayName: 'Text Content',
description: 'The text content to save',
required: true,
}),
},
async run(context) {
const slug = context.propsValue.slug;
const text = context.propsValue.text;
return await saveContent(context.auth, 'text', slug, {
nodecode: true,
text: text,
});
},
});

View File

@@ -0,0 +1,30 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { saveContent } from '../api';
import { cmsAuth } from '../auth';
export const saveToggleAction = createAction({
name: 'save_toggle',
auth: cmsAuth,
displayName: 'Save Toggle',
description: 'Save toggle content to Total CMS',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to save',
required: true,
}),
status: Property.Checkbox({
displayName: 'Status',
description: 'The status of the toggle. "true" is on, "false" is off.',
required: true,
}),
},
async run(context) {
const slug = context.propsValue.slug;
const status = context.propsValue.status ? 'true' : 'false';
return await saveContent(context.auth, 'toggle', slug, {
nodecode: true,
state: status,
});
},
});

View File

@@ -0,0 +1,39 @@
import {
createAction,
Property,
} from '@activepieces/pieces-framework';
import { saveContent } from '../api';
import { cmsAuth } from '../auth';
import { z } from 'zod';
import { propsValidation } from '@activepieces/pieces-common';
export const saveVideoAction = createAction({
name: 'save_video',
auth: cmsAuth,
displayName: 'Save Video Content',
description: 'Save video content to Total CMS',
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to save',
required: true,
}),
video: Property.ShortText({
displayName: 'Video URL',
description: 'The URL of the video to save',
required: true,
}),
},
async run(context) {
await propsValidation.validateZod(context.propsValue, {
video: z.string().url(),
});
const slug = context.propsValue.slug;
const video = context.propsValue.video;
return await saveContent(context.auth, 'video', slug, {
nodecode: true,
video: video,
});
},
});

View File

@@ -0,0 +1,173 @@
import {
httpClient,
HttpMethod,
HttpRequest,
QueryParams,
} from '@activepieces/pieces-common';
import { TotalCMSAuthType } from './auth';
import FormData from 'form-data';
export type KeyValuePair = {
[key: string]: string | boolean | number | object | undefined;
};
export type FileUpload = { filename: string; base64: string };
const totalcmsAPI = async (
auth: TotalCMSAuthType,
type: string,
slug: string,
query: QueryParams = {},
data: KeyValuePair = {},
method: HttpMethod = HttpMethod.GET
) => {
if (method === HttpMethod.GET) {
query['slug'] = slug;
query['type'] = type;
} else {
data['slug'] = slug;
data['type'] = type;
}
const request: HttpRequest = {
body: data,
queryParams: query,
method: method,
url: `${auth.props.domain}/rw_common/plugins/stacks/total-cms/totalapi.php`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'total-key': auth.props.license,
},
};
const response = await httpClient.sendRequest(request);
if (response.status !== 200) {
throw new Error(`Total CMS API error: ${response.status} ${response.body}`);
}
return {
success: true,
data: response.body['data'],
};
};
const totalcmsUploadAPI = async (
auth: TotalCMSAuthType,
type: string,
slug: string,
file: FileUpload,
data: KeyValuePair = {},
fileName = 'file'
) => {
const formData = new FormData();
formData.append('type', type);
formData.append('slug', slug);
formData.append(fileName, Buffer.from(file.base64, 'base64'), file.filename);
for (const key in data) {
if (fileName !== 'file') {
// blog post images use the format image[alt] or gallery[alt]
formData.append(`${fileName}[${key}]`, data[key]);
}
formData.append(key, data[key]);
}
const request: HttpRequest = {
body: formData,
method: HttpMethod.POST,
url: `${auth.props.domain}/rw_common/plugins/stacks/total-cms/totalapi.php`,
headers: {
'Content-Type': 'multipart/form-data',
'total-key': auth.props.license,
},
};
const response = await httpClient.sendRequest(request);
if (response.status !== 200) {
throw new Error(`Total CMS API error: ${response.status} ${response.body}`);
}
return {
success: true,
data: response.body['data'],
};
};
export async function saveFile(
auth: TotalCMSAuthType,
slug: string,
file: FileUpload,
data: KeyValuePair
) {
return totalcmsUploadAPI(auth, 'file', slug, file, data);
}
export async function saveDepot(
auth: TotalCMSAuthType,
slug: string,
file: FileUpload
) {
return totalcmsUploadAPI(auth, 'depot', slug, file);
}
export async function saveImage(
auth: TotalCMSAuthType,
slug: string,
file: FileUpload,
data: KeyValuePair
) {
return totalcmsUploadAPI(auth, 'image', slug, file, data);
}
export async function saveGallery(
auth: TotalCMSAuthType,
slug: string,
file: FileUpload,
data: KeyValuePair
) {
return totalcmsUploadAPI(auth, 'gallery', slug, file, data);
}
export async function saveBlogImage(
auth: TotalCMSAuthType,
slug: string,
file: FileUpload,
data: KeyValuePair
) {
return totalcmsUploadAPI(auth, 'blog', slug, file, data, 'image');
}
export async function saveBlogGallery(
auth: TotalCMSAuthType,
slug: string,
file: FileUpload,
data: KeyValuePair
) {
return totalcmsUploadAPI(auth, 'blog', slug, file, data, 'gallery');
}
export async function saveContent(
auth: TotalCMSAuthType,
type: string,
slug: string,
data: KeyValuePair
) {
return totalcmsAPI(auth, type, slug, {}, data, HttpMethod.POST);
}
export async function getContent(
auth: TotalCMSAuthType,
type: string,
slug: string,
query: QueryParams = {}
) {
return totalcmsAPI(auth, type, slug, query);
}
export async function getBlogPost(
auth: TotalCMSAuthType,
slug: string,
permalink: string
) {
return totalcmsAPI(auth, 'blog', slug, { permalink: permalink });
}

View File

@@ -0,0 +1,45 @@
import { AppConnectionValueForAuthProperty, PieceAuth, Property } from '@activepieces/pieces-framework';
import { z } from 'zod';
import { propsValidation } from '@activepieces/pieces-common';
import { saveContent } from './api';
import { AppConnectionType } from '@activepieces/shared';
export type TotalCMSAuthType = AppConnectionValueForAuthProperty<typeof cmsAuth>;
export const cmsAuth = PieceAuth.CustomAuth({
description: 'Setup your Total CMS connection',
props: {
domain: Property.ShortText({
displayName: 'Total CMS Domain',
description: 'The domain of your Total CMS website',
required: true,
}),
license: PieceAuth.SecretText({
displayName: 'License Key',
description: 'The License key for your Total CMS domain',
required: true,
}),
},
required: true,
async validate({ auth }) {
await propsValidation.validateZod(auth, {
domain: z.string().url(),
license: z.string(),
});
const response = await saveContent({
type: AppConnectionType.CUSTOM_AUTH,
props: auth,
}, 'text', 'activepieces', {
text: 'verified',
});
if (response.success !== true) {
throw new Error(
'Authentication failed. Please check your domain and license key and try again.'
);
}
return {
valid: true,
};
},
});

View File

@@ -0,0 +1,75 @@
import {
TriggerStrategy,
createTrigger,
Property,
PiecePropValueSchema,
AppConnectionValueForAuthProperty,
} from '@activepieces/pieces-framework';
import {
DedupeStrategy,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import { TotalCMSAuthType, cmsAuth } from '../auth';
import { getContent } from '../api';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof cmsAuth>,
{ slug: string }
> = {
strategy: DedupeStrategy.LAST_ITEM,
items: async ({ auth, propsValue }) => {
const slug = propsValue.slug;
const posts = await getContent(auth, 'blog', slug);
return posts.data.map((post: { permalink: string }) => ({
id: post.permalink,
data: post,
}));
},
};
export const newBlogPost = createTrigger({
name: 'new_blog_post',
displayName: 'New Blog Post',
description: 'Triggers when a new blog post is published',
type: TriggerStrategy.POLLING,
props: {
slug: Property.ShortText({
displayName: 'CMS ID',
description: 'The CMS ID of the content to retrieve',
required: true,
}),
},
sampleData: {},
onEnable: async (context) => {
await pollingHelper.onEnable(polling, {
auth: context.auth as TotalCMSAuthType,
store: context.store,
propsValue: context.propsValue,
});
},
onDisable: async (context) => {
await pollingHelper.onDisable(polling, {
auth: context.auth as TotalCMSAuthType,
store: context.store,
propsValue: context.propsValue,
});
},
run: async (context) => {
return await pollingHelper.poll(polling, {
auth: context.auth as TotalCMSAuthType,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
test: async (context) => {
return await pollingHelper.test(polling, {
auth: context.auth as TotalCMSAuthType,
store: context.store,
propsValue: context.propsValue,
files: context.files,
});
},
});