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 { weblingAuth } from '../../index';
import { createAction, PiecePropValueSchema, Property } from '@activepieces/pieces-framework';
import { getCalendars, getEventsById } from '../common/helpers';
import { z } from 'zod';
import { propsValidation } from '@activepieces/pieces-common';
export const eventsById = createAction({
auth: weblingAuth,
name: 'EventsById',
displayName: 'Get Events by ID',
description:
'Gets event data by a list of event IDs and optional calendar ID to filter.',
props: {
eventIds: Property.ShortText({
displayName: 'Event ID list',
required: true,
description:
"Comma separated list of event IDs (e.g. '536,525,506,535'). When at least one ID doesn't exist the whole query return a 404 error.",
}),
calendarId: Property.Dropdown<string,false,typeof weblingAuth>({
auth: weblingAuth,
displayName: 'Calendar',
description: 'Calendar to filter the events by.',
refreshers: [],
required: false,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'connect your account first',
options: [],
};
}
const authProp = auth;
const calendars = await getCalendars(authProp);
return {
disabled: false,
options: calendars.map((calendar) => {
return {
label: calendar.properties.title,
value: calendar.id,
};
}),
};
},
}),
},
async run(configValue) {
const { eventIds: eventIds, calendarId: calendarId } =
configValue.propsValue;
await propsValidation.validateZod(configValue.propsValue, {
eventIds: z.string().regex(/^\d+(,\d+)*$/),
});
const events = await getEventsById(configValue.auth, eventIds);
if (calendarId) {
return events.filter((event) => event.parents.includes(calendarId));
}
return events;
},
});

View File

@@ -0,0 +1,111 @@
import { weblingAuth } from '../../index';
import {
httpClient,
HttpMethod,
HttpRequest,
} from '@activepieces/pieces-common';
import { AppConnectionValueForAuthProperty, PiecePropValueSchema } from '@activepieces/pieces-framework';
import { CalendarObject, WeblingCalendarEvent, WeblingChanges } from './types';
export async function callApi<Type>(
authProp: AppConnectionValueForAuthProperty<typeof weblingAuth>,
request: string,
) {
const httpRequest: HttpRequest = {
method: HttpMethod.GET,
url: `https://${authProp.props.baseUrl}/api/1/${request}`,
headers: {
apikey: authProp.props.apikey,
},
};
const response = await httpClient.sendRequest<Type>(httpRequest);
return response;
}
export async function getChanges(
authProp: AppConnectionValueForAuthProperty<typeof weblingAuth>,
lastFetchEpochMS: number,
): Promise<WeblingChanges> {
// Webling API breaks if unix timestamp is too far in the past
if (lastFetchEpochMS === 0) {
const today = new Date();
const minUpdated = new Date();
minUpdated.setDate(today.getDate() - 7);
lastFetchEpochMS = minUpdated.getTime();
}
const response = await callApi<WeblingChanges>(
authProp,
`/changes/${lastFetchEpochMS / 1000}` // webling uses seconds instead of milliseconds
);
return response.body;
}
export async function getCalendars(
authProp: AppConnectionValueForAuthProperty<typeof weblingAuth>
): Promise<CalendarObject[]> {
const response = await callApi<CalendarObject[]>(
authProp,
'calendar?format=full'
);
return response.body;
}
export async function getAllEvents(
authProp: AppConnectionValueForAuthProperty<typeof weblingAuth>,
calendarId: string,
): Promise<WeblingCalendarEvent[]> {
const response = await callApi<WeblingCalendarEvent[]>(
authProp,
`calendarevent?filter=$parents.$id=${calendarId}&format=full`
);
return response.body;
}
export async function getEventsById(
authProp: AppConnectionValueForAuthProperty<typeof weblingAuth>,
eventIds: string,
): Promise<WeblingCalendarEvent[]> {
const request = `calendarevent/${eventIds}`
const response = await callApi<WeblingCalendarEvent[]>(
authProp,
request
);
return response.body;
};
export async function getUpdatedOrNewEvents(
authProp: AppConnectionValueForAuthProperty<typeof weblingAuth>,
calendarId: string,
lastFetchEpochMS: number
): Promise<WeblingCalendarEvent[]> {
// get changes since last call
const weblingChanges: WeblingChanges = await getChanges(
authProp,
lastFetchEpochMS / 1000
);
// this will also include ids of deleted objects
const changedEvents: string[] = weblingChanges.objects.calendarevents ?? [];
const deletedObjects: string[] = weblingChanges.deleted ?? [];
// filter out already deleted objects to treat seperately
// including a deleted event in a query list will result in a 404 response for the whole query
const updatedOrNewEvents: string[] = changedEvents.filter(
(event) => !deletedObjects.includes(event)
);
const response = await callApi<WeblingCalendarEvent[]>(
authProp,
`calendarevent/${updatedOrNewEvents.join(
','
)}?format=full&filter=$parents.$id=${calendarId}`
);
return response.body;
}

View File

@@ -0,0 +1,33 @@
import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework';
import { getCalendars } from './helpers';
import { weblingAuth } from '../../index';
export const weblingCommon = {
calendarDropdown: () => {
return Property.Dropdown<string,true,typeof weblingAuth>({
auth: weblingAuth,
displayName: 'Calendar',
refreshers: [],
required: true,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'connect your account first',
options: [],
};
}
const calendars = await getCalendars(auth);
return {
disabled: false,
options: calendars.map((calendar) => {
return {
label: calendar.properties.title,
value: calendar.id,
};
}),
};
},
});
},
};

View File

@@ -0,0 +1,111 @@
export interface MetaObject {
created: string;
createuser: {
label: string;
type: string;
};
lastmodified: string;
lastmodifieduser: {
label: string;
type: string;
};
}
export interface CalendarObject {
type: string;
meta: MetaObject;
readonly: boolean;
properties: {
title: string;
color: string;
isPublic: boolean;
publicHash: string;
icsHash: string;
};
parents: [];
children: {
calendarevent: string[];
};
links: object;
id: string;
}
export interface CalendarList {
objects: CalendarObject[];
}
export interface WeblingCalendarEvent {
type: 'calendarevent';
meta: MetaObject;
readonly: boolean;
properties: {
title: string;
description: string;
place: string;
begin: string;
end: string;
duration: string;
isAllDay: boolean;
isRecurring: boolean;
status: string;
recurrencePattern: string | null;
enableParticipantSignup: boolean;
enableParticipantMaybeState: boolean;
isSignupBinding: boolean;
maxParticipants: string | null;
signedupParticipants: string;
signupAllowedUntil: string | null;
doAutoAcceptParticipants: boolean;
questionSchema: string | null;
showParticipationsInPortal: boolean;
showAllAnswersInPortal: boolean;
};
parents: string[];
children: object;
links: object;
}
export interface WeblingChanges {
objects: WeblingObjectTypes;
deleted: string[];
context: object[];
definitions: object[];
settings: boolean;
quota: boolean;
subscription: boolean;
revision: string;
version: string;
}
// this list might be incomplete
export interface WeblingObjectTypes {
account?: string[];
accountgroup?: string[];
accountgrouptemplate?: string[];
accounttemplate?: string[];
apikey?: string[];
article?: string[];
articlegroup?: string[];
calendar?: string[];
calendarevents?: string[];
comment?: string[];
debitor?: string[];
debitorcategory?: string[];
document?: string[];
domain?: string[];
email?: string[];
entry?: string[];
entrygroup?: string[];
file?: string[];
member?: string[];
memberform?: string[];
membergroup?: string[];
page?: string[];
participant?: string[];
period?: string[];
periodchain?: string[];
periodgroup?: string[];
settings?: string[];
template?: string[];
user?: string[];
}

View File

@@ -0,0 +1,87 @@
import {
createTrigger,
TriggerStrategy,
PiecePropValueSchema,
AppConnectionValueForAuthProperty,
} from '@activepieces/pieces-framework';
import {
DedupeStrategy,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import { weblingAuth } from '../../index';
import { weblingCommon } from '../common';
import { WeblingCalendarEvent } from '../common/types';
import { getUpdatedOrNewEvents } from '../common/helpers';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof weblingAuth>,
{ calendarId?: string }
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, propsValue: { calendarId }, lastFetchEpochMS }) => {
// implement the logic to fetch the items
const items: WeblingCalendarEvent[] =
(await getUpdatedOrNewEvents(auth, calendarId!, lastFetchEpochMS)) ?? [];
return items.map((item) => ({
epochMilliSeconds: new Date(item.meta.lastmodified).getTime(),
data: item,
}));
},
};
export const onEventChanged = createTrigger({
auth: weblingAuth,
name: 'onEventChanged',
displayName: 'New or Updated Event',
description: 'Triggers when an event is added or updated.',
props: {
calendarId: weblingCommon.calendarDropdown(),
},
sampleData: {},
type: TriggerStrategy.POLLING,
async test(context) {
const { store, auth, propsValue } = context;
return await pollingHelper.test(polling, {
store,
auth,
propsValue: {
calendarId: propsValue.calendarId,
},
files: context.files,
});
},
async onEnable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onEnable(polling, {
store,
auth,
propsValue: {
calendarId: propsValue.calendarId,
},
});
},
async onDisable(context) {
const { store, auth, propsValue } = context;
await pollingHelper.onDisable(polling, {
store,
auth,
propsValue: {
calendarId: propsValue.calendarId,
},
});
},
async run(context) {
const { store, auth, propsValue } = context;
return await pollingHelper.poll(polling, {
store,
auth,
propsValue: {
calendarId: propsValue.calendarId,
},
files: context.files,
});
},
});

View File

@@ -0,0 +1,119 @@
import {
createTrigger,
TriggerStrategy,
PiecePropValueSchema,
AppConnectionValueForAuthProperty,
} from '@activepieces/pieces-framework';
import {
DedupeStrategy,
Polling,
pollingHelper,
} from '@activepieces/pieces-common';
import { weblingAuth } from '../../index';
import { WeblingChanges } from '../common/types';
import { getChanges } from '../common/helpers';
const polling: Polling<
AppConnectionValueForAuthProperty<typeof weblingAuth>,
{ calendarId?: string }
> = {
strategy: DedupeStrategy.TIMEBASED,
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
const changes: WeblingChanges = await getChanges(auth, lastFetchEpochMS);
const items = [
{
epochMilliSeconds: Date.now(), // maybe use revision instead?
data: changes,
},
];
return items;
},
};
export const onChangedData = createTrigger({
auth: weblingAuth,
name: 'onChangedData',
displayName: 'On Changed Data',
description:
'Triggers when anything was added, updated or deleted since last request.',
props: {},
sampleData: {
objects: {
account: [244, 246],
accountgroup: [242],
accountgrouptemplate: [241],
accounttemplate: [243, 245, 247, 248, 250],
apikey: [5241],
article: [4579, 4580],
articlegroup: [4578],
calendar: [235, 4428],
calendarevent: [4431, 4434, 4435, 4436],
comment: [4423],
debitor: [1205, 1208],
debitorcategory: [650],
document: [4506],
documentgroup: [114],
domain: [1771],
email: [5085],
entry: [321, 323, 338],
entrygroup: [320, 322, 337],
file: [4525],
member: [4270, 4271, 398, 399],
memberform: [4491],
membergroup: [100],
page: [4495],
participant: [4460],
period: [240],
periodchain: [239],
periodgroup: [238],
settings: [233],
template: [228],
user: [120, 343, 345],
},
deleted: [235],
context: [],
definitions: ['member'],
settings: false,
quota: true,
subscription: true,
revision: 4758,
version: 2560,
},
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,
});
},
});