Zapier integration (#491)
* create zapier app * install sanctum * move OAuthProviderController * make `api-external` middleware * add zapier endpoints * add tests * token management * zapier event handler * add policy * use `slug` instead of `id` * wip * check policies * change api prefix to `external` * ui tweaks * validate token abilities * open zapier URL * zapier ui tweaks * update zap * Fix linting * Added sample endpoints + minor UI changes * Run PHP code linter --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
65
integrations/zapier/.gitignore
vendored
Normal file
65
integrations/zapier/.gitignore
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# environment variables file
|
||||
.env
|
||||
.environment
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# zapier app config
|
||||
.zapierapprc
|
||||
45
integrations/zapier/ZAPIER.md
Normal file
45
integrations/zapier/ZAPIER.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Zapier
|
||||
|
||||
Install Zapier
|
||||
|
||||
```
|
||||
npm install -g zapier-platform-cli
|
||||
```
|
||||
|
||||
Install dependencies
|
||||
|
||||
```
|
||||
cd `zapier`
|
||||
npm install
|
||||
```
|
||||
|
||||
Login to Zapier
|
||||
|
||||
```
|
||||
zapier login
|
||||
```
|
||||
|
||||
Register the app
|
||||
|
||||
```
|
||||
zapier register [TITLE]
|
||||
```
|
||||
|
||||
Publish the app
|
||||
|
||||
```
|
||||
zapier push
|
||||
```
|
||||
|
||||
Set the base URL to receive webhooks from Zapier. The version usually looks like 1.0.0.
|
||||
|
||||
```
|
||||
zapier env:set [VERSION] BASE_URL=[BASE_URL]
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
- Create an access token: http://localhost:3000/settings/access-tokens
|
||||
- Create a Zap
|
||||
- Authenticate using your token
|
||||
- Submit a form
|
||||
20
integrations/zapier/authentication.js
vendored
Normal file
20
integrations/zapier/authentication.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
type: 'custom',
|
||||
test: {
|
||||
removeMissingValuesFrom: { body: false, params: false },
|
||||
url: '{{process.env.BASE_URL}}/external/zapier/validate',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
helpText:
|
||||
'Enter your API key, located at https://opnform.com/settings/access-tokens',
|
||||
computed: false,
|
||||
key: 'api_key',
|
||||
required: true,
|
||||
label: 'API Key',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
connectionLabel: '{{bundle.inputData.name}} {{bundle.inputData.email}}',
|
||||
customConfig: {},
|
||||
};
|
||||
24
integrations/zapier/index.js
vendored
Normal file
24
integrations/zapier/index.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
const authentication = require('./authentication');
|
||||
const newSubmissionTrigger = require('./triggers/new_submission.js');
|
||||
const listWorkspacesTrigger = require('./triggers/list_workspaces.js');
|
||||
const listFormsTrigger = require('./triggers/list_forms.js');
|
||||
|
||||
module.exports = {
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
requestTemplate: {
|
||||
headers: {
|
||||
Authorization: 'Bearer {{bundle.authData.api_key}}',
|
||||
'X-API-KEY': '{{bundle.authData.api_key}}',
|
||||
},
|
||||
params: { api_key: '{{bundle.authData.api_key}}' },
|
||||
body: {},
|
||||
},
|
||||
authentication: authentication,
|
||||
searches: {},
|
||||
triggers: {
|
||||
[newSubmissionTrigger.key]: newSubmissionTrigger,
|
||||
[listWorkspacesTrigger.key]: listWorkspacesTrigger,
|
||||
[listFormsTrigger.key]: listFormsTrigger,
|
||||
},
|
||||
};
|
||||
3799
integrations/zapier/package-lock.json
generated
Normal file
3799
integrations/zapier/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
integrations/zapier/package.json
Normal file
23
integrations/zapier/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "opnform-test-1",
|
||||
"version": "1.0.1",
|
||||
"description": "opnform-test-1",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "jest --testTimeout 10000"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v18",
|
||||
"npm": ">=5.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"zapier-platform-core": "15.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.6.0"
|
||||
},
|
||||
"private": true,
|
||||
"zapier": {
|
||||
"convertedByCLIVersion": "15.8.0"
|
||||
}
|
||||
}
|
||||
20
integrations/zapier/test/triggers/list_forms.test.js
vendored
Normal file
20
integrations/zapier/test/triggers/list_forms.test.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
// Use this to make test calls into your app:
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
// read the `.env` file into the environment, if available
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('triggers.list_forms', () => {
|
||||
it('should run', async () => {
|
||||
const bundle = { inputData: {} };
|
||||
|
||||
const results = await appTester(
|
||||
App.triggers['list_forms'].operation.perform,
|
||||
bundle
|
||||
);
|
||||
expect(results).toBeDefined();
|
||||
// TODO: add more assertions
|
||||
});
|
||||
});
|
||||
20
integrations/zapier/test/triggers/list_workspaces.test.js
vendored
Normal file
20
integrations/zapier/test/triggers/list_workspaces.test.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
// Use this to make test calls into your app:
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
// read the `.env` file into the environment, if available
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('triggers.list_workspaces', () => {
|
||||
it('should run', async () => {
|
||||
const bundle = { inputData: {} };
|
||||
|
||||
const results = await appTester(
|
||||
App.triggers['list_workspaces'].operation.perform,
|
||||
bundle
|
||||
);
|
||||
expect(results).toBeDefined();
|
||||
// TODO: add more assertions
|
||||
});
|
||||
});
|
||||
20
integrations/zapier/test/triggers/new_submission.test.js
vendored
Normal file
20
integrations/zapier/test/triggers/new_submission.test.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
// Use this to make test calls into your app:
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
// read the `.env` file into the environment, if available
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('triggers.new_submission', () => {
|
||||
it('should run', async () => {
|
||||
const bundle = { inputData: {} };
|
||||
|
||||
const results = await appTester(
|
||||
App.triggers['new_submission'].operation.perform,
|
||||
bundle
|
||||
);
|
||||
expect(results).toBeDefined();
|
||||
// TODO: add more assertions
|
||||
});
|
||||
});
|
||||
37
integrations/zapier/triggers/list_forms.js
vendored
Normal file
37
integrations/zapier/triggers/list_forms.js
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: {
|
||||
headers: { Accept: 'application/json' },
|
||||
params: {
|
||||
api_key: '{{bundle.authData.api_key}}',
|
||||
workspace_id: '{{bundle.inputData.workspace_id}}',
|
||||
},
|
||||
removeMissingValuesFrom: { body: false, params: false },
|
||||
url: '{{process.env.BASE_URL}}/external/zapier/forms',
|
||||
},
|
||||
inputFields: [
|
||||
{
|
||||
key: 'workspace_id',
|
||||
type: 'string',
|
||||
dynamic: 'list_workspaces.id.name',
|
||||
label: 'Workspace',
|
||||
required: true,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
],
|
||||
sample: { id: 'my-form', name: 'My Form' },
|
||||
outputFields: [
|
||||
{ key: 'id', label: 'ID', type: 'string' },
|
||||
{ key: 'name', label: 'Name', type: 'string' },
|
||||
],
|
||||
canPaginate: false,
|
||||
},
|
||||
display: {
|
||||
description: 'Get the list of all forms',
|
||||
hidden: true,
|
||||
label: 'List Forms',
|
||||
},
|
||||
key: 'list_forms',
|
||||
noun: 'Form',
|
||||
};
|
||||
21
integrations/zapier/triggers/list_workspaces.js
vendored
Normal file
21
integrations/zapier/triggers/list_workspaces.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: {
|
||||
headers: { Accept: 'application/json' },
|
||||
removeMissingValuesFrom: { body: false, params: false },
|
||||
url: '{{process.env.BASE_URL}}/external/zapier/workspaces',
|
||||
},
|
||||
sample: { id: 1, name: 'My Workspace' },
|
||||
outputFields: [
|
||||
{ key: 'id', label: 'ID', type: 'integer' },
|
||||
{ key: 'name', label: 'Name', type: 'string' },
|
||||
],
|
||||
},
|
||||
display: {
|
||||
description: "Get the list of all user's workspaces",
|
||||
hidden: true,
|
||||
label: 'List Workspaces',
|
||||
},
|
||||
key: 'list_workspaces',
|
||||
noun: 'Workspace',
|
||||
};
|
||||
81
integrations/zapier/triggers/new_submission.js
vendored
Normal file
81
integrations/zapier/triggers/new_submission.js
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
const perform = async (z, bundle) => {
|
||||
return [bundle.cleanedRequest];
|
||||
};
|
||||
|
||||
const performList = async (z, bundle) => {
|
||||
// Replace with the actual URL that returns recent submissions
|
||||
const response = await z.request({
|
||||
url: `${process.env.BASE_URL}/external/zapier/submissions/recent`,
|
||||
params: {
|
||||
form_id: bundle.inputData.form_id,
|
||||
},
|
||||
});
|
||||
|
||||
// Ensure the structure of the response matches the webhook data structure
|
||||
return response.data;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: perform,
|
||||
performList: performList,
|
||||
sample: {
|
||||
"form_title": "Your form title",
|
||||
"form_slug": "your-form-slug-og4lhg"
|
||||
},
|
||||
inputFields: [
|
||||
{
|
||||
key: 'workspace_id',
|
||||
type: 'string',
|
||||
label: 'Workspace',
|
||||
dynamic: 'list_workspaces.id.name',
|
||||
required: true,
|
||||
list: false,
|
||||
altersDynamicFields: true,
|
||||
},
|
||||
{
|
||||
key: 'form_id',
|
||||
type: 'string',
|
||||
label: 'Form',
|
||||
dynamic: 'list_forms.id.name',
|
||||
required: true,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
],
|
||||
type: 'hook',
|
||||
performUnsubscribe: {
|
||||
body: {
|
||||
hookUrl: '{{bundle.subscribeData.id}}',
|
||||
form_id: '{{bundle.inputData.form_id}}',
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
method: 'DELETE',
|
||||
removeMissingValuesFrom: { body: false, params: false },
|
||||
url: '{{process.env.BASE_URL}}/external/zapier/webhook',
|
||||
},
|
||||
performSubscribe: {
|
||||
body: {
|
||||
hookUrl: '{{bundle.targetUrl}}',
|
||||
form_id: '{{bundle.inputData.form_id}}',
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
removeMissingValuesFrom: { body: false, params: false },
|
||||
url: '{{process.env.BASE_URL}}/external/zapier/webhook',
|
||||
},
|
||||
},
|
||||
display: {
|
||||
description: 'Triggers when a new submission is created.',
|
||||
hidden: false,
|
||||
label: 'New Submission',
|
||||
},
|
||||
key: 'new_submission',
|
||||
noun: 'Submission',
|
||||
};
|
||||
Reference in New Issue
Block a user