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,18 @@
{
"extends": ["../../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@@ -0,0 +1,7 @@
# pieces-fireberry
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build pieces-fireberry` to build the library.

View File

@@ -0,0 +1,10 @@
{
"name": "@activepieces/piece-fireberry",
"version": "0.0.5",
"type": "commonjs",
"main": "./src/index.js",
"types": "./src/index.d.ts",
"dependencies": {
"tslib": "^2.3.0"
}
}

View File

@@ -0,0 +1,65 @@
{
"name": "pieces-fireberry",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/pieces/community/fireberry/src",
"projectType": "library",
"release": {
"version": {
"manifestRootsToUpdate": [
"dist/{projectRoot}"
],
"currentVersionResolver": "git-tag",
"fallbackCurrentVersionResolver": "disk"
}
},
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/pieces/community/fireberry",
"tsConfig": "packages/pieces/community/fireberry/tsconfig.lib.json",
"packageJson": "packages/pieces/community/fireberry/package.json",
"main": "packages/pieces/community/fireberry/src/index.ts",
"assets": [
"packages/pieces/community/fireberry/*.md",
{
"input": "packages/pieces/community/fireberry/src/i18n",
"output": "./src/i18n",
"glob": "**/!(i18n.json)"
}
],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
},
"dependsOn": [
"^build",
"prebuild"
]
},
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
]
},
"prebuild": {
"executor": "nx:run-commands",
"options": {
"cwd": "packages/pieces/community/fireberry",
"command": "bun install --no-save --silent"
},
"dependsOn": [
"^build"
]
}
}
}

View File

@@ -0,0 +1,38 @@
{
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Geben Sie Ihren Fireberry API Key ein. Sie können ihn aus Ihren Fireberry Account-Einstellungen generieren.",
"Create Record": "Datensatz erstellen",
"Update Record": "Datensatz aktualisieren",
"Delete Records": "Datensätze löschen",
"Find Records": "Datensätze finden",
"Create a new record in Fireberry.": "Erstellen Sie einen neuen Rekord in Fireberry.",
"Update an existing record in Fireberry.": "Aktualisiere einen vorhandenen Eintrag in Fireberry.",
"Delete records from Fireberry.": "Lösche Datensätze aus Fireberry.",
"Search for records in Fireberry.": "Suche nach Datensätzen in Fireberry.",
"Object Type": "Objekttyp",
"Fields": "Felder",
"Record": "Datensatz",
"Fields to Update": "Zu aktualisierende Felder",
"Records to Delete": "Zu löschende Datensätze",
"Confirm Deletion": "Löschen bestätigen",
"Search Query": "Suchanfrage",
"Fields to Return": "Felder zum Zurückgeben",
"Sort By Field": "Nach Feld sortieren",
"Sort Order": "Sortierung",
"Page Size": "Einträge pro Seite",
"Page Number": "Seitennummer",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "Aktivieren Sie diese Option, um zu bestätigen, dass Sie die ausgewählten Datensätze dauerhaft löschen möchten. Diese Aktion kann nicht rückgängig gemacht werden.",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "Suchkriterien eingeben (z.B. \"accountname=John\" oder \"email contains @example.com\"). Leer lassen um alle Datensätze zu erhalten.",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "Systemname des zu sortierenden Feldes (z.B. \"createdon\", \"Accountname\")",
"Number of records to return (max 50)": "Anzahl der zurückzugebenden Datensätze (max. 50)",
"Page number to retrieve (max 10)": "Seitenzahl zum Abrufen (max 10)",
"Descending (newest first)": "Absteigend (neueste zuerst)",
"Ascending (oldest first)": "Aufsteigend (älteste zuerst)",
"Record Created or Updated": "Datensatz erstellt oder aktualisiert",
"Fires when a record is created or updated in Fireberry.": "Feuert ab, wenn ein Datensatz in Fireberry erstellt oder aktualisiert wird.",
"Trigger Type": "Auslöse-Typ",
"Lookback Period (minutes)": "Sehzeitraum (Minuten)",
"How far back to look for records on first run (default: 60 minutes)": "Wie weit zurück nach Datensätzen beim ersten Start gesucht werden soll (Standard: 60 Minuten)",
"Created or Updated": "Erstellt oder aktualisiert",
"Created Only": "Nur erstellt",
"Updated Only": "Nur aktualisiert"
}

View File

@@ -0,0 +1,38 @@
{
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Introduzca su Clave API de Fireberry. Puede generarla desde la configuración de su cuenta Fireberry.",
"Create Record": "Crear registro",
"Update Record": "Actualizar registro",
"Delete Records": "Borrar registros",
"Find Records": "Buscar registros",
"Create a new record in Fireberry.": "Crear un nuevo registro en Fireberry.",
"Update an existing record in Fireberry.": "Actualizar un registro existente en Fireberry.",
"Delete records from Fireberry.": "Eliminar registros de Fireberry.",
"Search for records in Fireberry.": "Buscar registros en Fireberry.",
"Object Type": "Tipo de objeto",
"Fields": "Campos",
"Record": "Grabar",
"Fields to Update": "Campos a actualizar",
"Records to Delete": "Registros a eliminar",
"Confirm Deletion": "Confirmar eliminación",
"Search Query": "Buscar consulta",
"Fields to Return": "Campos a devolver",
"Sort By Field": "Ordenar por campo",
"Sort Order": "Ordenar",
"Page Size": "Tamaño de página",
"Page Number": "Número de página",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "Marque esta casilla para confirmar que desea eliminar permanentemente los registros seleccionados. Esta acción no se puede deshacer.",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "Introduzca criterios de búsqueda (por ejemplo, \"accountname=John\" o \"email contiene @example.com\"). Deje en blanco para obtener todos los registros.",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "Nombre del sistema del campo por el que ordenar (por ej., \"createdon\", \"nombre\")",
"Number of records to return (max 50)": "Número de registros a devolver (máx. 50)",
"Page number to retrieve (max 10)": "Número de página a recuperar (máx. 10)",
"Descending (newest first)": "Descendente (más reciente primero)",
"Ascending (oldest first)": "Ascendente (más antiguo primero)",
"Record Created or Updated": "Registro Creado o Actualizado",
"Fires when a record is created or updated in Fireberry.": "Dispara cuando un registro es creado o actualizado en Fireberry.",
"Trigger Type": "Tipo de disparador",
"Lookback Period (minutes)": "Periodo de búsqueda (minutos)",
"How far back to look for records on first run (default: 60 minutes)": "Qué tan lejos buscar registros en la primera ejecución (por defecto: 60 minutos)",
"Created or Updated": "Creado o actualizado",
"Created Only": "Sólo creado",
"Updated Only": "Sólo Actualizado"
}

View File

@@ -0,0 +1,38 @@
{
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Entrez votre clé API Fireberry. Vous pouvez la générer à partir des paramètres de votre compte Fireberry.",
"Create Record": "Créer un enregistrement",
"Update Record": "Mettre à jour l'enregistrement",
"Delete Records": "Supprimer les enregistrements",
"Find Records": "Trouver des enregistrements",
"Create a new record in Fireberry.": "Créer un nouvel enregistrement dans Fireberry.",
"Update an existing record in Fireberry.": "Mettre à jour un enregistrement existant dans Fireberry.",
"Delete records from Fireberry.": "Supprimer les enregistrements de Fireberry.",
"Search for records in Fireberry.": "Rechercher des enregistrements dans Fireberry.",
"Object Type": "Type d'objet",
"Fields": "Champs",
"Record": "Enregistrements",
"Fields to Update": "Champs à mettre à jour",
"Records to Delete": "Enregistrements à supprimer",
"Confirm Deletion": "Confirmer la suppression",
"Search Query": "Requête de recherche",
"Fields to Return": "Champs à retourner",
"Sort By Field": "Trier par champ",
"Sort Order": "Ordre de tri",
"Page Size": "Nombre d'élément",
"Page Number": "Numéro de page",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "Cochez cette case pour confirmer que vous souhaitez supprimer définitivement les enregistrements sélectionnés. Cette action ne peut pas être annulée.",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "Entrez les critères de recherche (par exemple, \"accountname=John\" ou \"email contains @example.com\"). Laissez vide pour obtenir tous les enregistrements.",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "Nom du système du champ à trier (par exemple, \"createdon\", \"accountname\")",
"Number of records to return (max 50)": "Nombre d'enregistrements à retourner (max 50)",
"Page number to retrieve (max 10)": "Numéro de page à récupérer (max 10)",
"Descending (newest first)": "Décroissant (plus récent en premier)",
"Ascending (oldest first)": "Ascendant (plus ancien en premier)",
"Record Created or Updated": "Enregistrement créé ou mis à jour",
"Fires when a record is created or updated in Fireberry.": "Se déclenche lorsqu'un enregistrement est créé ou mis à jour dans Fireberry.",
"Trigger Type": "Type de déclencheur",
"Lookback Period (minutes)": "Période de retour (minutes)",
"How far back to look for records on first run (default: 60 minutes)": "Jusqu'à quel point il faut chercher les enregistrements lors de la première exécution (par défaut: 60 minutes)",
"Created or Updated": "Créé ou mis à jour",
"Created Only": "Créé uniquement",
"Updated Only": "Mis à jour uniquement"
}

View File

@@ -0,0 +1,38 @@
{
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Fireberry API キーを入力します。Fireberry アカウントの設定から生成できます。",
"Create Record": "レコードを作成",
"Update Record": "更新記録",
"Delete Records": "レコードを削除",
"Find Records": "レコードを検索",
"Create a new record in Fireberry.": "Fireberryで新しいレコードを作成します。",
"Update an existing record in Fireberry.": "Fireberryで既存のレコードを更新します。",
"Delete records from Fireberry.": "Fireberryからレコードを削除する。",
"Search for records in Fireberry.": "Fireberryでレコードを検索します。",
"Object Type": "オブジェクトの種類",
"Fields": "フィールド",
"Record": "レコード",
"Fields to Update": "更新するフィールド",
"Records to Delete": "削除するレコード",
"Confirm Deletion": "削除の確認",
"Search Query": "検索クエリ",
"Fields to Return": "Fields to Return",
"Sort By Field": "フィールドでソート",
"Sort Order": "並び順",
"Page Size": "ページサイズ",
"Page Number": "ページ番号",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "選択したレコードを完全に削除するかどうかを確認するには、このチェックボックスをオンにします。この操作は元に戻せません。",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "検索条件を入力してください(例:\"accountname=John\" または \"email contains @example.com\")。すべてのレコードを取得するには空のままにしてください。",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "システム名でソートする項目(例:\"createdon\", \"accountname\")",
"Number of records to return (max 50)": "Number of records to return (max 50)",
"Page number to retrieve (max 10)": "取得するページ番号最大10",
"Descending (newest first)": "降順",
"Ascending (oldest first)": "昇順 (古い順)",
"Record Created or Updated": "作成または更新されたレコード",
"Fires when a record is created or updated in Fireberry.": "Fireberryでレコードが作成または更新されたときに発火します。",
"Trigger Type": "トリガータイプ",
"Lookback Period (minutes)": "ルックバック期間 (分)",
"How far back to look for records on first run (default: 60 minutes)": "最初の実行時にレコードを探すまでの距離デフォルト60分",
"Created or Updated": "作成または更新",
"Created Only": "作成者のみ",
"Updated Only": "更新のみ"
}

View File

@@ -0,0 +1,38 @@
{
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Voer uw Fireberry API-sleutel in. U kunt deze genereren via uw Fireberry account instellingen.",
"Create Record": "Record Maken",
"Update Record": "Update Record",
"Delete Records": "Records verwijderen",
"Find Records": "Records zoeken",
"Create a new record in Fireberry.": "Maak een nieuw record in Fireberry.",
"Update an existing record in Fireberry.": "Werk een bestaand record bij in Fireberry.",
"Delete records from Fireberry.": "Verwijder records van Fireberry.",
"Search for records in Fireberry.": "Zoek naar records in Fireberry.",
"Object Type": "Object type",
"Fields": "Velden",
"Record": "Opnemen",
"Fields to Update": "Velden om te updaten",
"Records to Delete": "Te verwijderen records",
"Confirm Deletion": "Verwijdering bevestigen",
"Search Query": "Zoek query",
"Fields to Return": "Velden om te retourneren",
"Sort By Field": "Sorteren op veld",
"Sort Order": "Sorteren bestelling",
"Page Size": "Paginagrootte",
"Page Number": "Pagina Nummer",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "Vink dit vakje aan om te bevestigen dat u de geselecteerde records permanent wilt verwijderen. Deze actie kan niet ongedaan worden gemaakt.",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "Voer zoekcriteria in (bijv. \"accountname=John\" of \"e-mail bevat @example.com\"). Laat leeg om alle records te krijgen.",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "Systeem naam van het te sorteren veld (bijv. \"aangemaakt\", \"accountname\")",
"Number of records to return (max 50)": "Aantal terugkerende records (max 50)",
"Page number to retrieve (max 10)": "Paginanummer op te halen (max 10)",
"Descending (newest first)": "Aflopend (nieuwste eerst)",
"Ascending (oldest first)": "Oplopend (oudste eerst)",
"Record Created or Updated": "Record gemaakt of bijgewerkt",
"Fires when a record is created or updated in Fireberry.": "Vuurt wanneer een record wordt aangemaakt of bijgewerkt in Fireberry.",
"Trigger Type": "Trigger type",
"Lookback Period (minutes)": "Zoekperiode (minuten)",
"How far back to look for records on first run (default: 60 minutes)": "Hoe ver terug te zoeken naar records bij eerste uitvoering (standaard: 60 minuten)",
"Created or Updated": "Gemaakt of bijgewerkt",
"Created Only": "Alleen gemaakt",
"Updated Only": "Alleen bijgewerkt"
}

View File

@@ -0,0 +1,38 @@
{
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Digite sua chave de API do Fireberry. Você pode gerá-la a partir das configurações da sua conta Fireberry.",
"Create Record": "Criar Registro",
"Update Record": "Atualizar Registro",
"Delete Records": "Excluir registros",
"Find Records": "Encontrar registros",
"Create a new record in Fireberry.": "Criar um novo registro em Fireberry.",
"Update an existing record in Fireberry.": "Atualizar um registro existente no Fireberry.",
"Delete records from Fireberry.": "Excluir registros da Fireberry.",
"Search for records in Fireberry.": "Procurar por registros na Fireberry.",
"Object Type": "Tipo de objeto",
"Fields": "campos",
"Record": "Gravar",
"Fields to Update": "Campos para Atualização",
"Records to Delete": "Registros para Excluir",
"Confirm Deletion": "Confirmar Exclusão",
"Search Query": "Consulta de Pesquisa",
"Fields to Return": "Campos para Devolver",
"Sort By Field": "Ordenar por Campo",
"Sort Order": "Ordem de classificação",
"Page Size": "Tamanho da página",
"Page Number": "Número da página",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "Marque esta caixa para confirmar que deseja apagar permanentemente os registros selecionados. Esta ação não pode ser desfeita.",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "Digite critérios de pesquisa (por exemplo, \"accountname=John\" ou \"email contains @example.com\"). Deixe em branco para obter todos os registros.",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "Nome do sistema do campo para ordenar (por exemplo, \"criado\", \"conta\")",
"Number of records to return (max 50)": "Número de registros a retornar (máx. 50)",
"Page number to retrieve (max 10)": "Número de página a ser recuperada (máx. 10)",
"Descending (newest first)": "Descendente (mais recente primeiro)",
"Ascending (oldest first)": "Crescente (mais antigos primeiro)",
"Record Created or Updated": "Registro Criado ou Atualizado",
"Fires when a record is created or updated in Fireberry.": "Atira quando um registro é criado ou atualizado no Fireberry.",
"Trigger Type": "Tipo de gatilho",
"Lookback Period (minutes)": "Período de Lookback (minutos)",
"How far back to look for records on first run (default: 60 minutes)": "Quão longe de procurar registros na primeira execução (padrão: 60 minutos)",
"Created or Updated": "Criado ou Atualizado",
"Created Only": "Criado Apenas",
"Updated Only": "Somente Atualizado"
}

View File

@@ -0,0 +1,39 @@
{
"Fireberry": "Огненная ягода",
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Введите ваш API ключ Fireberry. Вы можете сгенерировать его в настройках вашего аккаунта Fireberry.",
"Create Record": "Создать запись",
"Update Record": "Обновить запись",
"Delete Records": "Удалить записи",
"Find Records": "Найти записи",
"Create a new record in Fireberry.": "Создать новую запись в Fireberry.",
"Update an existing record in Fireberry.": "Обновить существующую запись в Fireberry.",
"Delete records from Fireberry.": "Удалить записи из Fireberry.",
"Search for records in Fireberry.": "Поиск записей в Fireberry.",
"Object Type": "Тип объекта",
"Fields": "Поля",
"Record": "Запись",
"Fields to Update": "Поля для обновления",
"Records to Delete": "Записи для удаления",
"Confirm Deletion": "Подтвердите удаление",
"Search Query": "Поисковый запрос",
"Fields to Return": "Поля для возврата",
"Sort By Field": "Сортировать по полю",
"Sort Order": "Порядок сортировки",
"Page Size": "Размер страницы",
"Page Number": "Номер страницы",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "Установите этот флажок, чтобы подтвердить, что вы хотите удалить выбранные записи. Это действие не может быть отменено.",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "Введите критерии поиска (например, \"accountname=John\" или \"email contains @example.com\"). Оставьте пустым, чтобы получить все записи.",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "Системное имя поля для сортировки (например, \"createdon\", \"accountname\")",
"Number of records to return (max 50)": "Количество возвращаемых записей (макс. 50)",
"Page number to retrieve (max 10)": "Номер страницы для получения (не более 10)",
"Descending (newest first)": "По убыванию (сначала новые)",
"Ascending (oldest first)": "По возрастанию (сначала старые)",
"Record Created or Updated": "Запись создана или обновлена",
"Fires when a record is created or updated in Fireberry.": "Вызывает, когда запись создается или обновляется в Fireberry.",
"Trigger Type": "Тип триггера",
"Lookback Period (minutes)": "Период поиска (минуты)",
"How far back to look for records on first run (default: 60 minutes)": "Как далеко назад искать записи при первом запуске (по умолчанию: 60 минут)",
"Created or Updated": "Создано или обновлено",
"Created Only": "Только созданные",
"Updated Only": "Только обновленные"
}

View File

@@ -0,0 +1,38 @@
{
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Enter your Fireberry API Key. You can generate it from your Fireberry account settings.",
"Create Record": "Create Record",
"Update Record": "Update Record",
"Delete Records": "Delete Records",
"Find Records": "Find Records",
"Create a new record in Fireberry.": "Create a new record in Fireberry.",
"Update an existing record in Fireberry.": "Update an existing record in Fireberry.",
"Delete records from Fireberry.": "Delete records from Fireberry.",
"Search for records in Fireberry.": "Search for records in Fireberry.",
"Object Type": "Object Type",
"Fields": "Fields",
"Record": "Record",
"Fields to Update": "Fields to Update",
"Records to Delete": "Records to Delete",
"Confirm Deletion": "Confirm Deletion",
"Search Query": "Search Query",
"Fields to Return": "Fields to Return",
"Sort By Field": "Sort By Field",
"Sort Order": "Sort Order",
"Page Size": "Page Size",
"Page Number": "Page Number",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "System name of field to sort by (e.g., \"createdon\", \"accountname\")",
"Number of records to return (max 50)": "Number of records to return (max 50)",
"Page number to retrieve (max 10)": "Page number to retrieve (max 10)",
"Descending (newest first)": "Descending (newest first)",
"Ascending (oldest first)": "Ascending (oldest first)",
"Record Created or Updated": "Record Created or Updated",
"Fires when a record is created or updated in Fireberry.": "Fires when a record is created or updated in Fireberry.",
"Trigger Type": "Trigger Type",
"Lookback Period (minutes)": "Lookback Period (minutes)",
"How far back to look for records on first run (default: 60 minutes)": "How far back to look for records on first run (default: 60 minutes)",
"Created or Updated": "Created or Updated",
"Created Only": "Created Only",
"Updated Only": "Updated Only"
}

View File

@@ -0,0 +1,39 @@
{
"Fireberry": "Fireberry",
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Enter your Fireberry API Key. You can generate it from your Fireberry account settings.",
"Create Record": "Create Record",
"Update Record": "Update Record",
"Delete Records": "Delete Records",
"Find Records": "Find Records",
"Create a new record in Fireberry.": "Create a new record in Fireberry.",
"Update an existing record in Fireberry.": "Update an existing record in Fireberry.",
"Delete records from Fireberry.": "Delete records from Fireberry.",
"Search for records in Fireberry.": "Search for records in Fireberry.",
"Object Type": "Object Type",
"Fields": "Fields",
"Record": "Record",
"Fields to Update": "Fields to Update",
"Records to Delete": "Records to Delete",
"Confirm Deletion": "Confirm Deletion",
"Search Query": "Search Query",
"Fields to Return": "Fields to Return",
"Sort By Field": "Sort By Field",
"Sort Order": "Sort Order",
"Page Size": "Phân trang",
"Page Number": "Page Number",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "System name of field to sort by (e.g., \"createdon\", \"accountname\")",
"Number of records to return (max 50)": "Number of records to return (max 50)",
"Page number to retrieve (max 10)": "Page number to retrieve (max 10)",
"Descending (newest first)": "Descending (newest first)",
"Ascending (oldest first)": "Ascending (oldest first)",
"Record Created or Updated": "Record Created or Updated",
"Fires when a record is created or updated in Fireberry.": "Fires when a record is created or updated in Fireberry.",
"Trigger Type": "Trigger Type",
"Lookback Period (minutes)": "Lookback Period (minutes)",
"How far back to look for records on first run (default: 60 minutes)": "How far back to look for records on first run (default: 60 minutes)",
"Created or Updated": "Created or Updated",
"Created Only": "Created Only",
"Updated Only": "Updated Only"
}

View File

@@ -0,0 +1,38 @@
{
"Enter your Fireberry API Key. You can generate it from your Fireberry account settings.": "Enter your Fireberry API Key. You can generate it from your Fireberry account settings.",
"Create Record": "Create Record",
"Update Record": "Update Record",
"Delete Records": "删除记录",
"Find Records": "Find Records",
"Create a new record in Fireberry.": "Create a new record in Fireberry.",
"Update an existing record in Fireberry.": "Update an existing record in Fireberry.",
"Delete records from Fireberry.": "Delete records from Fireberry.",
"Search for records in Fireberry.": "Search for records in Fireberry.",
"Object Type": "Object Type",
"Fields": "Fields",
"Record": "Record",
"Fields to Update": "Fields to Update",
"Records to Delete": "Records to Delete",
"Confirm Deletion": "确认删除",
"Search Query": "Search Query",
"Fields to Return": "Fields to Return",
"Sort By Field": "Sort By Field",
"Sort Order": "Sort Order",
"Page Size": "Page Size",
"Page Number": "Page Number",
"Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.": "Check this box to confirm you want to permanently delete the selected records. This action cannot be undone.",
"Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.": "Enter search criteria (e.g., \"accountname=John\" or \"email contains @example.com\"). Leave empty to get all records.",
"System name of field to sort by (e.g., \"createdon\", \"accountname\")": "System name of field to sort by (e.g., \"createdon\", \"accountname\")",
"Number of records to return (max 50)": "Number of records to return (max 50)",
"Page number to retrieve (max 10)": "Page number to retrieve (max 10)",
"Descending (newest first)": "Descending (newest first)",
"Ascending (oldest first)": "Ascending (oldest first)",
"Record Created or Updated": "Record Created or Updated",
"Fires when a record is created or updated in Fireberry.": "Fires when a record is created or updated in Fireberry.",
"Trigger Type": "Trigger Type",
"Lookback Period (minutes)": "Lookback Period (minutes)",
"How far back to look for records on first run (default: 60 minutes)": "How far back to look for records on first run (default: 60 minutes)",
"Created or Updated": "Created or Updated",
"Created Only": "Created Only",
"Updated Only": "Updated Only"
}

View File

@@ -0,0 +1,32 @@
import { createPiece, PieceAuth } from "@activepieces/pieces-framework";
import { PieceCategory } from "@activepieces/shared";
import { createRecordAction } from "./lib/actions/create-record.action";
import { updateRecordAction } from "./lib/actions/update-record.action";
import { deleteRecordAction } from "./lib/actions/delete-record.action";
import { findRecordAction } from "./lib/actions/find-record.action";
import { recordCreatedOrUpdatedTrigger } from "./lib/triggers/record-created-updated.trigger";
export const fireberryAuth = PieceAuth.SecretText({
displayName: 'API Key',
description: 'Enter your Fireberry API Key. You can generate it from your Fireberry account settings.',
required: true,
});
export const fireberry = createPiece({
displayName: "Fireberry",
auth: fireberryAuth,
minimumSupportedRelease: '0.36.1',
logoUrl: "https://cdn.activepieces.com/pieces/fireberry.png",
authors: ["sparkybug", "onyedikachi-david"],
categories: [PieceCategory.SALES_AND_CRM],
actions: [
createRecordAction,
updateRecordAction,
deleteRecordAction,
findRecordAction,
],
triggers: [
recordCreatedOrUpdatedTrigger,
],
});

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'}`);
}
},
});

View File

@@ -0,0 +1,20 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"importHelpers": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}