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,62 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { rabbitmqAuth } from '../..';
import { rabbitmqConnect } from '../common';
export const sendMessageToExchange = createAction({
auth: rabbitmqAuth,
name: 'sendMessageToExchange',
displayName: 'sendMessageToExchange',
description: 'Send a message on a RabbitMQ exchange',
props: {
exchange: Property.ShortText({
displayName: 'Exchange',
description: 'The name of the exchange to send the message to',
required: true,
}),
routingKey: Property.ShortText({
displayName: 'Routing Key (Optional)',
description: 'The routing key to use when sending the message',
required: false,
defaultValue: '',
}),
data: Property.Json({
displayName: 'Data',
description: 'The data to send',
required: true,
defaultValue: {
"key": "value",
"nested": { "key": "value" },
"array": ["value1", "value2"]
},
}),
},
async run(context) {
let connection;
let channel;
try {
const exchange = context.propsValue.exchange;
const routingKey = context.propsValue.routingKey || '';
connection = await rabbitmqConnect(context.auth.props);
channel = await connection.createChannel();
await channel.checkExchange(exchange);
const result = channel.publish(
exchange,
routingKey,
Buffer.from(JSON.stringify(context.propsValue.data))
);
if (!result) {
throw new Error('Failed to send message to exchange');
}
return result;
} finally {
if (channel)
await channel.close();
if (connection)
await connection.close();
}
}
});

View File

@@ -0,0 +1,55 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { rabbitmqAuth } from '../..';
import { rabbitmqConnect } from '../common';
export const sendMessageToQueue = createAction({
auth: rabbitmqAuth,
name: 'sendMessageToQueue',
displayName: 'sendMessageToQueue',
description: 'Send a message on a RabbitMQ queue',
props: {
queue: Property.ShortText({
displayName: 'Queue',
description: 'The name of the exchange to send the message to',
required: true,
}),
data: Property.Json({
displayName: 'Data',
description: 'The data to send',
required: true,
defaultValue: {
"key": "value",
"nested": { "key": "value" },
"array": ["value1", "value2"]
},
}),
},
async run(context) {
const queue = context.propsValue.queue;
let connection;
let channel;
try {
connection = await rabbitmqConnect(context.auth.props);
channel = await connection.createChannel();
await channel.checkQueue(queue);
const result = channel.sendToQueue(
queue,
Buffer.from(JSON.stringify(context.propsValue.data))
);
if (!result) {
throw new Error('Failed to send message to exchange');
}
return result;
} finally {
if (channel) {
await channel.close();
}
if (connection) {
await connection.close();
}
}
}
});

View File

@@ -0,0 +1,24 @@
import { PiecePropValueSchema } from '@activepieces/pieces-framework';
import { rabbitmqAuth } from '../..';
import amqp, { ChannelModel, Connection } from 'amqplib';
export async function rabbitmqConnect(
auth: PiecePropValueSchema<typeof rabbitmqAuth>,
): Promise<ChannelModel> {
return amqp.connect(createAmqpURI(auth), (err: Error, conn: Connection) => {
if (err) {
throw err;
}
return conn;
});
}
function createAmqpURI(auth: PiecePropValueSchema<typeof rabbitmqAuth>): string {
const uri = `amqp://${auth.username}:${auth.password}@${auth.host}:${auth.port}`;
if (!auth.vhost) {
return uri;
}
return `${uri}/${auth.vhost}`;
}

View File

@@ -0,0 +1,88 @@
import {
createTrigger,
TriggerStrategy,
PiecePropValueSchema,
Property,
AppConnectionValueForAuthProperty,
} from '@activepieces/pieces-framework';
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
import { rabbitmqAuth } from '../../index';
import { rabbitmqConnect } from '../common';
import dayjs from 'dayjs';
const polling: Polling<AppConnectionValueForAuthProperty<typeof rabbitmqAuth>, {
queue: string,
maxMessagesPerPoll: number,
}> = {
strategy: DedupeStrategy.LAST_ITEM,
items: async ({ auth, propsValue }) => {
const connection = await rabbitmqConnect(auth.props);
const channel = await connection.createChannel();
const messages = [];
try {
const queueInfo = await channel.checkQueue(propsValue.queue);
if (queueInfo.messageCount === 0) {
return [];
}
for (let i = 0; i < propsValue.maxMessagesPerPoll; i++) {
const message = await channel.get(propsValue.queue);
if (!message) {
break;
}
messages.push({
id: dayjs().toISOString(),
data: JSON.parse(message.content.toString()),
});
channel.ack(message);
}
} finally {
await channel.close();
await connection.close();
}
return messages;
},
};
export const messageReceived = createTrigger({
auth: rabbitmqAuth,
name: 'messageReceived',
displayName: 'Message Received',
description: 'Triggers when a message is received on a RabbitMQ queue',
props: {
queue: Property.ShortText({
displayName: 'Queue',
description: 'The name of the queue to listen to',
required: true,
}),
maxMessagesPerPoll: Property.Number({
displayName: 'Max Messages Per Poll',
description: 'The maximum number of messages to fetch per poll',
required: true,
defaultValue: 50,
}),
},
sampleData: {},
type: TriggerStrategy.POLLING,
async test(context) {
const { store, auth, propsValue } = context;
return await pollingHelper.test(polling, { store, auth, propsValue, files: context.files });
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, { store, auth, propsValue });
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, { store, auth, propsValue });
},
async run(context) {
const { store, auth, propsValue } = context;
return await pollingHelper.poll(polling, { store, auth, propsValue, files: context.files });
},
});