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,60 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import jimp from 'jimp';
export const compressImage = createAction({
name: 'compress_image',
description: 'Compresses an image',
displayName: 'Compresses an image',
props: {
image: Property.File({
displayName: 'Image',
required: true,
}),
quality: Property.StaticDropdown({
displayName: 'Quality',
description:
'Specifies the quality of the image after compression (0-100).',
required: true,
options: {
options: [
{ label: 'High Quality', value: 90 },
{ label: 'Lossy Quality', value: 60 },
],
},
}),
format: Property.StaticDropdown({
displayName: 'Format',
description: 'Specifies the format of the image after compression.',
required: true,
options: {
options: [
{ label: 'JPG', value: 'jpg' },
{ label: 'PNG', value: 'png' },
],
},
}),
resultFileName: Property.ShortText({
displayName: 'Result File Name',
description:
'Specifies the output file name for the result image (without extension).',
required: false,
}),
},
async run(context) {
const image = await jimp.read(context.propsValue.image.data);
image.quality(context.propsValue.quality);
const imageBuffer = await image.getBufferAsync(image.getMIME());
const imageReference = await context.files.write({
fileName:
(context.propsValue.resultFileName ?? 'image') +
'.' +
context.propsValue.format,
data: imageBuffer,
});
return imageReference;
},
});

View File

@@ -0,0 +1,65 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import jimp from 'jimp';
export const cropImage = createAction({
name: 'crop_image',
description: 'Crops an image',
displayName: 'Crop an image',
props: {
image: Property.File({
displayName: 'Image',
required: true,
}),
left: Property.Number({
displayName: 'Left',
description:
'Specifies the horizontal position, indicating where the cropping starts from the left side of the image.',
required: true,
}),
top: Property.Number({
displayName: 'Top',
description:
'Represents the vertical position, indicating the starting point from the top of the image.',
required: true,
}),
width: Property.Number({
displayName: 'Width',
description: 'Determines the horizontal size of the cropped area.',
required: true,
}),
height: Property.Number({
displayName: 'Height',
description: 'Determines the vertical size of the cropped area.',
required: true,
}),
resultFileName: Property.ShortText({
displayName: 'Result File Name',
description:
'Specifies the output file name for the cropped image (without extension).',
required: false,
}),
},
async run(context) {
const image = await jimp.read(context.propsValue.image.data);
await image.crop(
context.propsValue.left,
context.propsValue.top,
context.propsValue.width,
context.propsValue.height
);
const imageBuffer = await image.getBufferAsync(image.getMIME());
const fileName =
(context.propsValue.resultFileName ?? 'image') +
'.' +
image.getExtension();
const imageReference = await context.files.write({
fileName: fileName,
data: imageBuffer,
});
return imageReference;
},
});

View File

@@ -0,0 +1,18 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import * as ExifReader from 'exifreader';
export const getMetaData = createAction({
name: 'get_meta_data',
description: 'Gets metadata from an image',
displayName: 'Get image metadata',
props: {
image: Property.File({
displayName: 'Image',
required: true,
}),
},
async run(context) {
const tags = await ExifReader.load(context.propsValue.image.data);
return tags;
},
});

View File

@@ -0,0 +1,32 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import mime from 'mime-types';
export const imageToBase64 = createAction({
name: 'image_to_base64',
description: 'Converts an image to an url-like Base64 string',
displayName: 'Image to Base64',
props: {
image: Property.File({
displayName: 'Image',
description: 'The image to convert',
required: true,
}),
override_mime_type: Property.ShortText({
displayName: 'Override mime type',
description:
'The mime type to use when converting the image. In case you want to override the default mime type. Example image/png',
required: false,
}),
},
async run(context) {
const image = context.propsValue.image;
const mimeType = mime.lookup(
image.extension ? image.extension : 'image/png'
);
const actualMimeType = context.propsValue.override_mime_type
? context.propsValue.override_mime_type
: mimeType;
return `data:${actualMimeType};base64,${image.base64}`;
},
});

View File

@@ -0,0 +1,54 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import jimp from 'jimp';
export const resizeImage = createAction({
name: 'resize_image',
description: 'Resizes an image',
displayName: 'Resize an image',
props: {
image: Property.File({
displayName: 'Image',
required: true,
}),
width: Property.Number({
displayName: 'Width',
description: 'Specifies the width of the image.',
required: true,
}),
height: Property.Number({
displayName: 'Height',
description: 'Specifies the height of the image.',
required: true,
}),
aspectRatio: Property.Checkbox({
displayName: 'Maintain aspect ratio for height',
required: false,
defaultValue: false,
}),
resultFileName: Property.ShortText({
displayName: 'Result File Name',
description:
'Specifies the output file name for the result image (without extension).',
required: false,
}),
},
async run(context) {
const image = await jimp.read(context.propsValue.image.data);
await image.resize(
context.propsValue.width,
context.propsValue.aspectRatio ? jimp.AUTO : context.propsValue.height
);
const imageBuffer = await image.getBufferAsync(image.getMIME());
const imageReference = await context.files.write({
fileName:
(context.propsValue.resultFileName ?? 'image') +
'.' +
image.getExtension(),
data: imageBuffer,
});
return imageReference;
},
});

View File

@@ -0,0 +1,49 @@
import { Property, createAction } from '@activepieces/pieces-framework';
import jimp from 'jimp';
export const rotateImage = createAction({
name: 'rotate_image',
description: 'Rotates an image',
displayName: 'Rotate an image',
props: {
image: Property.File({
displayName: 'Image',
required: true,
}),
degree: Property.StaticDropdown({
displayName: 'Degree',
description:
'Specifies the degree of clockwise rotation applied to the image.',
required: true,
options: {
options: [
{ value: 90, label: '90°' },
{ value: 180, label: '180°' },
{ value: 270, label: '270°' },
],
},
}),
resultFileName: Property.ShortText({
displayName: 'Result File Name',
description:
'Specifies the output file name for the result image (without extension).',
required: false,
}),
},
async run(context) {
const image = await jimp.read(context.propsValue.image.data);
await image.rotate(-context.propsValue.degree);
const imageBuffer = await image.getBufferAsync(image.getMIME());
const imageReference = await context.files.write({
fileName:
(context.propsValue.resultFileName ?? 'image') +
'.' +
image.getExtension(),
data: imageBuffer,
});
return imageReference;
},
});