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,27 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { fireberryAuth } from '../../index';
import { objectTypeDropdown, objectFields } from '../common/props';
import { FireberryClient } from '../common/client';
export const createRecordAction = createAction({
name: 'create_record',
displayName: 'Create Record',
description: 'Create a new record in Fireberry.',
auth: fireberryAuth,
props: {
objectType: objectTypeDropdown,
fields: objectFields,
},
async run({ auth, propsValue }) {
const client = new FireberryClient(auth);
const { objectType, fields } = propsValue;
const fieldsObj = typeof fields === 'string' ? JSON.parse(fields) : fields;
if (typeof fieldsObj !== 'object' || fieldsObj === null) {
throw new Error('Fields must be an object');
}
return await client.batchCreate(objectType as string, [fieldsObj]);
},
});

View File

@@ -0,0 +1,116 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { fireberryAuth } from '../../index';
import { objectTypeDropdown } from '../common/props';
import { FireberryClient } from '../common/client';
const recordsToDeleteDropdown = Property.MultiSelectDropdown({
auth: fireberryAuth,
displayName: 'Records to Delete',
required: true,
refreshers: ['objectType'],
options: async ({ auth, objectType }) => {
if (!auth || !objectType) {
return {
disabled: true,
options: [],
placeholder: 'Select object type first',
};
}
try {
const objectTypeStr = typeof objectType === 'string' ? objectType : (objectType as { value: string })?.value;
const client = new FireberryClient(auth);
const response = await client.request<{
success: boolean;
data: {
Records: Array<Record<string, any>>;
PrimaryField: string;
PrimaryKey: string;
Total_Records: number;
}
}>({
method: HttpMethod.GET,
resourceUri: `/api/record/${objectTypeStr}?$top=100`,
});
if (!response.data?.Records || !Array.isArray(response.data.Records)) {
return {
disabled: false,
options: [],
placeholder: response.data?.Total_Records === 0 ? 'No records found' : 'Error loading records',
};
}
const primaryField = response.data.PrimaryField;
const primaryKey = response.data.PrimaryKey;
const options = response.data.Records.map((record: any) => {
const displayName = record[primaryField] ||
record.name || record.title || record.subject ||
record.firstname || record.lastname || record.email ||
record.accountname || record.contactname ||
`Record ${record[primaryKey]?.substring(0, 8) || 'Unknown'}`;
return {
label: displayName,
value: record[primaryKey],
};
});
return {
disabled: false,
options: options.slice(0, 100),
};
} catch (error) {
return {
disabled: false,
options: [],
placeholder: 'Error loading records',
};
}
},
});
export const deleteRecordAction = createAction({
name: 'delete_record',
displayName: 'Delete Records',
description: 'Delete records from Fireberry.',
auth: fireberryAuth,
props: {
objectType: objectTypeDropdown,
recordIds: recordsToDeleteDropdown,
confirmDeletion: Property.Checkbox({
displayName: 'Confirm Deletion',
required: true,
description: 'Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.',
}),
},
async run({ auth, propsValue }) {
const client = new FireberryClient(auth);
const { objectType, recordIds, confirmDeletion } = propsValue;
if (!confirmDeletion) {
throw new Error('You must confirm deletion by checking the confirmation box');
}
if (!recordIds || !Array.isArray(recordIds) || recordIds.length === 0) {
throw new Error('At least one record must be selected for deletion');
}
if (recordIds.length > 20) {
throw new Error('Maximum 20 records can be deleted at once');
}
const result = await client.batchDelete(objectType as string, recordIds);
return {
success: true,
deletedCount: recordIds.length,
recordIds: recordIds,
message: `Successfully deleted ${recordIds.length} record(s)`,
apiResponse: result,
};
},
});

View File

@@ -0,0 +1,177 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { fireberryAuth } from '../../index';
import { objectTypeDropdown } from '../common/props';
import { FireberryClient } from '../common/client';
const fieldsToReturn = Property.DynamicProperties({
displayName: 'Fields to Return',
refreshers: ['objectType'],
required: false,
auth: fireberryAuth,
props: async ({ auth, objectType }) => {
if (!auth || !objectType) return {};
const objectTypeStr = typeof objectType === 'string' ? objectType : (objectType as { value: string })?.value;
const client = new FireberryClient(auth);
try {
const metadata = await client.getObjectFieldsMetadata(objectTypeStr);
const props: Record<string, any> = {};
for (const field of metadata.data) {
const systemFields = ['createdby', 'modifiedby', 'deletedby'];
if (field.fieldName.endsWith('id') && !field.label) {
continue;
}
if (systemFields.includes(field.fieldName.toLowerCase())) {
continue;
}
props[field.fieldName] = Property.Checkbox({
displayName: field.label || field.fieldName,
required: false,
description: `Include ${field.label || field.fieldName} in search results`,
});
}
return props;
} catch (error) {
console.error('Error fetching fields for selection:', error);
return {};
}
},
});
export const findRecordAction = createAction({
name: 'find_record',
displayName: 'Find Records',
description: 'Search for records in Fireberry.',
auth: fireberryAuth,
props: {
objectType: objectTypeDropdown,
searchQuery: Property.LongText({
displayName: 'Search Query',
required: false,
description: 'Enter search criteria (e.g., "accountname=John" or "email contains @example.com"). Leave empty to get all records.',
}),
fieldsToReturn: fieldsToReturn,
sortBy: Property.ShortText({
displayName: 'Sort By Field',
required: false,
description: 'System name of field to sort by (e.g., "createdon", "accountname")',
}),
sortOrder: Property.StaticDropdown({
displayName: 'Sort Order',
required: false,
defaultValue: 'desc',
options: {
disabled: false,
options: [
{ label: 'Descending (newest first)', value: 'desc' },
{ label: 'Ascending (oldest first)', value: 'asc' },
],
},
}),
pageSize: Property.Number({
displayName: 'Page Size',
required: false,
defaultValue: 25,
description: 'Number of records to return (max 50)',
}),
pageNumber: Property.Number({
displayName: 'Page Number',
required: false,
defaultValue: 1,
description: 'Page number to retrieve (max 10)',
}),
},
async run({ auth, propsValue }) {
const client = new FireberryClient(auth);
const { objectType, searchQuery, fieldsToReturn, sortBy, sortOrder, pageSize, pageNumber } = propsValue;
const selectedFields: string[] = [];
if (fieldsToReturn && typeof fieldsToReturn === 'object') {
for (const [fieldName, isSelected] of Object.entries(fieldsToReturn)) {
if (isSelected === true) {
selectedFields.push(fieldName);
}
}
}
const objectsMetadata = await client.getObjectsMetadata();
const targetObject = objectsMetadata.data.find(obj => obj.systemName === objectType);
if (!targetObject) {
throw new Error(`Object type '${objectType}' not found`);
}
const queryBody: Record<string, any> = {
objecttype: parseInt(targetObject.objectType),
};
if (selectedFields.length > 0) {
queryBody['fields'] = selectedFields.join(',');
}
if (searchQuery && searchQuery.trim()) {
queryBody['query'] = searchQuery.trim();
}
if (sortBy && sortBy.trim()) {
queryBody['sort_by'] = sortBy.trim();
queryBody['sort_type'] = sortOrder || 'desc';
}
if (pageSize) {
queryBody['page_size'] = Math.min(Math.max(1, pageSize), 50);
}
if (pageNumber) {
queryBody['page_number'] = Math.min(Math.max(1, pageNumber), 10);
}
const response = await client.request<{
success: boolean;
data: {
ObjectName: string;
SystemName: string;
ObjectType: number;
PrimaryKey: string;
PrimaryField: string;
PageNum: number;
SortBy: string;
SortBy_Desc: boolean;
IsLastPage: boolean;
Columns: Array<Record<string, any>>;
Data: Array<Record<string, any>>;
};
}>({
method: HttpMethod.POST,
resourceUri: '/api/query',
body: queryBody,
});
if (!response.success) {
throw new Error('Query failed');
}
return {
success: true,
query: queryBody,
results: {
objectName: response.data.ObjectName,
systemName: response.data.SystemName,
objectType: response.data.ObjectType,
pageNumber: response.data.PageNum,
sortBy: response.data.SortBy,
sortDescending: response.data.SortBy_Desc,
isLastPage: response.data.IsLastPage,
primaryKey: response.data.PrimaryKey,
primaryField: response.data.PrimaryField,
columns: response.data.Columns,
records: response.data.Data,
recordCount: response.data.Data.length,
},
};
},
});

View File

@@ -0,0 +1,269 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { fireberryAuth } from '../../index';
import { objectTypeDropdown } from '../common/props';
import { FireberryClient } from '../common/client';
const recordDropdown = Property.Dropdown({
auth: fireberryAuth,
displayName: 'Record',
required: true,
refreshers: ['objectType'],
options: async ({ auth, objectType }) => {
if (!auth || !objectType) {
return {
disabled: true,
options: [],
placeholder: 'Select object type first',
};
}
try {
const objectTypeStr = typeof objectType === 'string' ? objectType : (objectType as { value: string })?.value;
const client = new FireberryClient(auth);
const response = await client.request<{
success: boolean;
data: {
Records: Array<Record<string, any>>;
PrimaryField: string;
PrimaryKey: string;
Total_Records: number;
}
}>({
method: HttpMethod.GET,
resourceUri: `/api/record/${objectTypeStr}?$top=50`,
});
if (!response.data?.Records || !Array.isArray(response.data.Records)) {
return {
disabled: false,
options: [],
placeholder: response.data?.Total_Records === 0 ? 'No records found' : 'Error loading records',
};
}
const primaryField = response.data.PrimaryField;
const primaryKey = response.data.PrimaryKey;
const options = response.data.Records.map((record: any) => {
const displayName = record[primaryField] ||
record.name || record.title || record.subject ||
record.firstname || record.lastname || record.email ||
record.accountname || record.contactname ||
`Record ${record[primaryKey]?.substring(0, 8) || 'Unknown'}`;
return {
label: displayName,
value: record[primaryKey],
};
});
return {
disabled: false,
options: options.slice(0, 50),
};
} catch (error) {
return {
disabled: false,
options: [],
placeholder: 'Error loading records',
};
}
},
});
const updateFields = Property.DynamicProperties({
displayName: 'Fields to Update',
refreshers: ['objectType'],
required: true,
auth: fireberryAuth,
props: async ({ auth, objectType }) => {
if (!auth || !objectType) return {};
const objectTypeStr = typeof objectType === 'string' ? objectType : (objectType as { value: string })?.value;
const client = new FireberryClient(auth);
try {
const metadata = await client.getObjectFieldsMetadata(objectTypeStr);
const props: Record<string, any> = {};
const fieldTypeMap: Record<string, string> = {
'a1e7ed6f-5083-477b-b44c-9943a6181359': 'text',
'ce972d02-5013-46d4-9d1d-f09df1ac346a': 'datetime',
'6a34bfe3-fece-4da1-9136-a7b1e5ae3319': 'number',
'a8fcdf65-91bc-46fd-82f6-1234758345a1': 'lookup',
'b4919f2e-2996-48e4-a03c-ba39fb64386c': 'picklist',
'80108f9d-1e75-40fa-9fa9-02be4ddc1da1': 'longtext',
};
const picklistCache: Record<string, any> = {};
const picklistFields = metadata.data.filter(field =>
fieldTypeMap[field.systemFieldTypeId] === 'picklist'
);
for (const field of picklistFields) {
const largeLists = ['objecttypecode', 'resultcode'];
if (!largeLists.includes(field.fieldName.toLowerCase())) {
try {
const picklistData = await client.getPicklistValues(objectTypeStr, field.fieldName);
if (picklistData.data?.values && Array.isArray(picklistData.data.values)) {
picklistCache[field.fieldName] = picklistData.data.values;
}
} catch (error) {
picklistCache[field.fieldName] = [];
}
}
}
for (const field of metadata.data) {
const systemFields = ['createdby', 'modifiedby', 'deletedby', 'createdon', 'modifiedon', 'deletedon'];
if (field.fieldName.endsWith('id') && !field.label) {
continue;
}
if (systemFields.includes(field.fieldName.toLowerCase())) {
continue;
}
const fieldType = fieldTypeMap[field.systemFieldTypeId] || 'text';
const isRequired = false;
switch (fieldType) {
case 'text':
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
description: 'Leave empty to keep current value',
});
break;
case 'number':
props[field.fieldName] = Property.Number({
displayName: field.label || field.fieldName,
required: isRequired,
description: 'Leave empty to keep current value',
});
break;
case 'datetime':
props[field.fieldName] = Property.DateTime({
displayName: field.label || field.fieldName,
required: isRequired,
description: 'Leave empty to keep current value',
});
break;
case 'picklist': {
const largeLists = ['objecttypecode', 'resultcode'];
if (largeLists.includes(field.fieldName.toLowerCase())) {
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
description: 'Enter numeric value (leave empty to keep current)',
});
} else {
const values = picklistCache[field.fieldName] || [];
if (values.length > 0 && values.length <= 20) {
const options = values.map((option: any) => ({
label: option.name || option.value,
value: option.value,
}));
props[field.fieldName] = Property.StaticDropdown({
displayName: field.label || field.fieldName,
required: isRequired,
options: {
disabled: false,
options: [
{ label: '-- Keep Current Value --', value: '' },
...options
],
},
});
} else {
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
description: values.length > 20
? `Enter numeric value (${values.length} options available, leave empty to keep current)`
: 'Enter value (leave empty to keep current)',
});
}
}
break;
}
case 'longtext': {
props[field.fieldName] = Property.LongText({
displayName: field.label || field.fieldName,
required: isRequired,
description: 'Leave empty to keep current value',
});
break;
}
case 'lookup': {
let description = 'Record ID (leave empty to keep current)';
if (field.fieldName.includes('account')) description = 'Account record ID (leave empty to keep current)';
else if (field.fieldName.includes('contact')) description = 'Contact record ID (leave empty to keep current)';
else if (field.fieldName.includes('owner')) description = 'User record ID for owner (leave empty to keep current)';
else if (field.fieldName.includes('product')) description = 'Product record ID (leave empty to keep current)';
else if (field.fieldName.includes('user')) description = 'User record ID (leave empty to keep current)';
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
description,
});
break;
}
default: {
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
description: 'Leave empty to keep current value',
});
break;
}
}
}
return props;
} catch (error) {
console.error('Error fetching update fields:', error);
return {};
}
},
});
export const updateRecordAction = createAction({
name: 'update_record',
displayName: 'Update Record',
description: 'Update an existing record in Fireberry.',
auth: fireberryAuth,
props: {
objectType: objectTypeDropdown,
recordId: recordDropdown,
fields: updateFields,
},
async run({ auth, propsValue }) {
const client = new FireberryClient(auth);
const { objectType, recordId, fields } = propsValue;
if (!recordId) {
throw new Error('Record ID is required');
}
const fieldsObj = typeof fields === 'string' ? JSON.parse(fields) : fields;
if (typeof fieldsObj !== 'object' || fieldsObj === null) {
throw new Error('Fields must be an object');
}
const updateData: Record<string, any> = {};
for (const [key, value] of Object.entries(fieldsObj)) {
if (value !== '' && value !== null && value !== undefined) {
updateData[key] = value;
}
}
const recordToUpdate = { id: recordId, record: updateData };
return await client.batchUpdate(objectType as string, [recordToUpdate]);
},
});

View File

@@ -0,0 +1,146 @@
import { HttpMethod, httpClient, HttpRequest, AuthenticationType } from '@activepieces/pieces-common';
import { AppConnectionValueForAuthProperty } from '@activepieces/pieces-framework';
import { fireberryAuth } from '../..';
const FIREBERRY_API_BASE_URL = 'https://api.fireberry.com';
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 1000;
function delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function normalizeQueryParams(params?: Record<string, string | number | boolean>): Record<string, string> | undefined {
if (!params) return undefined;
const result: Record<string, string> = {};
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
result[key] = String(params[key]);
}
}
return result;
}
export class FireberryClient {
private apiKey: string;
constructor(apiKey: AppConnectionValueForAuthProperty<typeof fireberryAuth>) {
this.apiKey = apiKey.secret_text;
}
private parseError(error: any): string {
if (error?.response?.body) {
try {
const body = typeof error.response.body === 'string' ? JSON.parse(error.response.body) : error.response.body;
if (body?.error) {
if (typeof body.error === 'string') return body.error;
if (body.error.message) return body.error.message;
}
if (body?.message) return body.message;
} catch {
return 'Unknown error';
}
}
if (error?.message) return error.message;
return 'Unknown error';
}
private shouldRetry(status: number): boolean {
return status === 429 || (status >= 500 && status < 600);
}
async request<T = unknown>({
method,
resourceUri,
body,
queryParams,
}: {
method: HttpMethod;
resourceUri: string;
body?: unknown;
queryParams?: Record<string, string | number | boolean>;
}): Promise<T> {
const request: HttpRequest = {
method,
url: `${FIREBERRY_API_BASE_URL}${resourceUri}`,
headers: {
'tokenid': this.apiKey,
'Content-Type': 'application/json',
},
body,
queryParams: normalizeQueryParams(queryParams),
timeout: 10000, // 10 seconds
};
let lastError: any = null;
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
const response = await httpClient.sendRequest<T>(request);
if (response.status >= 200 && response.status < 300) {
return response.body;
} else {
const errorMsg = this.parseError({ response });
if (this.shouldRetry(response.status) && attempt < MAX_RETRIES - 1) {
await delay(RETRY_DELAY_MS * (attempt + 1));
continue;
}
throw new Error(`Fireberry API error (${response.status}): ${errorMsg}`);
}
} catch (error: any) {
const status = error?.response?.status;
if (status && this.shouldRetry(status) && attempt < MAX_RETRIES - 1) {
await delay(RETRY_DELAY_MS * (attempt + 1));
lastError = error;
continue;
}
const errorMsg = this.parseError(error);
throw new Error(`Fireberry API error${status ? ` (${status})` : ''}: ${errorMsg}`);
}
}
throw new Error(`Fireberry API error: ${this.parseError(lastError)}`);
}
async getObjectsMetadata(): Promise<{ success: boolean; data: Array<{ name: string; systemName: string; objectType: string }> }> {
return this.request<{ success: boolean; data: Array<{ name: string; systemName: string; objectType: string }> }>({
method: HttpMethod.GET,
resourceUri: '/metadata/records',
});
}
async getObjectFieldsMetadata(object: string): Promise<{ success: boolean; data: Array<{ label: string; fieldName: string; systemFieldTypeId: string; systemName: string }> }> {
return this.request<{ success: boolean; data: Array<{ label: string; fieldName: string; systemFieldTypeId: string; systemName: string }> }>({
method: HttpMethod.GET,
resourceUri: `/metadata/records/${object}/fields`,
});
}
async getPicklistValues(object: string, fieldName: string): Promise<{ success: boolean; data: { values: Array<{ name: string; value: string }> } }> {
return this.request<{ success: boolean; data: { values: Array<{ name: string; value: string }> } }>({
method: HttpMethod.GET,
resourceUri: `/metadata/records/${object}/fields/${fieldName}/values`,
});
}
async batchCreate(object: string, records: any[]): Promise<any> {
return this.request({
method: HttpMethod.POST,
resourceUri: `/api/v3/record/${object}/batch/create`,
body: { data: records },
});
}
async batchUpdate(object: string, records: any[]): Promise<any> {
return this.request({
method: HttpMethod.POST,
resourceUri: `/api/v3/record/${object}/batch/update`,
body: { data: records },
});
}
async batchDelete(object: string, ids: string[]): Promise<any> {
return this.request({
method: HttpMethod.POST,
resourceUri: `/api/v3/record/${object}/batch/delete`,
body: { data: ids },
});
}
}

View File

@@ -0,0 +1,174 @@
import { Property } from '@activepieces/pieces-framework';
import { FireberryClient } from './client';
import { fireberryAuth } from '../..';
export const objectTypeDropdown = Property.Dropdown({
displayName: 'Object Type',
required: true,
refreshers: [],
auth: fireberryAuth,
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Connect your Fireberry account',
};
}
const client = new FireberryClient(auth);
const metadata = await client.getObjectsMetadata();
const options = metadata.data.map(obj => ({
label: obj.name,
value: obj.systemName,
}));
return {
disabled: false,
options,
};
},
});
export const objectFields = Property.DynamicProperties({
displayName: 'Fields',
refreshers: ['objectType'],
required: true,
auth: fireberryAuth,
props: async ({ auth, objectType }) => {
if (!auth || !objectType) return {};
const objectTypeStr = typeof objectType === 'string' ? objectType : (objectType as { value: string })?.value;
const client = new FireberryClient(auth);
const metadata = await client.getObjectFieldsMetadata(objectTypeStr);
const props: Record<string, any> = {};
const fieldTypeMap: Record<string, string> = {
'a1e7ed6f-5083-477b-b44c-9943a6181359': 'text',
'ce972d02-5013-46d4-9d1d-f09df1ac346a': 'datetime',
'6a34bfe3-fece-4da1-9136-a7b1e5ae3319': 'number',
'a8fcdf65-91bc-46fd-82f6-1234758345a1': 'lookup',
'b4919f2e-2996-48e4-a03c-ba39fb64386c': 'picklist',
'80108f9d-1e75-40fa-9fa9-02be4ddc1da1': 'longtext',
};
const picklistCache: Record<string, any> = {};
const picklistFields = metadata.data.filter(field =>
fieldTypeMap[field.systemFieldTypeId] === 'picklist'
);
for (const field of picklistFields) {
const largeLists = ['objecttypecode', 'resultcode'];
if (!largeLists.includes(field.fieldName.toLowerCase())) {
try {
const picklistData = await client.getPicklistValues(objectTypeStr, field.fieldName);
if (picklistData.data?.values && Array.isArray(picklistData.data.values)) {
picklistCache[field.fieldName] = picklistData.data.values;
}
} catch (error) {
picklistCache[field.fieldName] = [];
}
}
}
for (const field of metadata.data) {
const systemFields = ['createdby', 'modifiedby', 'deletedby', 'createdon', 'modifiedon', 'deletedon'];
if (field.fieldName.endsWith('id') && !field.label) {
continue;
}
if (systemFields.includes(field.fieldName.toLowerCase())) {
continue;
}
const fieldType = fieldTypeMap[field.systemFieldTypeId] || 'text';
const isRequired = false;
switch (fieldType) {
case 'text':
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
});
break;
case 'number':
props[field.fieldName] = Property.Number({
displayName: field.label || field.fieldName,
required: isRequired,
});
break;
case 'datetime':
props[field.fieldName] = Property.DateTime({
displayName: field.label || field.fieldName,
required: isRequired,
description: 'Date and time in UTC format',
});
break;
case 'picklist':{
const largeLists = ['objecttypecode', 'resultcode'];
if (largeLists.includes(field.fieldName.toLowerCase())) {
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
description: 'Enter the numeric value for this field',
});
} else {
const values = picklistCache[field.fieldName] || [];
if (values.length > 0 && values.length <= 20) {
const options = values.map((option: any) => ({
label: option.name || option.value,
value: option.value,
}));
props[field.fieldName] = Property.StaticDropdown({
displayName: field.label || field.fieldName,
required: isRequired,
options: {
disabled: false,
options,
},
});
} else {
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
description: values.length > 20
? `Enter numeric value (${values.length} options available)`
: 'Enter the value for this field',
});
}
}
break;
}
case 'longtext':{
props[field.fieldName] = Property.LongText({
displayName: field.label || field.fieldName,
required: isRequired,
description: 'Long text content',
});
break;
}
case 'lookup':{
let description = 'Record ID (GUID)';
if (field.fieldName.includes('account')) description = 'Account record ID';
else if (field.fieldName.includes('contact')) description = 'Contact record ID';
else if (field.fieldName.includes('owner')) description = 'User record ID for owner';
else if (field.fieldName.includes('product')) description = 'Product record ID';
else if (field.fieldName.includes('user')) description = 'User record ID';
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
description,
});
break;
}
default:{
props[field.fieldName] = Property.ShortText({
displayName: field.label || field.fieldName,
required: isRequired,
description: `${fieldType} field`,
});
break;
}
}
}
return props;
},
});

View File

@@ -0,0 +1,216 @@
import { createTrigger, TriggerStrategy, Property } from '@activepieces/pieces-framework';
import { HttpMethod } from '@activepieces/pieces-common';
import { fireberryAuth } from '../../index';
import { objectTypeDropdown } from '../common/props';
import { FireberryClient } from '../common/client';
export const recordCreatedOrUpdatedTrigger = createTrigger({
name: 'record_created_or_updated',
displayName: 'Record Created or Updated',
description: 'Fires when a record is created or updated in Fireberry.',
auth: fireberryAuth,
props: {
objectType: objectTypeDropdown,
triggerType: Property.StaticDropdown({
displayName: 'Trigger Type',
required: true,
defaultValue: 'both',
options: {
disabled: false,
options: [
{ label: 'Created or Updated', value: 'both' },
{ label: 'Created Only', value: 'created' },
{ label: 'Updated Only', value: 'updated' },
],
},
}),
lookbackMinutes: Property.Number({
displayName: 'Lookback Period (minutes)',
required: false,
defaultValue: 60,
description: 'How far back to look for records on first run (default: 60 minutes)',
}),
},
type: TriggerStrategy.POLLING,
sampleData: {
accountname: "Sample Account",
emailaddress1: "sample@example.com",
createdon: "2025-07-17T10:39:30.003",
modifiedon: "2025-07-17T10:39:30.003",
accountid: "12345678-1234-1234-1234-123456789abc",
},
async test({ auth, propsValue }) {
const client = new FireberryClient(auth);
const { objectType } = propsValue;
if (!objectType) {
return [];
}
try {
const objectsMetadata = await client.getObjectsMetadata();
const targetObject = objectsMetadata.data.find(obj => obj.systemName === objectType);
if (!targetObject) {
throw new Error(`Object type '${objectType}' not found`);
}
const objectNumber = parseInt(targetObject.objectType);
const sevenDaysAgo = new Date(Date.now() - (7 * 24 * 60 * 60 * 1000)).toISOString().split('T')[0];
const response = await client.request<{
success: boolean;
data: {
ObjectName: string;
SystemName: string;
ObjectType: number;
PrimaryKey: string;
PrimaryField: string;
PageNum: number;
SortBy: string;
SortBy_Desc: boolean;
IsLastPage: boolean;
Columns: Array<Record<string, any>>;
Data: Array<Record<string, any>>;
};
}>({
method: HttpMethod.POST,
resourceUri: '/api/query',
body: {
objecttype: objectNumber,
query: `modifiedon >= '${sevenDaysAgo}'`,
sort_by: 'modifiedon',
sort_type: 'desc',
page_size: 3,
},
});
if (!response.success) {
throw new Error('Failed to fetch sample data from Fireberry');
}
const records = response.data.Data || [];
if (records.length === 0) {
const fallbackResponse = await client.request<{
success: boolean;
data: {
Data: Array<Record<string, any>>;
};
}>({
method: HttpMethod.POST,
resourceUri: '/api/query',
body: {
objecttype: objectNumber,
sort_by: 'modifiedon',
sort_type: 'desc',
page_size: 3,
},
});
if (fallbackResponse.success && fallbackResponse.data.Data) {
return fallbackResponse.data.Data;
}
}
return records;
} catch (error: any) {
console.error('Failed to generate sample data:', error);
return [{
[objectType === 'Contact' ? 'firstname' : 'accountname']: 'Sample Record',
createdon: new Date().toISOString(),
modifiedon: new Date().toISOString(),
}];
}
},
async onEnable({ store }) {
await store.put('lastPollTime', new Date().toISOString());
},
async onDisable({ store }) {
await store.delete('lastPollTime');
},
async run({ auth, propsValue, store }) {
const client = new FireberryClient(auth);
const { objectType, triggerType, lookbackMinutes } = propsValue;
let lastPollTime = await store.get<string>('lastPollTime');
if (!lastPollTime) {
const lookback = lookbackMinutes || 60;
const cutoffTime = new Date(Date.now() - (lookback * 60 * 1000));
lastPollTime = cutoffTime.toISOString();
}
const objectsMetadata = await client.getObjectsMetadata();
const targetObject = objectsMetadata.data.find(obj => obj.systemName === objectType);
if (!targetObject) {
throw new Error(`Object type '${objectType}' not found`);
}
const objectNumber = parseInt(targetObject.objectType);
let query = '';
const cutoffDate = new Date(lastPollTime).toISOString().split('T')[0];
if (triggerType === 'created') {
query = `createdon >= '${cutoffDate}'`;
} else if (triggerType === 'updated') {
query = `modifiedon >= '${cutoffDate}'`;
} else {
query = `modifiedon >= '${cutoffDate}'`;
}
try {
const response = await client.request<{
success: boolean;
data: {
ObjectName: string;
SystemName: string;
ObjectType: number;
PrimaryKey: string;
PrimaryField: string;
PageNum: number;
SortBy: string;
SortBy_Desc: boolean;
IsLastPage: boolean;
Columns: Array<Record<string, any>>;
Data: Array<Record<string, any>>;
};
}>({
method: HttpMethod.POST,
resourceUri: '/api/query',
body: {
objecttype: objectNumber,
query: query,
sort_by: 'modifiedon',
sort_type: 'desc',
page_size: 50,
},
});
if (!response.success) {
throw new Error('Failed to fetch records from Fireberry');
}
const records = response.data.Data || [];
await store.put('lastPollTime', new Date().toISOString());
return records.reverse();
} catch (error: any) {
if (error.message?.includes('429')) {
throw new Error('Rate limit exceeded. Please try again later.');
}
if (error.message?.includes('401') || error.message?.includes('403')) {
throw new Error('Authentication failed. Please check your Fireberry API key.');
}
console.error('Fireberry trigger error:', error);
throw new Error(`Failed to fetch records: ${error.message || 'Unknown error'}`);
}
},
});