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:
@@ -0,0 +1,68 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { placidAuth } from '../../index';
|
||||
import { HttpMethod, httpClient, AuthenticationType } from '@activepieces/pieces-common';
|
||||
import { PLACID_BASE_URL } from '../common';
|
||||
import FormData from 'form-data';
|
||||
|
||||
export const convertFileToUrl = createAction({
|
||||
auth: placidAuth,
|
||||
name: 'convert_file_to_url',
|
||||
displayName: 'Convert File to URL',
|
||||
description: 'Convert uploaded file(s) into media URL(s) consumable by Placid templates.',
|
||||
props: {
|
||||
file: Property.File({
|
||||
displayName: 'File',
|
||||
description: 'The file to convert to a URL.',
|
||||
required: true,
|
||||
}),
|
||||
filename: Property.ShortText({
|
||||
displayName: 'Filename',
|
||||
description: 'Optional custom filename for the uploaded file.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { file, filename } = context.propsValue;
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
// Convert base64 to buffer for form-data
|
||||
const buffer = Buffer.from(file.base64, 'base64');
|
||||
formData.append('file', buffer, {
|
||||
filename: filename || file.filename,
|
||||
contentType: file.extension ? `application/${file.extension}` : 'application/octet-stream',
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.POST,
|
||||
url: `${PLACID_BASE_URL}/media`,
|
||||
body: formData,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: context.auth.secret_text,
|
||||
},
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
},
|
||||
});
|
||||
|
||||
return response.body;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(
|
||||
'File upload not supported. This feature may not be available for demo accounts or may require a paid Placid plan. Please check your account permissions.',
|
||||
);
|
||||
}
|
||||
if (error.response?.status === 413) {
|
||||
throw new Error('File too large. Please check Placid file size limits.');
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error(
|
||||
'File upload access forbidden. This feature may require additional permissions in your Placid account.',
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { placidAuth } from '../../index';
|
||||
import { PlacidClient } from '../common/client';
|
||||
import {
|
||||
imageTemplateDropdown,
|
||||
webhookProperty,
|
||||
createNowProperty,
|
||||
passthroughProperty,
|
||||
templateLayersProperty,
|
||||
} from '../common/props';
|
||||
import { isNil } from '@activepieces/shared';
|
||||
import { PlacidCreateImageRequest } from '../common';
|
||||
|
||||
export const createImage = createAction({
|
||||
auth: placidAuth,
|
||||
name: 'create_image',
|
||||
displayName: 'Create Image',
|
||||
description: 'Generates a dynamic image from a specified template using input data.',
|
||||
props: {
|
||||
template: imageTemplateDropdown,
|
||||
layers: templateLayersProperty('image'),
|
||||
outputDpi: Property.StaticDropdown({
|
||||
displayName: 'Output DPI',
|
||||
required: false,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: '72 DPI', value: 72 },
|
||||
{ label: '150 DPI', value: 150 },
|
||||
{ label: '300 DPI', value: 300 },
|
||||
],
|
||||
},
|
||||
}),
|
||||
outputFilename: Property.ShortText({
|
||||
displayName: 'Output File Name',
|
||||
required: false,
|
||||
}),
|
||||
webhook_success: webhookProperty,
|
||||
create_now: createNowProperty,
|
||||
passthrough: passthroughProperty,
|
||||
},
|
||||
async run(context) {
|
||||
const { template, webhook_success, create_now, passthrough, outputDpi, outputFilename } =
|
||||
context.propsValue;
|
||||
|
||||
const layers = context.propsValue.layers ?? {};
|
||||
|
||||
const modifiedLayers: Record<string, any> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(layers)) {
|
||||
if (value === '' || isNil(value)) continue;
|
||||
|
||||
const [mainKey, subKey] = key.split(':::');
|
||||
if (!mainKey || !subKey) continue;
|
||||
|
||||
if (!modifiedLayers[mainKey]) {
|
||||
modifiedLayers[mainKey] = {};
|
||||
}
|
||||
|
||||
modifiedLayers[mainKey][subKey] = value;
|
||||
}
|
||||
|
||||
const client = new PlacidClient(context.auth);
|
||||
|
||||
const modifications = {
|
||||
...(outputFilename && { filename: outputFilename }),
|
||||
...(outputDpi && { dpi: outputDpi }),
|
||||
};
|
||||
|
||||
const request: PlacidCreateImageRequest = {
|
||||
template_uuid: template,
|
||||
...(modifiedLayers && { layers: modifiedLayers }),
|
||||
...(webhook_success && { webhook_success }),
|
||||
...(create_now !== undefined && { create_now }),
|
||||
...(passthrough && { passthrough }),
|
||||
...(Object.keys(modifications).length && { modifications }),
|
||||
};
|
||||
|
||||
return await client.createImage(request);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { placidAuth } from '../../index';
|
||||
import { PlacidClient } from '../common/client';
|
||||
import {
|
||||
pdfTemplateDropdown,
|
||||
webhookProperty,
|
||||
createNowProperty,
|
||||
passthroughProperty,
|
||||
templateLayersProperty,
|
||||
} from '../common/props';
|
||||
import { isNil } from '@activepieces/shared';
|
||||
import { PlacidCreatePdfRequest } from '../common';
|
||||
|
||||
export const createPdf = createAction({
|
||||
auth: placidAuth,
|
||||
name: 'create_pdf',
|
||||
displayName: 'Create PDF',
|
||||
description: 'Generates a PDF document from a specified template.',
|
||||
props: {
|
||||
template: pdfTemplateDropdown,
|
||||
layers: templateLayersProperty('pdf'),
|
||||
outputDpi: Property.StaticDropdown({
|
||||
displayName: 'Output DPI',
|
||||
required: false,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{ label: '96 DPI', value: 96 },
|
||||
{ label: '150 DPI', value: 150 },
|
||||
{ label: '300 DPI', value: 300 },
|
||||
],
|
||||
},
|
||||
}),
|
||||
outputFilename: Property.ShortText({
|
||||
displayName: 'Output File Name',
|
||||
required: false,
|
||||
}),
|
||||
webhook_success: webhookProperty,
|
||||
create_now: createNowProperty,
|
||||
passthrough: passthroughProperty,
|
||||
},
|
||||
async run(context) {
|
||||
const { template, outputDpi, outputFilename, webhook_success, create_now, passthrough } =
|
||||
context.propsValue;
|
||||
|
||||
const layers = context.propsValue.layers ?? {};
|
||||
|
||||
const modifiedLayers: Record<string, any> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(layers)) {
|
||||
if (value === '' || isNil(value)) continue;
|
||||
|
||||
const [mainKey, subKey] = key.split(':::');
|
||||
if (!mainKey || !subKey) continue;
|
||||
|
||||
if (!modifiedLayers[mainKey]) {
|
||||
modifiedLayers[mainKey] = {};
|
||||
}
|
||||
|
||||
modifiedLayers[mainKey][subKey] = value;
|
||||
}
|
||||
|
||||
const client = new PlacidClient(context.auth);
|
||||
|
||||
const modifications = {
|
||||
...(outputFilename && { filename: outputFilename }),
|
||||
...(outputDpi && { dpi: outputDpi }),
|
||||
};
|
||||
|
||||
// PDFs require a pages array structure
|
||||
const request: PlacidCreatePdfRequest = {
|
||||
pages: [
|
||||
{
|
||||
template_uuid: template,
|
||||
...(modifiedLayers && { layers: modifiedLayers }),
|
||||
},
|
||||
],
|
||||
...(Object.keys(modifications).length && { modifications }),
|
||||
...(webhook_success && { webhook_success }),
|
||||
...(create_now !== undefined && { create_now }),
|
||||
...(passthrough && { passthrough }),
|
||||
};
|
||||
|
||||
return await client.createPdf(request);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { placidAuth } from '../../index';
|
||||
import { PlacidClient } from '../common/client';
|
||||
import {
|
||||
videoTemplateDropdown,
|
||||
webhookProperty,
|
||||
createNowProperty,
|
||||
passthroughProperty,
|
||||
templateLayersProperty,
|
||||
} from '../common/props';
|
||||
import { isNil } from '@activepieces/shared';
|
||||
import { PlacidCreateVideoRequest } from '../common';
|
||||
|
||||
export const createVideo = createAction({
|
||||
auth: placidAuth,
|
||||
name: 'create_video',
|
||||
displayName: 'Create Video',
|
||||
description: 'Produces a video based on a template.',
|
||||
props: {
|
||||
template: videoTemplateDropdown,
|
||||
layers: templateLayersProperty('video'),
|
||||
outputFps: Property.Number({
|
||||
displayName: 'Output FPS',
|
||||
required: false,
|
||||
defaultValue: 25,
|
||||
}),
|
||||
outputFilename: Property.ShortText({
|
||||
displayName: 'Output File Name',
|
||||
required: false,
|
||||
}),
|
||||
webhook_success: webhookProperty,
|
||||
create_now: createNowProperty,
|
||||
passthrough: passthroughProperty,
|
||||
},
|
||||
async run(context) {
|
||||
const { template, outputFps, outputFilename, webhook_success, create_now, passthrough } =
|
||||
context.propsValue;
|
||||
|
||||
const layers = context.propsValue.layers ?? {};
|
||||
|
||||
const modifiedLayers: Record<string, any> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(layers)) {
|
||||
if (value === '' || isNil(value)) continue;
|
||||
|
||||
const [mainKey, subKey] = key.split(':::');
|
||||
if (!mainKey || !subKey) continue;
|
||||
|
||||
if (!modifiedLayers[mainKey]) {
|
||||
modifiedLayers[mainKey] = {};
|
||||
}
|
||||
|
||||
modifiedLayers[mainKey][subKey] = value;
|
||||
}
|
||||
|
||||
const client = new PlacidClient(context.auth);
|
||||
|
||||
const modifications = {
|
||||
...(outputFilename && { filename: outputFilename }),
|
||||
...(outputFps && { fps: outputFps }),
|
||||
};
|
||||
|
||||
// Videos require a clips array structure
|
||||
const request: PlacidCreateVideoRequest = {
|
||||
clips: [
|
||||
{
|
||||
template_uuid: template,
|
||||
...(modifiedLayers && { layers: modifiedLayers }),
|
||||
},
|
||||
],
|
||||
...(Object.keys(modifications).length && { modifications }),
|
||||
...(webhook_success && { webhook_success }),
|
||||
...(create_now !== undefined && { create_now }),
|
||||
...(passthrough && { passthrough }),
|
||||
};
|
||||
|
||||
return await client.createVideo(request);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { placidAuth } from '../../index';
|
||||
import { PlacidClient } from '../common/client';
|
||||
|
||||
export const getImage = createAction({
|
||||
auth: placidAuth,
|
||||
name: 'get_image',
|
||||
displayName: 'Get Image',
|
||||
description: 'Retrieves a generated image by its ID.',
|
||||
props: {
|
||||
imageId: Property.ShortText({
|
||||
displayName: 'Image ID',
|
||||
description: 'The ID of the image to retrieve.',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { imageId } = context.propsValue;
|
||||
const client = new PlacidClient(context.auth);
|
||||
return await client.getImage(imageId);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { placidAuth } from '../../index';
|
||||
import { PlacidClient } from '../common/client';
|
||||
|
||||
export const getPdf = createAction({
|
||||
auth: placidAuth,
|
||||
name: 'get_pdf',
|
||||
displayName: 'Get PDF',
|
||||
description: 'Retrieves the generated PDF by its ID.',
|
||||
props: {
|
||||
pdfId: Property.ShortText({
|
||||
displayName: 'PDF ID',
|
||||
description: 'The ID of the PDF to retrieve',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { pdfId } = context.propsValue;
|
||||
const client = new PlacidClient(context.auth);
|
||||
return await client.getPdf(pdfId);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { placidAuth } from '../../index';
|
||||
import { PlacidClient } from '../common/client';
|
||||
|
||||
export const getVideo = createAction({
|
||||
auth: placidAuth,
|
||||
name: 'get_video',
|
||||
displayName: 'Get Video',
|
||||
description: 'Retrieves the generated video by its ID.',
|
||||
props: {
|
||||
videoId: Property.ShortText({
|
||||
displayName: 'Video ID',
|
||||
description: 'The ID of the video to retrieve.',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { videoId } = context.propsValue;
|
||||
const client = new PlacidClient(context.auth);
|
||||
return await client.getVideo(videoId);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,266 @@
|
||||
import { AuthenticationType, HttpMethod, httpClient } from '@activepieces/pieces-common';
|
||||
import {
|
||||
PLACID_BASE_URL,
|
||||
PlacidTemplate,
|
||||
PlacidImage,
|
||||
PlacidCreateImageRequest,
|
||||
PlacidCreatePdfRequest,
|
||||
PlacidCreateVideoRequest,
|
||||
} from './index';
|
||||
import { AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
|
||||
import { placidAuth } from '../..';
|
||||
|
||||
export class PlacidClient {
|
||||
constructor(private apiKey: AppConnectionValueForAuthProperty<typeof placidAuth>) {}
|
||||
|
||||
async listTemplates(): Promise<PlacidTemplate[]> {
|
||||
const templates = [];
|
||||
let nextUrl: string | undefined = `${PLACID_BASE_URL}/templates`;
|
||||
|
||||
do {
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: nextUrl,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey.secret_text,
|
||||
},
|
||||
});
|
||||
|
||||
const { data, links } = response.body as { data: PlacidTemplate[]; links: { next?: string } };
|
||||
templates.push(...data);
|
||||
|
||||
nextUrl = links.next;
|
||||
} while (nextUrl);
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
async getTemplate(templateId: string): Promise<PlacidTemplate> {
|
||||
const response = await httpClient.sendRequest<PlacidTemplate>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${PLACID_BASE_URL}/templates/${templateId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey.secret_text,
|
||||
},
|
||||
});
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async createImage(request: PlacidCreateImageRequest): Promise<PlacidImage> {
|
||||
const response = await httpClient.sendRequest<PlacidImage>({
|
||||
method: HttpMethod.POST,
|
||||
url: `${PLACID_BASE_URL}/images`,
|
||||
body: request,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey.secret_text,
|
||||
},
|
||||
});
|
||||
|
||||
const result = response.body;
|
||||
|
||||
// If create_now is true, poll until completion
|
||||
if (request.create_now && result.status === 'queued') {
|
||||
return await this.pollForCompletion(result.id, 'image');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getImage(imageId: string): Promise<PlacidImage> {
|
||||
try {
|
||||
const response = await httpClient.sendRequest<PlacidImage>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${PLACID_BASE_URL}/images/${imageId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey.secret_text,
|
||||
},
|
||||
});
|
||||
return response.body;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(
|
||||
`Image with ID ${imageId} not found. Please verify the image ID exists in your Placid account.`,
|
||||
);
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error(
|
||||
`Access forbidden for image ID ${imageId}. This image may not belong to your account or may have been deleted.`,
|
||||
);
|
||||
}
|
||||
if (error.response?.status === 500) {
|
||||
throw new Error(
|
||||
`Server error while retrieving image ID ${imageId}. Please try again later or contact support.`,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteImage(imageId: string): Promise<void> {
|
||||
await httpClient.sendRequest({
|
||||
method: HttpMethod.DELETE,
|
||||
url: `${PLACID_BASE_URL}/images/${imageId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey.secret_text,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async createPdf(request: PlacidCreatePdfRequest): Promise<PlacidImage> {
|
||||
const response = await httpClient.sendRequest<PlacidImage>({
|
||||
method: HttpMethod.POST,
|
||||
url: `${PLACID_BASE_URL}/pdfs`,
|
||||
body: request,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey.secret_text,
|
||||
},
|
||||
});
|
||||
|
||||
const result = response.body;
|
||||
|
||||
// If create_now is true, poll until completion
|
||||
if (request.create_now && result.status === 'queued') {
|
||||
return await this.pollForCompletion(result.id, 'pdf');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async createVideo(request: PlacidCreateVideoRequest): Promise<PlacidImage> {
|
||||
const response = await httpClient.sendRequest<PlacidImage>({
|
||||
method: HttpMethod.POST,
|
||||
url: `${PLACID_BASE_URL}/videos`,
|
||||
body: request,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey.secret_text,
|
||||
},
|
||||
});
|
||||
|
||||
const result = response.body;
|
||||
|
||||
// If create_now is true, poll until completion
|
||||
if (request.create_now && result.status === 'queued') {
|
||||
return await this.pollForCompletion(result.id, 'video');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async pollForCompletion(id: string, type: 'image' | 'pdf' | 'video'): Promise<PlacidImage> {
|
||||
const maxAttempts = 30; // 5 minutes max (10 second intervals)
|
||||
const interval = 10000; // 10 seconds
|
||||
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
try {
|
||||
// Wait before polling (except first attempt)
|
||||
if (attempt > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
|
||||
// Get current status
|
||||
let result: PlacidImage;
|
||||
switch (type) {
|
||||
case 'image':
|
||||
result = await this.getImage(id);
|
||||
break;
|
||||
case 'pdf':
|
||||
result = await this.getPdf(id);
|
||||
break;
|
||||
case 'video':
|
||||
result = await this.getVideo(id);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if completed
|
||||
if (result.status === 'finished') {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if failed
|
||||
if (result.status === 'error') {
|
||||
throw new Error(`${type} generation failed: ${result.error_message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
// Continue polling if still queued/processing
|
||||
} catch (error) {
|
||||
// If it's the last attempt, throw the error
|
||||
if (attempt === maxAttempts - 1) {
|
||||
throw new Error(`Polling timeout: ${type} generation took too long`);
|
||||
}
|
||||
// Otherwise, continue polling
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Polling timeout: ${type} generation took too long`);
|
||||
}
|
||||
|
||||
async getPdf(pdfId: string): Promise<PlacidImage> {
|
||||
try {
|
||||
const response = await httpClient.sendRequest<PlacidImage>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${PLACID_BASE_URL}/pdfs/${pdfId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey.secret_text,
|
||||
},
|
||||
});
|
||||
return response.body;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(
|
||||
`PDF with ID ${pdfId} not found. Please verify the PDF ID exists in your Placid account.`,
|
||||
);
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error(
|
||||
`Access forbidden for PDF ID ${pdfId}. This PDF may not belong to your account or may have been deleted.`,
|
||||
);
|
||||
}
|
||||
if (error.response?.status === 500) {
|
||||
throw new Error(
|
||||
`Server error while retrieving PDF ID ${pdfId}. Please try again later or contact support.`,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getVideo(videoId: string): Promise<PlacidImage> {
|
||||
try {
|
||||
const response = await httpClient.sendRequest<PlacidImage>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${PLACID_BASE_URL}/videos/${videoId}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: this.apiKey.secret_text,
|
||||
},
|
||||
});
|
||||
return response.body;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(
|
||||
`Video with ID ${videoId} not found. Please verify the video ID exists in your Placid account.`,
|
||||
);
|
||||
}
|
||||
if (error.response?.status === 403) {
|
||||
throw new Error(
|
||||
`Access forbidden for video ID ${videoId}. This video may not belong to your account or may have been deleted.`,
|
||||
);
|
||||
}
|
||||
if (error.response?.status === 500) {
|
||||
throw new Error(
|
||||
`Server error while retrieving video ID ${videoId}. Please try again later or contact support.`,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
export const PLACID_BASE_URL = 'https://api.placid.app/api/rest';
|
||||
|
||||
export interface PlacidTemplate {
|
||||
uuid: string;
|
||||
title: string;
|
||||
thumbnail?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
tags?: string[];
|
||||
custom_data?: any;
|
||||
collections?: any[];
|
||||
layers: PlacidLayer[];
|
||||
}
|
||||
|
||||
export interface PlacidLayer {
|
||||
name: string;
|
||||
type: 'text' | 'picture' | 'shape' | 'browserframe' | 'subtitle' | 'barcode' | 'rating';
|
||||
}
|
||||
|
||||
export interface PlacidImage {
|
||||
id: string;
|
||||
status: 'queued' | 'finished' | 'error';
|
||||
image_url?: string;
|
||||
polling_url: string;
|
||||
error_message?: string;
|
||||
}
|
||||
|
||||
export interface PlacidCreateImageRequest {
|
||||
template_uuid: string;
|
||||
layers?: Record<string, any>;
|
||||
modifications?: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
filename?: string;
|
||||
dpi?: number;
|
||||
};
|
||||
webhook_success?: string;
|
||||
create_now?: boolean;
|
||||
passthrough?: Record<string, any> | string;
|
||||
}
|
||||
|
||||
export interface PlacidCreatePdfRequest {
|
||||
pages: Array<{
|
||||
template_uuid: string;
|
||||
layers?: Record<string, any>;
|
||||
}>;
|
||||
modifications?: {
|
||||
filename?: string;
|
||||
quality?: number;
|
||||
dpi?: number;
|
||||
};
|
||||
webhook_success?: string;
|
||||
create_now?: boolean;
|
||||
passthrough?: Record<string, any> | string;
|
||||
}
|
||||
|
||||
export interface PlacidCreateVideoRequest {
|
||||
clips: Array<{
|
||||
template_uuid: string;
|
||||
layers?: Record<string, any>;
|
||||
}>;
|
||||
modifications?: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
filename?: string;
|
||||
canvas_background?: string;
|
||||
fps?: number;
|
||||
};
|
||||
webhook_success?: string;
|
||||
create_now?: boolean;
|
||||
passthrough?: Record<string, any> | string;
|
||||
}
|
||||
|
||||
export interface PlacidWebhookPayload {
|
||||
id: string;
|
||||
status: string;
|
||||
image_url?: string;
|
||||
template_uuid: string;
|
||||
passthrough?: Record<string, any>;
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import { DynamicPropsValue, Property } from '@activepieces/pieces-framework';
|
||||
import { PlacidClient } from './client';
|
||||
import { placidAuth } from '../..';
|
||||
|
||||
const createTemplateDropdown = (outputType?: 'image' | 'pdf' | 'video') =>
|
||||
Property.Dropdown({
|
||||
auth: placidAuth,
|
||||
displayName: 'Template',
|
||||
description: `Select a Placid template${outputType ? ` for ${outputType} generation` : ''}`,
|
||||
required: true,
|
||||
refreshers: ['auth'],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please connect your account first',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const client = new PlacidClient(auth);
|
||||
const templates = await client.listTemplates();
|
||||
|
||||
if (!templates || templates.length === 0) {
|
||||
return {
|
||||
options: [],
|
||||
placeholder: 'No templates found in your Placid project',
|
||||
};
|
||||
}
|
||||
|
||||
// Filter templates by tags or naming if outputType is specified
|
||||
let filteredTemplates = templates;
|
||||
if (outputType) {
|
||||
filteredTemplates = templates.filter((template) => {
|
||||
const title = template.title.toLowerCase();
|
||||
const tags = template.tags?.map((tag) => tag.toLowerCase()) || [];
|
||||
|
||||
// Check if template title or tags indicate it's for this output type
|
||||
return (
|
||||
title.includes(outputType) ||
|
||||
tags.includes(outputType) ||
|
||||
tags.includes(`${outputType}s`) ||
|
||||
// For backwards compatibility, if no specific filtering matches, include all
|
||||
(!title.includes('image') &&
|
||||
!title.includes('pdf') &&
|
||||
!title.includes('video') &&
|
||||
!tags.some((tag) =>
|
||||
['image', 'pdf', 'video', 'images', 'pdfs', 'videos'].includes(tag),
|
||||
))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// If filtering resulted in no templates, show all templates with a note
|
||||
if (outputType && filteredTemplates.length === 0) {
|
||||
filteredTemplates = templates;
|
||||
}
|
||||
|
||||
return {
|
||||
options: filteredTemplates.map((template) => {
|
||||
return {
|
||||
label: template.title,
|
||||
value: template.uuid,
|
||||
};
|
||||
}),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: `Failed to load templates: ${
|
||||
error instanceof Error ? error.message : 'Unknown error'
|
||||
}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// General template dropdown for all types
|
||||
export const templateDropdown = createTemplateDropdown();
|
||||
|
||||
// Specific template dropdowns for each output type
|
||||
export const imageTemplateDropdown = createTemplateDropdown('image');
|
||||
export const pdfTemplateDropdown = createTemplateDropdown('pdf');
|
||||
export const videoTemplateDropdown = createTemplateDropdown('video');
|
||||
|
||||
export const modificationsProperty = Property.Object({
|
||||
displayName: 'Modifications',
|
||||
description: 'Optional modifications to the generated image (width, height, filename)',
|
||||
required: false,
|
||||
});
|
||||
|
||||
export const webhookProperty = Property.ShortText({
|
||||
displayName: 'Webhook URL',
|
||||
description: 'Optional webhook URL to receive notification when generation is complete.',
|
||||
required: false,
|
||||
});
|
||||
|
||||
export const createNowProperty = Property.Checkbox({
|
||||
displayName: 'Create Now',
|
||||
description: 'Whether to create the image immediately (synchronous) or queue it (asynchronous).',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
});
|
||||
|
||||
export const passthroughProperty = Property.ShortText({
|
||||
displayName: 'Passthrough Text',
|
||||
required: false,
|
||||
});
|
||||
|
||||
export const templateLayersProperty = (type: string) =>
|
||||
Property.DynamicProperties({
|
||||
auth: placidAuth,
|
||||
displayName: 'Layers',
|
||||
refreshers: ['template'],
|
||||
required: false,
|
||||
props: async ({ auth, template }) => {
|
||||
if (!auth || !template) return {};
|
||||
|
||||
const props: DynamicPropsValue = {};
|
||||
|
||||
const client = new PlacidClient(auth);
|
||||
const response = await client.getTemplate(template as unknown as string);
|
||||
|
||||
for (const layer of response.layers) {
|
||||
switch (layer.type) {
|
||||
case 'text': {
|
||||
props[`${layer.name}:::text`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Text`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::text_color`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Text Color`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::font`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Text Font`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::alt_text_color`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Alternate Text Color`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::alt_font`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Alternate Text Font`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::hide`] = Property.Checkbox({
|
||||
displayName: `${layer.name} : Hide Layer`,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'shape': {
|
||||
props[`${layer.name}:::background_color`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Background Color`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::hide`] = Property.Checkbox({
|
||||
displayName: `${layer.name} : Hide Layer`,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'picture': {
|
||||
if (type === 'video') {
|
||||
props[`${layer.name}:::video`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Video`,
|
||||
required: false,
|
||||
});
|
||||
}
|
||||
props[`${layer.name}:::image`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Image`,
|
||||
required: false,
|
||||
});
|
||||
|
||||
props[`${layer.name}:::hide`] = Property.Checkbox({
|
||||
displayName: `${layer.name} : Hide Layer`,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'browserframe': {
|
||||
props[`${layer.name}:::image`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Image`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::url`] = Property.ShortText({
|
||||
displayName: `${layer.name} : URL Text`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::hide`] = Property.Checkbox({
|
||||
displayName: `${layer.name} : Hide Layer`,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'barcode': {
|
||||
props[`${layer.name}:::value`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Value`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::color`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Color`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::hide`] = Property.Checkbox({
|
||||
displayName: `${layer.name} : Hide Layer`,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'rating': {
|
||||
props[`${layer.name}:::value`] = Property.ShortText({
|
||||
displayName: `${layer.name} : Value`,
|
||||
required: false,
|
||||
});
|
||||
props[`${layer.name}:::hide`] = Property.Checkbox({
|
||||
displayName: `${layer.name} : Hide Layer`,
|
||||
required: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return props;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user