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,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;
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user