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,12 @@
{
"RSS Feed": "RSS Feed",
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
"New Item In Feed": "New Item In Feed",
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
"RSS Feed URL": "RSS Feed URL",
"RSS Feed URLs": "RSS Feed URLs",
"Single RSS feed URL": "Single RSS feed URL",
"List of RSS feed URLs": "List of RSS feed URLs"
}

View File

@@ -0,0 +1,11 @@
{
"Stay updated with RSS feeds": "Mit RSS-Feeds auf dem Laufenden bleiben",
"New Item In Feed": "Neues Element im Feed",
"New Items in Multiple Feeds": "Neue Artikel in mehreren Feeds",
"Runs when a new item is added in the RSS feed": "Läuft wenn ein neuer Eintrag im RSS-Feed hinzugefügt wird",
"Runs when a new item is added in one of the RSS feed": "Läuft wenn ein neuer Eintrag in einem RSS-Feed hinzugefügt wird",
"RSS Feed URL": "RSS-Feed-URL",
"RSS Feed URLs": "RSS-Feed URLs",
"Single RSS feed URL": "Einzelne RSS-Feed-URL",
"List of RSS feed URLs": "Liste der RSS-Feed-URLs"
}

View File

@@ -0,0 +1,11 @@
{
"Stay updated with RSS feeds": "Mantente actualizado con RSS feeds",
"New Item In Feed": "Nuevo elemento en la alimentación",
"New Items in Multiple Feeds": "Nuevos artículos en Múltiples Ingresos",
"Runs when a new item is added in the RSS feed": "Ejecuta cuando se añade un nuevo elemento en el feed RSS",
"Runs when a new item is added in one of the RSS feed": "Ejecuta cuando se añade un nuevo elemento en uno de los canales RSS",
"RSS Feed URL": "URL del feed RSS",
"RSS Feed URLs": "URL del feed RSS",
"Single RSS feed URL": "URL única de fuente RSS",
"List of RSS feed URLs": "Lista de URLs de fuente RSS"
}

View File

@@ -0,0 +1,11 @@
{
"Stay updated with RSS feeds": "Restez à jour avec les fils RSS",
"New Item In Feed": "Nouvel élément dans le flux",
"New Items in Multiple Feeds": "Nouveaux éléments dans plusieurs flux",
"Runs when a new item is added in the RSS feed": "Exécute lorsqu'un nouvel élément est ajouté dans le flux RSS",
"Runs when a new item is added in one of the RSS feed": "Exécute lorsqu'un nouvel élément est ajouté dans un flux RSS",
"RSS Feed URL": "URL du flux RSS",
"RSS Feed URLs": "URL du flux RSS",
"Single RSS feed URL": "URL du flux RSS unique",
"List of RSS feed URLs": "Liste des URLs de flux RSS"
}

View File

@@ -0,0 +1,12 @@
{
"RSS Feed": "RSS Feed",
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
"New Item In Feed": "New Item In Feed",
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
"RSS Feed URL": "RSS Feed URL",
"RSS Feed URLs": "RSS Feed URLs",
"Single RSS feed URL": "Single RSS feed URL",
"List of RSS feed URLs": "List of RSS feed URLs"
}

View File

@@ -0,0 +1,12 @@
{
"RSS Feed": "RSS Feed",
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
"New Item In Feed": "New Item In Feed",
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
"RSS Feed URL": "RSS Feed URL",
"RSS Feed URLs": "RSS Feed URLs",
"Single RSS feed URL": "Single RSS feed URL",
"List of RSS feed URLs": "List of RSS feed URLs"
}

View File

@@ -0,0 +1,11 @@
{
"Stay updated with RSS feeds": "RSSフィードを更新してください",
"New Item In Feed": "フィード内の新しいアイテム",
"New Items in Multiple Feeds": "複数のフィードに新しい項目",
"Runs when a new item is added in the RSS feed": "RSS フィードに新しいアイテムが追加されたときに実行されます。",
"Runs when a new item is added in one of the RSS feed": "RSS フィードのいずれかに新しいアイテムが追加されたときに実行されます。",
"RSS Feed URL": "RSSフィードのURL",
"RSS Feed URLs": "RSSフィードのURL",
"Single RSS feed URL": "Single RSS feed URL",
"List of RSS feed URLs": "RSSフィードURL一覧"
}

View File

@@ -0,0 +1,11 @@
{
"Stay updated with RSS feeds": "Blijf op de hoogte van RSS-feeds",
"New Item In Feed": "Nieuw item in feed",
"New Items in Multiple Feeds": "Nieuwe items in meerdere feeds",
"Runs when a new item is added in the RSS feed": "Voert uit wanneer een nieuw item wordt toegevoegd in de RSS-feed",
"Runs when a new item is added in one of the RSS feed": "Voert uit wanneer een nieuw item wordt toegevoegd in een van de RSS-feed",
"RSS Feed URL": "RSS Feed URL",
"RSS Feed URLs": "RSS Feed URL's",
"Single RSS feed URL": "Single RSS feed URL",
"List of RSS feed URLs": "Lijst van RSS feed URL's"
}

View File

@@ -0,0 +1,11 @@
{
"Stay updated with RSS feeds": "Mantenha-se atualizado com feeds RSS",
"New Item In Feed": "Novo item no Feed",
"New Items in Multiple Feeds": "Novos itens em Múltiplos Feeds",
"Runs when a new item is added in the RSS feed": "Executa quando um novo item é adicionado no feed RSS",
"Runs when a new item is added in one of the RSS feed": "Executa quando um novo item é adicionado em um dos feeds RSS",
"RSS Feed URL": "URL do Feed RSS",
"RSS Feed URLs": "URLs do Feed RSS",
"Single RSS feed URL": "Única URL de feed RSS",
"List of RSS feed URLs": "Lista de URLs de feed RSS"
}

View File

@@ -0,0 +1,12 @@
{
"RSS Feed": "RSS-канал",
"Stay updated with RSS feeds": "Будьте в курсе RSS-каналов",
"New Item In Feed": "Новый элемент в ленте новостей",
"New Items in Multiple Feeds": "Новые элементы в нескольких каналах",
"Runs when a new item is added in the RSS feed": "Выполняется при добавлении нового элемента в RSS-канал",
"Runs when a new item is added in one of the RSS feed": "Выполняется при добавлении нового элемента в один из RSS-каналов",
"RSS Feed URL": "URL RSS",
"RSS Feed URLs": "URL-адреса RSS",
"Single RSS feed URL": "URL одного канала RSS",
"List of RSS feed URLs": "Список URL RSS-ленты"
}

View File

@@ -0,0 +1,11 @@
{
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
"New Item In Feed": "New Item In Feed",
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
"RSS Feed URL": "RSS Feed URL",
"RSS Feed URLs": "RSS Feed URLs",
"Single RSS feed URL": "Single RSS feed URL",
"List of RSS feed URLs": "List of RSS feed URLs"
}

View File

@@ -0,0 +1,12 @@
{
"RSS Feed": "RSS Feed",
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
"New Item In Feed": "New Item In Feed",
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
"RSS Feed URL": "RSS Feed URL",
"RSS Feed URLs": "RSS Feed URLs",
"Single RSS feed URL": "Single RSS feed URL",
"List of RSS feed URLs": "List of RSS feed URLs"
}

View File

@@ -0,0 +1,11 @@
{
"Stay updated with RSS feeds": "Stay updated with RSS feeds",
"New Item In Feed": "New Item In Feed",
"New Items in Multiple Feeds": "New Items in Multiple Feeds",
"Runs when a new item is added in the RSS feed": "Runs when a new item is added in the RSS feed",
"Runs when a new item is added in one of the RSS feed": "Runs when a new item is added in one of the RSS feed",
"RSS Feed URL": "RSS Feed URL",
"RSS Feed URLs": "RSS Feed URLs",
"Single RSS feed URL": "Single RSS feed URL",
"List of RSS feed URLs": "List of RSS feed URLs"
}

View File

@@ -0,0 +1,18 @@
import { PieceAuth, createPiece } from '@activepieces/pieces-framework';
import { rssNewItemTrigger } from './lib/triggers/new-item-trigger';
import { rssNewItemListTrigger } from './lib/triggers/new-item-list-triggers';
export const rssFeed = createPiece({
displayName: 'RSS Feed',
description: 'Stay updated with RSS feeds',
authors: ["Abdallah-Alwarawreh","kishanprmr","khaledmashaly","abuaboud", "Kevinyu-alan"],
minimumSupportedRelease: '0.30.0',
logoUrl: 'https://cdn.activepieces.com/pieces/rss.png',
categories: [],
auth: PieceAuth.None(),
actions: [],
triggers: [
rssNewItemTrigger,
rssNewItemListTrigger
],
});

View File

@@ -0,0 +1,14 @@
// Some RSS feeds use the id field, some use the guid field, and some use neither.
export function getId(item: { id: string; guid: string }) {
if (item === undefined) {
return undefined;
}
if (item.guid) {
return item.guid;
}
if (item.id) {
return item.id;
}
return JSON.stringify(item);
}

View File

@@ -0,0 +1,14 @@
import { Property } from '@activepieces/pieces-framework';
export const rssFeedUrl = Property.ShortText({
displayName: 'RSS Feed URL',
description: 'Single RSS feed URL',
required: true,
});
export const rssFeedUrls = Property.Array({
displayName: 'RSS Feed URLs',
description: 'List of RSS feed URLs',
required: true,
defaultValue: [],
});

View File

@@ -0,0 +1,113 @@
export const sampleData = {
title: 'AWS Cloud Quest: Container Services',
description:
'<p>This is the DIY challenge of the Container Services in AWS Cloud Quest.</p>\n\n<p></ol>',
summary:
'<p>This is the DIY challenge of the Container Services in AWS Cloud Quest.</ol>',
date: '2023-03-08T21:57:48.000Z',
pubdate: '2023-03-08T21:57:48.000Z',
pubDate: '2023-03-08T21:57:48.000Z',
link: 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
guid: 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
author: 'architec',
comments: null,
origlink: null,
image: {},
source: {},
categories: ['aws'],
enclosures: [],
'rss:@': {},
'rss:title': {
'@': {},
'#': 'AWS Cloud Quest: Container Services',
},
'dc:creator': {
'@': {},
'#': 'architec',
},
'rss:pubdate': {
'@': {},
'#': 'Wed, 08 Mar 2023 21:57:48 +0000',
},
'rss:link': {
'@': {},
'#': 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
},
permalink: 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
'rss:guid': {
'@': {},
'#': 'https://dev.to/arc/aws-cloud-quest-container-services-1hi7',
},
'rss:description': {
'@': {},
'#': '<p>This is the DIY challenge of the Container Services in AWS Cloud Quest.</p>\n\n<p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pZTG6rga--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/993bebzvmiomak17lm98.png" class="article-body-image-wrapper"><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pZTG6rga--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/993bebzvmiomak17lm98.png" alt="Image description" width="880" height="419"></a></p>\n\n<h3>\n \n \n DIY Steps:\n</h3>\n\n<ol>\n<li>Repeat step 28-42</li>\n</ol>',
},
'rss:category': {
'@': {},
'#': 'aws',
},
meta: {
'#ns': [
{
'xmlns:atom': 'http://www.w3.org/2005/Atom',
},
{
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
},
],
'@': [
{
'xmlns:atom': 'http://www.w3.org/2005/Atom',
},
{
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
},
],
'#xml': {
version: '1.0',
encoding: 'UTF-8',
},
'#type': 'rss',
'#version': '2.0',
title: 'DEV Community',
description: 'The most recent home feed on DEV Community.',
date: null,
pubdate: null,
pubDate: null,
link: 'https://dev.to/',
xmlurl: 'https://dev.to/feed/',
xmlUrl: 'https://dev.to/feed/',
author: null,
language: 'en',
favicon: null,
copyright: null,
generator: null,
cloud: {},
image: {},
categories: [],
'rss:@': {},
'rss:title': {
'@': {},
'#': 'DEV Community',
},
'rss:description': {
'@': {},
'#': 'The most recent home feed on DEV Community.',
},
'rss:link': {
'@': {},
'#': 'https://dev.to/',
},
'atom:link': {
'@': {
rel: 'self',
type: 'application/rss+xml',
href: 'https://dev.to/feed/',
},
},
'rss:language': {
'@': {},
'#': 'en',
},
},
}

View File

@@ -0,0 +1,179 @@
import {
DedupeStrategy,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import {
AppConnectionValueForAuthProperty,
PieceAuth,
PieceAuthProperty,
PiecePropValueSchema,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import { rssFeedUrls } from '../common/props';
import FeedParser from 'feedparser';
import axios from 'axios';
import { isNil } from '@activepieces/shared';
import dayjs from 'dayjs';
import { getId } from '../common/getId';
import { sampleData } from '../common/sampleData';
type PollingProps = {
rss_feed_urls: string[];
};
export const rssNewItemListTrigger = createTrigger({
name: 'new-item-list',
displayName: 'New Items in Multiple Feeds',
description: 'Runs when a new item is added in one of the RSS feed',
type: TriggerStrategy.POLLING,
sampleData: sampleData,
auth: PieceAuth.None(),
props: {
rss_feed_urls: rssFeedUrls,
},
async test({ auth, propsValue, store, files }): Promise<unknown[]> {
return await pollingHelper.test(polling, {
auth,
store: store,
propsValue: propsValue as PollingProps,
files: files,
});
},
async onEnable({ auth, propsValue, store }): Promise<void> {
await pollingHelper.onEnable(polling, {
auth,
store: store,
propsValue: propsValue as PollingProps,
});
},
async onDisable({ auth, propsValue, store }): Promise<void> {
const lastFetchDate = await store.get<number>('_lastRssPublishDate');
if (!isNil(lastFetchDate)) {
await store.delete('_lastRssPublishDate');
}
await pollingHelper.onDisable(polling, {
auth,
store: store,
propsValue: propsValue as PollingProps,
});
},
async run({ auth, propsValue, store, files }): Promise<unknown[]> {
const lastFetchDate = await store.get<number>('_lastRssPublishDate');
const newItems = (
await pollingHelper.poll(polling, {
auth,
store: store,
propsValue: propsValue as PollingProps,
files: files,
})
).filter((f) => {
if (isNil(lastFetchDate)) {
return true;
}
const newItem = f as { pubdate: string; pubDate: string };
const newDate = newItem.pubdate ?? newItem.pubDate;
if (isNil(newDate)) {
return true;
}
return dayjs(newDate).unix() > lastFetchDate;
});
let newFetchDateUnix = lastFetchDate;
for (const item of newItems) {
const newItem = item as { pubdate: string; pubDate: string };
const newDate = newItem.pubdate ?? newItem.pubDate;
if (!isNil(newDate)) {
const newDateUnix = dayjs(newDate).unix();
if (isNil(newFetchDateUnix) || newDateUnix > newFetchDateUnix) {
newFetchDateUnix = newDateUnix;
}
}
}
if (!isNil(newFetchDateUnix)) {
await store.put('_lastRssPublishDate', newFetchDateUnix);
}
return newItems.sort((a, b) => {
const aDate =
(a as { pubdate: string; pubDate: string }).pubdate ??
(a as { pubdate: string; pubDate: string }).pubDate;
const bDate =
(b as { pubdate: string; pubDate: string }).pubdate ??
(b as { pubdate: string; pubDate: string }).pubDate;
if (aDate && bDate) {
const aUnix = dayjs(aDate).unix();
const bUnix = dayjs(bDate).unix();
if (aUnix === bUnix) {
return newItems.indexOf(a) - newItems.indexOf(b);
} else {
return bUnix - aUnix;
}
} else {
return newItems.indexOf(a) - newItems.indexOf(b);
}
});
},
});
async function getRssItemsFromMultipleUrls(urls: string[]): Promise<any[]> {
const allItems: any[] = [];
await Promise.all(
urls.map(async ( url ) => {
try {
const items = await getRssItems(url);
allItems.push(...items);
} catch (error) {
console.error(`Error fetching RSS feed from ${url}:`, error);
}
})
);
return allItems;
}
const polling: Polling<AppConnectionValueForAuthProperty<undefined>, PollingProps> = {
strategy: DedupeStrategy.LAST_ITEM,
items: async ({ store, propsValue }) => {
const items = await getRssItemsFromMultipleUrls(propsValue.rss_feed_urls);
return items.map((item) => ({
id: getId(item),
data: item,
}));
},
};
function getRssItems(url: string): Promise<any[]> {
return new Promise((resolve, reject) => {
axios
.get(url, {
responseType: 'stream',
})
.then((response) => {
const feedparser = new FeedParser({
addmeta: true,
});
response.data.pipe(feedparser);
const items: any[] = [];
feedparser.on('readable', () => {
let item = feedparser.read();
while (item) {
items.push(item);
item = feedparser.read();
}
});
feedparser.on('end', () => {
resolve(items);
});
feedparser.on('error', (error: any) => {
reject(error);
});
})
.catch((error) => {
reject(error);
});
});
}

View File

@@ -0,0 +1,170 @@
import {
DedupeStrategy,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import {
AppConnectionValueForAuthProperty,
PieceAuth,
PieceAuthProperty,
PiecePropValueSchema,
Store,
StoreScope,
TriggerStrategy,
createTrigger,
} from '@activepieces/pieces-framework';
import { rssFeedUrl } from '../common/props';
import FeedParser from 'feedparser';
import axios from 'axios';
import { isNil } from '@activepieces/shared';
import dayjs from 'dayjs';
import { getId } from '../common/getId';
import { sampleData } from '../common/sampleData';
export const rssNewItemTrigger = createTrigger({
name: 'new-item',
displayName: 'New Item In Feed',
description: 'Runs when a new item is added in the RSS feed',
type: TriggerStrategy.POLLING,
sampleData: sampleData,
auth: PieceAuth.None(),
props: {
rss_feed_url: rssFeedUrl,
},
async test({ auth, propsValue, store, files }): Promise<unknown[]> {
return await pollingHelper.test(polling, {
auth,
store: store,
propsValue: propsValue,
files: files,
});
},
async onEnable({ auth, propsValue, store }): Promise<void> {
await pollingHelper.onEnable(polling, {
auth,
store: store,
propsValue: propsValue,
});
},
async onDisable({ auth, propsValue, store }): Promise<void> {
const lastFetchDate = await store.get<number>('_lastRssPublishDate');
if (!isNil(lastFetchDate)) {
await store.delete('_lastRssPublishDate');
}
await pollingHelper.onDisable(polling, {
auth,
store: store,
propsValue: propsValue,
});
},
async run({ auth, propsValue, store, files }): Promise<unknown[]> {
const lastFetchDate = await store.get<number>('_lastRssPublishDate');
const newItems = (
await pollingHelper.poll(polling, {
auth,
store: store,
propsValue: propsValue,
files: files,
})
).filter((f) => {
if (isNil(lastFetchDate)) {
return true;
}
const newItem = f as { pubdate: string; pubDate: string };
const newDate = newItem.pubdate ?? newItem.pubDate;
if (isNil(newDate)) {
return true;
}
return dayjs(newDate).unix() > lastFetchDate;
});
let newFetchDateUnix = lastFetchDate;
for (const item of newItems) {
const newItem = item as { pubdate: string; pubDate: string };
const newDate = newItem.pubdate ?? newItem.pubDate;
if (!isNil(newDate)) {
const newDateUnix = dayjs(newDate).unix();
if (isNil(newFetchDateUnix) || newDateUnix > newFetchDateUnix) {
newFetchDateUnix = newDateUnix;
}
}
}
if (!isNil(newFetchDateUnix)) {
await store.put('_lastRssPublishDate', newFetchDateUnix);
}
return newItems.sort((a, b) => {
const aDate =
(a as { pubdate: string; pubDate: string }).pubdate ??
(a as { pubdate: string; pubDate: string }).pubDate;
const bDate =
(b as { pubdate: string; pubDate: string }).pubdate ??
(b as { pubdate: string; pubDate: string }).pubDate;
if (aDate && bDate) {
const aUnix = dayjs(aDate).unix();
const bUnix = dayjs(bDate).unix();
if (aUnix === bUnix) {
return newItems.indexOf(a) - newItems.indexOf(b);
} else {
return bUnix - aUnix;
}
} else {
return newItems.indexOf(a) - newItems.indexOf(b);
}
});
},
});
const polling: Polling<
AppConnectionValueForAuthProperty<undefined>,
{ rss_feed_url: string }
> = {
strategy: DedupeStrategy.LAST_ITEM,
items: async ({
propsValue,
}: {
store: Store;
propsValue: { rss_feed_url: string };
}) => {
const items = await getRssItems(propsValue.rss_feed_url);
return items.map((item) => ({
id: getId(item),
data: item,
}));
},
};
function getRssItems(url: string): Promise<any[]> {
return new Promise((resolve, reject) => {
axios
.get(url, {
responseType: 'stream',
})
.then((response) => {
const feedparser = new FeedParser({
addmeta: true,
});
response.data.pipe(feedparser);
const items: any[] = [];
feedparser.on('readable', () => {
let item = feedparser.read();
while (item) {
items.push(item);
item = feedparser.read();
}
});
feedparser.on('end', () => {
resolve(items);
});
feedparser.on('error', (error: any) => {
reject(error);
});
})
.catch((error) => {
reject(error);
});
});
}