Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe), packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier (CLI app), vendor/zapier-platform, and research/zapier vendor corpus. Gate 0 structure checks pass. Product code and research are not yet wired.
This commit is contained in:
commit
b4150c8250
1364 changed files with 6814366 additions and 0 deletions
1
vendor/zapier-platform/example-apps/onedrive/.eslintignore
vendored
Normal file
1
vendor/zapier-platform/example-apps/onedrive/.eslintignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
coverage/*
|
||||
92
vendor/zapier-platform/example-apps/onedrive/.eslintrc.json
vendored
Normal file
92
vendor/zapier-platform/example-apps/onedrive/.eslintrc.json
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"arrow-parens": [2, "always"],
|
||||
"camelcase": 0,
|
||||
"comma-dangle": 0,
|
||||
"comma-spacing": 2,
|
||||
"consistent-return": 2,
|
||||
"curly": [2, "all"],
|
||||
"dot-notation": [2, { "allowKeywords": true }],
|
||||
"eol-last": 2,
|
||||
"eqeqeq": [2, "smart"],
|
||||
"indent": [2, 2],
|
||||
"jsx-quotes": [2, "prefer-double"],
|
||||
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
|
||||
"keyword-spacing": 2,
|
||||
"new-cap": 0,
|
||||
"new-parens": 2,
|
||||
"no-alert": 2,
|
||||
"no-array-constructor": 2,
|
||||
"no-caller": 2,
|
||||
"no-console": [2, { "allow": ["info", "warn", "error"] }],
|
||||
"no-delete-var": 2,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-iterator": 2,
|
||||
"no-label-var": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-loop-func": 2,
|
||||
"no-mixed-spaces-and-tabs": [2, false],
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-native-reassign": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-object": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-process-exit": 2,
|
||||
"no-proto": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-script-url": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-unused-expressions": 2,
|
||||
"no-unused-vars": [
|
||||
2,
|
||||
{ "vars": "all", "args": "after-used", "varsIgnorePattern": "should" }
|
||||
],
|
||||
"no-use-before-define": 2,
|
||||
"no-with": 2,
|
||||
"quotes": [
|
||||
2,
|
||||
"single",
|
||||
{ "avoidEscape": true, "allowTemplateLiterals": true }
|
||||
],
|
||||
"semi": 2,
|
||||
"semi-spacing": [2, { "before": false, "after": true }],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||
"strict": [0, "never"],
|
||||
"yoda": [2, "never"]
|
||||
},
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"before": true,
|
||||
"after": true,
|
||||
"beforeEach": true,
|
||||
"afterEach": true,
|
||||
"it": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 7,
|
||||
"sourceType": "module"
|
||||
}
|
||||
}
|
||||
6
vendor/zapier-platform/example-apps/onedrive/.gitignore
vendored
Normal file
6
vendor/zapier-platform/example-apps/onedrive/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
build
|
||||
docs
|
||||
node_modules
|
||||
*.log
|
||||
.environment
|
||||
coverage
|
||||
1
vendor/zapier-platform/example-apps/onedrive/.nvmrc
vendored
Normal file
1
vendor/zapier-platform/example-apps/onedrive/.nvmrc
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
v6.10.2
|
||||
7
vendor/zapier-platform/example-apps/onedrive/.travis.yml
vendored
Normal file
7
vendor/zapier-platform/example-apps/onedrive/.travis.yml
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "6.10.2"
|
||||
before_script: bash ./travis-setup.sh && npm install -g zapier-platform-cli
|
||||
script: zapier test
|
||||
notifications:
|
||||
email: false
|
||||
88
vendor/zapier-platform/example-apps/onedrive/README.md
vendored
Normal file
88
vendor/zapier-platform/example-apps/onedrive/README.md
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Example App - OneDrive
|
||||
|
||||
This is an example CLI App for the Zapier Developer Platform. It uses the OneDrive API as the target, showing what a
|
||||
full-featured app looks like. It follows the best practices for building an app on Zapier.
|
||||
|
||||
What it demonstrates:
|
||||
|
||||
* OAuth2
|
||||
* File Handling
|
||||
* Hydration
|
||||
* `beforeRequest` handler (and how to skip it for a specific call)
|
||||
* Resources
|
||||
* Breaking an app up into maintable pieces
|
||||
|
||||
Throughout the code we've added `CODE TIP` and `UX TIP` comments. These are good spots to find out some of the best
|
||||
practices and gotchas you may run into.
|
||||
|
||||
## Getting Started
|
||||
|
||||
For a quicker primer on the OneDrive API see [here](https://dev.onedrive.com/getting-started.htm).
|
||||
|
||||
Also note, this app is not one you can download and start using immediately. Since it relies on the OneDrive API,
|
||||
there is some out-of-band setup that you must complete if you want to be able to use the app on zapier.com. We walk you
|
||||
through it all, but be prepared!
|
||||
|
||||
### 1. Install
|
||||
|
||||
To pull down the code and install the needed dependencies, run:
|
||||
|
||||
```bash
|
||||
zapier-platform init --template=onedrive ./
|
||||
npm install
|
||||
```
|
||||
> We recommend using the zapier-platform-cli and `zapier-platform init .` to create an app - you’ll be presented with a list of currently available templates to start with.
|
||||
|
||||
### 2. Seeing Your App on zapier.com
|
||||
|
||||
The first step is to do `zapier-platform register 'My Example OneDrive App'`. This logs you in and tells Zapier about your new app.
|
||||
|
||||
Now try a `zapier-platform push`. At this point, you can go to zapier.com/app/editor and see your app in the Editor.
|
||||
You won't be able to use the app yet (there are a couple steps required before auth works), but this gives you
|
||||
a feel for some of the basic commands of the CLI tool.
|
||||
|
||||
### 3. Register a Microsoft App
|
||||
|
||||
The OneDrive API follows a typical OAuth2 flow that requires your app to have a `CLIENT_ID` and `CLIENT_SECRET`. For
|
||||
security reasons, we cannot include those for you, so you need to head over to `https://apps.dev.microsoft.com/`
|
||||
and register to obtain your own. When registering your app in MS, the minimal information you need to setup is:
|
||||
|
||||
1. App Name - Can be anything, like 'My Example Zapier App'
|
||||
1. Application Secret - Generate a new password, copying that password somewhere for later
|
||||
1. Platform - Choose web. For Redirect URL, run `zapier-platform describe --format=json | grep 'Redirect URI'` in your terminal and paste in the URI provided by Zapier
|
||||
1. Click Save
|
||||
|
||||
You now need to tell Zapier what `CLIENT_ID` and `CLIENT_SECRET` to use when doing the OAuth2 flow. OneDrive uses
|
||||
the Applicaiton ID of the app you just registered as the `CLIENT_ID`, and the Application Secret as the `CLIENT_SECRET`.
|
||||
You can set those in Zapier like so:
|
||||
|
||||
```bash
|
||||
zapier-platform env 1.0.0 CLIENT_ID <app_id>
|
||||
zapier-platform env 1.0.0 CLIENT_SECRET <app_secret>
|
||||
```
|
||||
|
||||
You should now be able to go back to zapier.com and connect your OneDrive account!
|
||||
|
||||
### 4. Exploring
|
||||
|
||||
At this point you have a fully working Zapier app you can build Zaps with. Feel free to make tweaks to your app
|
||||
locally, do a `zapier-platform push`, and see the changes take effect in production right away.
|
||||
|
||||
## Testing
|
||||
|
||||
The tests are provided as an example of how to write good tests, along with stubs to give an idea of what scenarios to
|
||||
cover. If you want to actually run the test suite, you need to do a couple things.
|
||||
|
||||
First is to manually walk through the OAuth2 flow and capture an access token and refresh token. You can then do:
|
||||
|
||||
```bash
|
||||
export ACCESS_TOKEN='<access_token>'
|
||||
export REFRESH_TOKEN='<refresh_token>'
|
||||
```
|
||||
|
||||
The tests will pick those up and run with those credentials.
|
||||
|
||||
The second thing you need to do is update `TEST_RESOURCES` in `test/test-utils.js` with IDs and folder names in your
|
||||
OneDrive account. Once that is updated, you should be able to do:
|
||||
|
||||
`zapier-platform test`
|
||||
127
vendor/zapier-platform/example-apps/onedrive/authentication.js
vendored
Normal file
127
vendor/zapier-platform/example-apps/onedrive/authentication.js
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
'use strict';
|
||||
|
||||
const baseOauthUrl = 'https://login.microsoftonline.com/common/oauth2';
|
||||
// To get your OAuth2 redirect URI, run `zapier describe` and update this variable.
|
||||
// Will looke like 'https://zapier.com/dashboard/auth/oauth/return/App123CLIAPI/'
|
||||
const redirectUri = '';
|
||||
|
||||
const getAuthorizeURL = (z, bundle) => {
|
||||
let url = `${baseOauthUrl}/v2.0/authorize`;
|
||||
|
||||
const urlParts = [
|
||||
`client_id=${process.env.CLIENT_ID}`,
|
||||
`redirect_uri=${encodeURIComponent(bundle.inputData.redirect_uri)}`,
|
||||
'response_type=code',
|
||||
];
|
||||
|
||||
if (bundle.inputData.accountType === 'business') {
|
||||
url = `${baseOauthUrl}/authorize`;
|
||||
} else {
|
||||
urlParts.push(`state=${bundle.inputData.state}`);
|
||||
}
|
||||
|
||||
const finalUrl = `${url}?${urlParts.join('&')}`;
|
||||
|
||||
return finalUrl;
|
||||
};
|
||||
|
||||
const getAccessToken = (z, bundle) => {
|
||||
let url = `${baseOauthUrl}/v2.0/token`;
|
||||
|
||||
const body = {
|
||||
code: bundle.inputData.code,
|
||||
client_id: process.env.CLIENT_ID,
|
||||
client_secret: process.env.CLIENT_SECRET,
|
||||
grant_type: 'authorization_code',
|
||||
};
|
||||
|
||||
if (bundle.inputData.accountType === 'business') {
|
||||
url = `${baseOauthUrl}/token`;
|
||||
body.redirect_uri = redirectUri;
|
||||
body.resource = 'https://graph.microsoft.com/';
|
||||
}
|
||||
|
||||
const promise = z.request(url, {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
});
|
||||
|
||||
return promise.then((response) => ({
|
||||
access_token: response.data.access_token,
|
||||
refresh_token: response.data.refresh_token,
|
||||
id_token: response.data.id_token,
|
||||
}));
|
||||
};
|
||||
|
||||
const refreshAccessToken = (z, bundle) => {
|
||||
let url = `${baseOauthUrl}/v2.0/token`;
|
||||
|
||||
const body = {
|
||||
refresh_token: bundle.authData.refresh_token,
|
||||
client_id: process.env.CLIENT_ID,
|
||||
client_secret: process.env.CLIENT_SECRET,
|
||||
grant_type: 'refresh_token',
|
||||
};
|
||||
|
||||
if (bundle.authData.accountType === 'business') {
|
||||
url = `${baseOauthUrl}/token`;
|
||||
body.redirect_uri = redirectUri;
|
||||
body.resource = 'https://graph.microsoft.com/';
|
||||
}
|
||||
|
||||
const promise = z.request(url, {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
});
|
||||
|
||||
return promise.then((response) => ({
|
||||
access_token: response.data.access_token,
|
||||
refresh_token: response.data.refresh_token,
|
||||
id_token: response.data.id_token,
|
||||
}));
|
||||
};
|
||||
|
||||
// The test call Zapier makes to ensure an access token is valid
|
||||
// UX TIP: Hit an endpoint that always returns data with valid credentials,
|
||||
// like a /profile or /me endpoint. That way the success/failure is related to
|
||||
// the token and not because the user didn't happen to have a recently created record.
|
||||
const testAuth = (z) => {
|
||||
const promise = z.request({
|
||||
url: 'https://graph.microsoft.com/v1.0/me',
|
||||
});
|
||||
|
||||
return promise.then((response) => response.data);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
type: 'oauth2',
|
||||
connectionLabel: '{{bundle.inputData.userPrincipalName}}',
|
||||
oauth2Config: {
|
||||
authorizeUrl: getAuthorizeURL,
|
||||
getAccessToken,
|
||||
refreshAccessToken,
|
||||
// Set so Zapier automatically checks for 401s and calls refreshAccessToken
|
||||
autoRefresh: true,
|
||||
// offline_access is necessary for the refresh_token
|
||||
scope: 'User.Read Files.ReadWrite.All offline_access',
|
||||
},
|
||||
test: testAuth,
|
||||
fields: [
|
||||
{
|
||||
key: 'accountType',
|
||||
label: 'Account Type',
|
||||
choices: {
|
||||
personal: 'Personal - live.com/outlook.com',
|
||||
business: 'Business - Work or School',
|
||||
},
|
||||
default: 'personal',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
14
vendor/zapier-platform/example-apps/onedrive/before-handlers.js
vendored
Normal file
14
vendor/zapier-platform/example-apps/onedrive/before-handlers.js
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
// This adds the needed auth header to a request. By registering this on the
|
||||
// app's `beforeRequest` section, every z.request() call will run this function.
|
||||
const includeBearerToken = (request, z, bundle) => {
|
||||
if (bundle.authData.access_token) {
|
||||
request.headers.Authorization = `Bearer ${bundle.authData.access_token}`;
|
||||
}
|
||||
return request;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
includeBearerToken,
|
||||
};
|
||||
12
vendor/zapier-platform/example-apps/onedrive/constants.js
vendored
Normal file
12
vendor/zapier-platform/example-apps/onedrive/constants.js
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
const BASE_ITEM_URL = 'https://graph.microsoft.com/v1.0';
|
||||
|
||||
const BIG_FILE_MSG = 'File contents too big.';
|
||||
const BINARY_CONTENT_MSG = 'Binary contents unsupported.';
|
||||
|
||||
module.exports = {
|
||||
BASE_ITEM_URL,
|
||||
BIG_FILE_MSG,
|
||||
BINARY_CONTENT_MSG,
|
||||
};
|
||||
80
vendor/zapier-platform/example-apps/onedrive/creates/text-file.js
vendored
Normal file
80
vendor/zapier-platform/example-apps/onedrive/creates/text-file.js
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
|
||||
const utils = require('../utils');
|
||||
const getStringByteSize = utils.getStringByteSize;
|
||||
const baseItem = require('../resources/base-item');
|
||||
const fileResource = require('../resources/file');
|
||||
|
||||
const createTextFile = (z, bundle) => {
|
||||
const folder = bundle.inputData.folder || '';
|
||||
let name = `${bundle.inputData.name}.txt`;
|
||||
|
||||
// Remove potential duplicate extension
|
||||
if (name.endsWith('.txt.txt')) {
|
||||
name = name.slice(0, -4);
|
||||
}
|
||||
|
||||
const fileSize = getStringByteSize(bundle.inputData.file);
|
||||
const contentType = 'text/plain; charset=UTF-8';
|
||||
|
||||
return baseItem.handleCreateWithSession(
|
||||
z,
|
||||
bundle,
|
||||
bundle.inputData.file,
|
||||
fileSize,
|
||||
contentType,
|
||||
folder,
|
||||
name,
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'textFile',
|
||||
noun: 'Text File',
|
||||
|
||||
display: {
|
||||
label: 'Create New Text File',
|
||||
description:
|
||||
'Creates a brand new text file from plain text content you specify.',
|
||||
},
|
||||
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'folder',
|
||||
type: 'string',
|
||||
label: 'Folder',
|
||||
required: false,
|
||||
dynamic: 'folderList._path.name',
|
||||
helpText:
|
||||
'Folder where to place the file. Keep clicking the dropdown to go inside folders. Defaults to the top-level folder if left blank.',
|
||||
},
|
||||
{
|
||||
key: 'file',
|
||||
type: 'text',
|
||||
label: 'File',
|
||||
required: true,
|
||||
helpText: 'Plain text content to put inside the new text file.',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
type: 'string',
|
||||
label: 'Name of New File',
|
||||
required: true,
|
||||
helpText:
|
||||
'Specify the name of this file. ".txt" will always be appended.',
|
||||
},
|
||||
],
|
||||
|
||||
perform: createTextFile,
|
||||
|
||||
// CODE TIP: If you define a resource and additional operations on that
|
||||
// resource, you can simply import the resource and re-use parts of the
|
||||
// definition in the other operations, like here. In this case, creating a
|
||||
// text file and uploading a file are the same endpoint in the OneDrive API
|
||||
// so the sample and output are the same for each.
|
||||
sample: fileResource.sample,
|
||||
|
||||
outputFields: fileResource.outputFields,
|
||||
},
|
||||
};
|
||||
50
vendor/zapier-platform/example-apps/onedrive/hydrators.js
vendored
Normal file
50
vendor/zapier-platform/example-apps/onedrive/hydrators.js
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
'use strict';
|
||||
|
||||
const utils = require('./utils');
|
||||
const handleError = utils.handleError;
|
||||
const getStringByteSize = utils.getStringByteSize;
|
||||
const {
|
||||
BASE_ITEM_URL,
|
||||
BIG_FILE_MSG,
|
||||
BINARY_CONTENT_MSG,
|
||||
} = require('./constants');
|
||||
|
||||
const getFileContents = (z, bundle) => {
|
||||
const options = {
|
||||
url: `${BASE_ITEM_URL}/me/drive/items/${bundle.inputData.id}/content`,
|
||||
};
|
||||
|
||||
return z
|
||||
.request(options)
|
||||
.then((response) => {
|
||||
const fileContents = response.content;
|
||||
const fileSize = getStringByteSize(fileContents);
|
||||
|
||||
// UX TIP: It's good to be mindful of how users will consume the outputs
|
||||
// of your app and how those outputs travel through Zapier.
|
||||
// In this scenario, users asked for the full content of a file so they
|
||||
// could map it into the body of an email vs. adding it as an attachment.
|
||||
// Cool workflow, but there are limits to passing around large payloads.
|
||||
// Here we compromise and hydrate the full content of the file for the
|
||||
// user, but only allow it through if it's under a sane size limit. That
|
||||
// way we don't send MBs of text through Zapier in every task. Note that
|
||||
// this does not prevent the user from transfering large files in Zaps, as
|
||||
// file fields are handled differently. This is specifically around our
|
||||
// app providing a field that the user wants to map into text inputs in
|
||||
// other Actions.
|
||||
if (fileSize >= 100 * 1024) {
|
||||
return BIG_FILE_MSG;
|
||||
}
|
||||
|
||||
try {
|
||||
return decodeURIComponent(fileContents);
|
||||
} catch (e) {
|
||||
return BINARY_CONTENT_MSG;
|
||||
}
|
||||
})
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getFileContents,
|
||||
};
|
||||
47
vendor/zapier-platform/example-apps/onedrive/index.js
vendored
Normal file
47
vendor/zapier-platform/example-apps/onedrive/index.js
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
const authentication = require('./authentication');
|
||||
const { includeBearerToken } = require('./before-handlers');
|
||||
const hydrators = require('./hydrators');
|
||||
|
||||
const folder = require('./resources/folder');
|
||||
const file = require('./resources/file');
|
||||
const createTextFile = require('./creates/text-file');
|
||||
|
||||
// We can roll up all our behaviors in an App.
|
||||
const App = {
|
||||
// This is just shorthand to reference the installed dependencies you have. Zapier will
|
||||
// need to know these before we can upload
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
|
||||
authentication,
|
||||
|
||||
hydrators,
|
||||
|
||||
// Ordered list of functions to pass the request object through before
|
||||
// sending it. Here we register one function that will set the auth header.
|
||||
beforeRequest: [includeBearerToken],
|
||||
|
||||
afterResponse: [],
|
||||
|
||||
/* Register the two resources for this app. Each resource defines the `list`,
|
||||
* `search`, and `create` properties, allowing Zapier to automatically generate
|
||||
* a trigger, search, and a create for each one.
|
||||
*/
|
||||
resources: {
|
||||
[folder.key]: folder,
|
||||
[file.key]: file,
|
||||
},
|
||||
|
||||
triggers: {},
|
||||
|
||||
searches: {},
|
||||
|
||||
/* In addition to the default create from the file resource, we also want to
|
||||
* allow users to create plain text files, so we register that create here.
|
||||
*/
|
||||
creates: {
|
||||
[createTextFile.key]: createTextFile,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = App;
|
||||
34
vendor/zapier-platform/example-apps/onedrive/package.json
vendored
Normal file
34
vendor/zapier-platform/example-apps/onedrive/package.json
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "zapier-platform-example-app-onedrive",
|
||||
"version": "1.0.0",
|
||||
"description": "An example app for the Zapier platform. Implements part of the OneDrive API",
|
||||
"repository": "zapier/zapier-platform-example-app-onedrive",
|
||||
"homepage": "https://zapier.com/",
|
||||
"author": "Zapier <partners@zapier.com>",
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"lint": "node_modules/.bin/eslint .",
|
||||
"posttest": "npm run lint",
|
||||
"test": "mocha --recursive --timeout 25000",
|
||||
"coverage": "istanbul cover _mocha -- --recursive --timeout 25000"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.10.2",
|
||||
"npm": ">=3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "2.4.1",
|
||||
"content-disposition": "0.5.2",
|
||||
"lodash": "4.17.23",
|
||||
"node-fetch": "1.7.1",
|
||||
"zapier-platform-core": "19.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "3.19.0",
|
||||
"istanbul": "0.4.5",
|
||||
"mocha": "3.2.0",
|
||||
"should": "11.2.1"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
154
vendor/zapier-platform/example-apps/onedrive/resources/base-item.js
vendored
Normal file
154
vendor/zapier-platform/example-apps/onedrive/resources/base-item.js
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
'use strict';
|
||||
|
||||
//
|
||||
// This is what file and folder use as a "base"
|
||||
//
|
||||
|
||||
const _ = require('lodash');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const utils = require('../utils');
|
||||
const hydrators = require('../hydrators');
|
||||
const parseResponse = utils.parseResponse;
|
||||
const handleError = utils.handleError;
|
||||
const cleanupPaths = utils.cleanupPaths;
|
||||
const { BASE_ITEM_URL } = require('../constants');
|
||||
|
||||
const getItem = (itemType, z, bundle) => {
|
||||
const options = {
|
||||
url: `${BASE_ITEM_URL}/me/drive/items/${bundle.inputData.id}`,
|
||||
};
|
||||
return z
|
||||
.request(options)
|
||||
.then(_.partial(parseResponse, z, itemType))
|
||||
.then((item) => cleanupPaths([item])[0])
|
||||
.then((item) => {
|
||||
if (item.file) {
|
||||
item.fileContents = z.dehydrate(hydrators.getFileContents, {
|
||||
id: item.id,
|
||||
});
|
||||
}
|
||||
|
||||
return item;
|
||||
})
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
const listItems = (itemType, z, bundle) => {
|
||||
let folder = bundle.inputData.folder || '';
|
||||
|
||||
if (folder) {
|
||||
folder = `:${encodeURIComponent(folder)}:`; // OneDrive's URL format
|
||||
}
|
||||
|
||||
const options = {
|
||||
url: `${BASE_ITEM_URL}/me/drive/root${folder}/children`,
|
||||
};
|
||||
|
||||
return z
|
||||
.request(options)
|
||||
.then(_.partial(parseResponse, z, itemType))
|
||||
.then(cleanupPaths)
|
||||
.then((items) => {
|
||||
items.forEach((item) => {
|
||||
if (item.file) {
|
||||
item.fileContents = z.dehydrate(hydrators.getFileContents, {
|
||||
id: item.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
})
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
const searchItem = (itemType, z, bundle) => {
|
||||
let folder = bundle.inputData.folder || '';
|
||||
|
||||
if (folder) {
|
||||
folder = `:${encodeURIComponent(folder)}:`; // OneDrive's URL format
|
||||
}
|
||||
|
||||
const options = {
|
||||
url: `${BASE_ITEM_URL}/me/drive/root${folder}/search(q='${encodeURIComponent(
|
||||
bundle.inputData.name,
|
||||
)}')`,
|
||||
};
|
||||
|
||||
return z
|
||||
.request(options)
|
||||
.then(_.partial(parseResponse, z, itemType))
|
||||
.then(cleanupPaths)
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
// Note this only works for files, but is here so creates/text-file can reuse it
|
||||
const handleCreateWithSession = (
|
||||
z,
|
||||
bundle,
|
||||
fileContents,
|
||||
fileSize,
|
||||
fileContentType,
|
||||
folder,
|
||||
name,
|
||||
) => {
|
||||
if (folder) {
|
||||
folder = encodeURIComponent(folder);
|
||||
}
|
||||
|
||||
name = encodeURIComponent(name);
|
||||
|
||||
return z
|
||||
.request({
|
||||
url: `${BASE_ITEM_URL}/me/drive/root:${folder}/${name}:/createUploadSession`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
item: {
|
||||
'@microsoft.graph.conflictBehavior': 'rename',
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
const uploadUrl = response.data.uploadUrl;
|
||||
|
||||
// This should work fine for files up to 60MB (https://dev.onedrive.com/items/upload_large_files.htm#upload-fragments)
|
||||
|
||||
if (!fileContentType.includes('charset')) {
|
||||
fileContentType += '; charset=UTF-8';
|
||||
}
|
||||
|
||||
// CODE TIP: If you define beforeRequest handlers, then end up in a
|
||||
// situation where you don't want that pre-processing, you can drop to a
|
||||
// raw `fetch` call instead of the z.request() to avoid those handlers
|
||||
return fetch(uploadUrl, {
|
||||
method: 'PUT',
|
||||
body: fileContents,
|
||||
headers: {
|
||||
'content-type': fileContentType,
|
||||
'content-length': fileSize,
|
||||
'content-range': `bytes 0-${fileSize - 1}/${fileSize}`,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((response) => response.text())
|
||||
.then((content) => {
|
||||
const resourceId = z.JSON.parse(content).id;
|
||||
bundle.inputData = {
|
||||
id: resourceId,
|
||||
};
|
||||
|
||||
return _.partial(getItem, 'file')(z, bundle);
|
||||
})
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getItem,
|
||||
listItems,
|
||||
searchItem,
|
||||
handleCreateWithSession,
|
||||
};
|
||||
165
vendor/zapier-platform/example-apps/onedrive/resources/file.js
vendored
Normal file
165
vendor/zapier-platform/example-apps/onedrive/resources/file.js
vendored
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const utils = require('../utils');
|
||||
const handleError = utils.handleError;
|
||||
const getFileDetailsFromRequest = utils.getFileDetailsFromRequest;
|
||||
const baseItem = require('./base-item');
|
||||
|
||||
const getFile = _.partial(baseItem.getItem, 'file');
|
||||
|
||||
const listFiles = _.partial(baseItem.listItems, 'file');
|
||||
|
||||
const createFile = (z, bundle) => {
|
||||
const folder = bundle.inputData.folder || '';
|
||||
// NOTE: `file` is really a URL like "https://zapier.com/engine/hydrate/<unique_id>/.blahblahblah:blah:blah/"
|
||||
const file = bundle.inputData.file;
|
||||
|
||||
return getFileDetailsFromRequest(file)
|
||||
.then((fileDetails) => {
|
||||
const name =
|
||||
bundle.inputData.name || fileDetails.filename || 'unnamedfile.unknown';
|
||||
|
||||
return baseItem.handleCreateWithSession(
|
||||
z,
|
||||
bundle,
|
||||
fileDetails.content,
|
||||
fileDetails.size,
|
||||
fileDetails.contentType,
|
||||
folder,
|
||||
name,
|
||||
);
|
||||
})
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
const searchFile = _.partial(baseItem.searchItem, 'file');
|
||||
|
||||
module.exports = {
|
||||
key: 'file',
|
||||
noun: 'File',
|
||||
|
||||
get: {
|
||||
display: {
|
||||
label: 'Get File',
|
||||
description: 'Gets a file.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'id',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
perform: getFile,
|
||||
},
|
||||
},
|
||||
|
||||
// Will become a trigger on the app. Key will be `fileList`
|
||||
list: {
|
||||
display: {
|
||||
label: 'New File',
|
||||
description: 'Triggers when a new file is added in a folder.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'folder',
|
||||
type: 'string',
|
||||
label: 'Folder',
|
||||
required: false,
|
||||
dynamic: 'folderList._path.name',
|
||||
helpText:
|
||||
'Folder where to look for the file. Keep clicking the dropdown to go inside folders. Defaults to the top-level folder if left blank.',
|
||||
},
|
||||
],
|
||||
perform: listFiles,
|
||||
},
|
||||
},
|
||||
|
||||
// Will become a create on the app. Key will be `fileCreate`
|
||||
create: {
|
||||
display: {
|
||||
label: 'Upload File',
|
||||
description: 'Upload an existing file or attachment.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'folder',
|
||||
type: 'string',
|
||||
label: 'Folder',
|
||||
required: false,
|
||||
dynamic: 'folderList._path.name',
|
||||
helpText:
|
||||
'Folder where to place the file. Keep clicking the dropdown to go inside folders. Defaults to the top-level folder if left blank.',
|
||||
},
|
||||
{
|
||||
key: 'file',
|
||||
type: 'file',
|
||||
label: 'File',
|
||||
required: true,
|
||||
helpText:
|
||||
'Must be a file object from another service (or some text or URL).',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
type: 'string',
|
||||
label: 'File Name',
|
||||
required: false,
|
||||
helpText:
|
||||
'By default, we use the same name and extension as the original file.',
|
||||
},
|
||||
],
|
||||
perform: createFile,
|
||||
},
|
||||
},
|
||||
|
||||
// Will become a search on the app. Key will be `fileSearch`
|
||||
search: {
|
||||
display: {
|
||||
label: 'Find File',
|
||||
description: 'Finds a file by name.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'folder',
|
||||
type: 'string',
|
||||
label: 'Folder',
|
||||
required: false,
|
||||
dynamic: 'folderList._path.name',
|
||||
helpText:
|
||||
'Folder where to look for the file. Keep clicking the dropdown to go inside folders. Defaults to the top-level folder if left blank.',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
required: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
perform: searchFile,
|
||||
},
|
||||
},
|
||||
|
||||
sample: {
|
||||
id: '1',
|
||||
name: 'Example.jpg',
|
||||
_path: '/Something/Example.jpg',
|
||||
_parent: '/Something',
|
||||
webUrl: 'https://example.com',
|
||||
'@microsoft.graph.downloadUrl': 'https://example.com',
|
||||
createdDateTime: '2016-09-16T03:37:04.72Z',
|
||||
lastModifiedDateTime: '2016-09-16T03:37:04.72Z',
|
||||
},
|
||||
|
||||
outputFields: [
|
||||
{ key: 'id', label: 'ID' },
|
||||
{ key: 'name', label: 'File Name' },
|
||||
{ key: '_path', label: 'File Path' },
|
||||
{ key: '_parent', label: 'Folder' },
|
||||
{ key: 'webUrl', label: 'URL' },
|
||||
{ key: '@microsoft.graph.downloadUrl', label: 'Download URL' },
|
||||
],
|
||||
};
|
||||
170
vendor/zapier-platform/example-apps/onedrive/resources/folder.js
vendored
Normal file
170
vendor/zapier-platform/example-apps/onedrive/resources/folder.js
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const utils = require('../utils');
|
||||
const parseResponse = utils.parseResponse;
|
||||
const handleError = utils.handleError;
|
||||
const cleanupPaths = utils.cleanupPaths;
|
||||
const extractParentsFromPath = utils.extractParentsFromPath;
|
||||
const { BASE_ITEM_URL } = require('../constants');
|
||||
const baseItem = require('./base-item');
|
||||
|
||||
const getFolder = _.partial(baseItem.getItem, 'folder');
|
||||
|
||||
const listFolders = (z, bundle) => {
|
||||
return baseItem.listItems('folder', z, bundle).then((results) => {
|
||||
// Add parents when being called in the context of populating a dynamic dropdown (prefill).
|
||||
// This allows users to "navigate back" to previous dirs in the Zap Editor
|
||||
if (bundle.meta.prefill && bundle.inputData.folder) {
|
||||
const parents = extractParentsFromPath(bundle.inputData.folder);
|
||||
parents.forEach((result) => results.unshift(result));
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
};
|
||||
|
||||
const createFolder = (z, bundle) => {
|
||||
let folder = bundle.inputData.folder || '';
|
||||
|
||||
if (folder) {
|
||||
folder = `:${encodeURIComponent(folder)}:`; // OneDrive URI format
|
||||
}
|
||||
|
||||
return z
|
||||
.request({
|
||||
url: `${BASE_ITEM_URL}/me/drive/root${folder}/children`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
name: bundle.inputData.name,
|
||||
folder: {}, // This tells OneDrive it's a folder (https://dev.onedrive.com/items/create.htm#example)
|
||||
'@microsoft.graph.conflictBehavior': 'rename',
|
||||
},
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(_.partial(parseResponse, z, 'folder'))
|
||||
.then(cleanupPaths)
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
const searchFolder = _.partial(baseItem.searchItem, 'folder');
|
||||
|
||||
module.exports = {
|
||||
key: 'folder',
|
||||
noun: 'Folder',
|
||||
|
||||
get: {
|
||||
display: {
|
||||
label: 'Get Folder',
|
||||
description: 'Gets a folder.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'id',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
perform: getFolder,
|
||||
},
|
||||
},
|
||||
|
||||
// Will become a trigger on the app. Key will be `folderList`
|
||||
list: {
|
||||
display: {
|
||||
label: 'New Folder',
|
||||
description: 'Triggers when a new folder is added.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'folder',
|
||||
type: 'string',
|
||||
label: 'Folder',
|
||||
required: false,
|
||||
// As mentioned above, triggers genereated from resources follow a
|
||||
// format of `<resource.key>List`, so that is what we use in all the dynamic dropdowns
|
||||
dynamic: 'folderList._path.name',
|
||||
helpText:
|
||||
'Folder where to look for the folder. Keep clicking the dropdown to go inside folders. Defaults to the top-level folder if left blank.',
|
||||
},
|
||||
],
|
||||
perform: listFolders,
|
||||
},
|
||||
},
|
||||
|
||||
// Will become a create on the app. Key will be `folderCreate`
|
||||
create: {
|
||||
display: {
|
||||
label: 'Create Folder',
|
||||
description: 'Creates a new folder.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'folder',
|
||||
type: 'string',
|
||||
label: 'Folder',
|
||||
required: false,
|
||||
dynamic: 'folderList._path.name',
|
||||
helpText:
|
||||
'Folder where to create the folder. Keep clicking the dropdown to go inside folders. Defaults to the top-level folder if left blank.',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
required: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
perform: createFolder,
|
||||
},
|
||||
},
|
||||
|
||||
// Will become a search on the app. Key will be `folderSearch`
|
||||
search: {
|
||||
display: {
|
||||
label: 'Find Folder',
|
||||
description: 'Finds a folder by name.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'folder',
|
||||
type: 'string',
|
||||
label: 'Folder',
|
||||
required: false,
|
||||
dynamic: 'folderList._path.name',
|
||||
helpText:
|
||||
'Folder where to look for the folder. Keep clicking the dropdown to go inside folders. Defaults to the top-level folder if left blank.',
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
required: true,
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
perform: searchFolder,
|
||||
},
|
||||
},
|
||||
|
||||
sample: {
|
||||
id: '1',
|
||||
name: 'Example',
|
||||
_path: '/Something/Example',
|
||||
_parent: '/Something',
|
||||
webUrl: 'https://example.com',
|
||||
createdDateTime: '2016-09-16T03:37:04.72Z',
|
||||
lastModifiedDateTime: '2016-09-16T03:37:04.72Z',
|
||||
},
|
||||
|
||||
outputFields: [
|
||||
{ key: 'id', label: 'ID' },
|
||||
{ key: 'name', label: 'Folder Name' },
|
||||
{ key: '_path', label: 'Folder Path' },
|
||||
{ key: '_parent', label: 'Parent Folder' },
|
||||
{ key: 'webUrl', label: 'URL' },
|
||||
],
|
||||
};
|
||||
75
vendor/zapier-platform/example-apps/onedrive/test/authentication.js
vendored
Normal file
75
vendor/zapier-platform/example-apps/onedrive/test/authentication.js
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
require('should');
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const testUtils = require('./test-utils');
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
describe('Authentication', () => {
|
||||
before(testUtils.globalBeforeSetup);
|
||||
|
||||
it('should refresh auth', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.authentication.oauth2Config.refreshAccessToken, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('access_token');
|
||||
result.should.have.property('refresh_token');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should test auth', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
accountType: 'personal',
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.authentication.test, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('userPrincipalName');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should generate an authorize URL', (done) => {
|
||||
const bundle = {
|
||||
// When an app runs on zapier.com, these will be generated by Zapier and
|
||||
// passed to your app in the bundle. For the purpose of this test, we
|
||||
// create a bundle with dummy values to ensure our function works.
|
||||
inputData: {
|
||||
state: '4444',
|
||||
redirect_uri: 'https://zapier.com/',
|
||||
accountType: 'personal',
|
||||
},
|
||||
environment: {
|
||||
CLIENT_ID: '1234',
|
||||
CLIENT_SECRET: 'asdf',
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.authentication.oauth2Config.authorizeUrl, bundle)
|
||||
.then((authorizeUrl) => {
|
||||
authorizeUrl.should.eql(
|
||||
'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=1234&redirect_uri=http%3A%2F%2Fzapier.com%2F&response_type=code&state=4444',
|
||||
);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
112
vendor/zapier-platform/example-apps/onedrive/test/creates/text-file.js
vendored
Normal file
112
vendor/zapier-platform/example-apps/onedrive/test/creates/text-file.js
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const testUtils = require('../test-utils');
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
const TEST_RESOURCES = testUtils.TEST_RESOURCES;
|
||||
|
||||
describe('Create Text File', () => {
|
||||
before(testUtils.globalBeforeSetup);
|
||||
|
||||
it('should create a new text file in the root dir', (done) => {
|
||||
const file = 'Sample content';
|
||||
const name = `Test-${new Date().getTime()}`;
|
||||
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
file,
|
||||
name,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.creates.textFile.operation.perform, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('file');
|
||||
result.should.not.have.property('folder');
|
||||
result.name.should.containEql(name);
|
||||
result.name.should.containEql('.txt');
|
||||
result.name.should.not.containEql('.txt.txt');
|
||||
result._parent.should.eql('');
|
||||
result._path.should.containEql(`/${name}`);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should create a text file with a unicode name and contents', (done) => {
|
||||
const file = 'Ohweeeeee®™¥¶‰÷!!!!!!!!!!!';
|
||||
const name = `Test-ç€is-æ-${new Date().getTime()}.txt`;
|
||||
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: TEST_RESOURCES.folder.path,
|
||||
name,
|
||||
file,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.creates.textFile.operation.perform, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('file');
|
||||
result.should.not.have.property('folder');
|
||||
result.name.should.containEql(name);
|
||||
result.name.should.containEql('.txt');
|
||||
result.name.should.not.containEql('.txt.txt');
|
||||
result._parent.should.eql(TEST_RESOURCES.folder.parent);
|
||||
result._path.should.containEql(`${TEST_RESOURCES.folder.path}/${name}`);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should create a new text file in the root dir, renaming it, because another one exists', (done) => {
|
||||
const file = 'Sample content';
|
||||
const name = 'boom';
|
||||
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
file,
|
||||
name,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.creates.textFile.operation.perform, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('file');
|
||||
result.should.not.have.property('folder');
|
||||
result.name.should.containEql(`${name} `);
|
||||
result.name.should.containEql('.txt');
|
||||
result.name.should.not.containEql('.txt.txt');
|
||||
result.name.should.not.containEql(`${name}.txt`);
|
||||
result._parent.should.eql('');
|
||||
result._path.should.containEql(`/${name} `);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
47
vendor/zapier-platform/example-apps/onedrive/test/hydrators.js
vendored
Normal file
47
vendor/zapier-platform/example-apps/onedrive/test/hydrators.js
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
require('should');
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const testUtils = require('./test-utils');
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
const TEST_RESOURCES = testUtils.TEST_RESOURCES;
|
||||
|
||||
describe('Hydrators', () => {
|
||||
before(testUtils.globalBeforeSetup);
|
||||
|
||||
it('should get file contents', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
},
|
||||
inputData: {
|
||||
id: TEST_RESOURCES.root.id,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.hydrators.getFileContents, bundle)
|
||||
.then((fileContents) => {
|
||||
fileContents.length.should.above(0);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should get unicode file contents', (done) => {
|
||||
// CODE TIP: Hydrating files often involves touching the raw bytes (and
|
||||
// handling the file names), so it's a good idea to test different edge cases
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not get file contents for big files', (done) => {
|
||||
// An example edge case that would be worth testing
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not get file contents for binary files', (done) => {
|
||||
// An example edge case that would be worth testing
|
||||
done();
|
||||
});
|
||||
});
|
||||
186
vendor/zapier-platform/example-apps/onedrive/test/resources/file.js
vendored
Normal file
186
vendor/zapier-platform/example-apps/onedrive/test/resources/file.js
vendored
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const testUtils = require('../test-utils');
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
const TEST_RESOURCES = testUtils.TEST_RESOURCES;
|
||||
|
||||
describe('File Resource', () => {
|
||||
before(testUtils.globalBeforeSetup);
|
||||
|
||||
it('should get an existing file in the root folder', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
id: TEST_RESOURCES.root.id,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.file.get.operation.perform, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('file');
|
||||
result.should.not.have.property('folder');
|
||||
result.name.should.eql(TEST_RESOURCES.root.name);
|
||||
result._path.should.eql(TEST_RESOURCES.root.path);
|
||||
result._parent.should.eql(TEST_RESOURCES.root.parent);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should get an existing file in a child folder', (done) => {
|
||||
// An example scenario that could be worth testing if the HTTP request for
|
||||
// a child folder is sufficiently different from a root folder
|
||||
done();
|
||||
});
|
||||
|
||||
it('should list files in root dir', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.file.list.operation.perform, bundle)
|
||||
.then((results) => {
|
||||
results.length.should.above(0);
|
||||
results.forEach((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('file');
|
||||
result.should.not.have.property('folder');
|
||||
});
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should not miss any fields from the sample', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.file.list.operation.perform, bundle)
|
||||
.then((results) => {
|
||||
results.length.should.above(0);
|
||||
results.forEach((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('file');
|
||||
result.should.not.have.property('folder');
|
||||
});
|
||||
const file = results[0];
|
||||
const sampleKeys = Object.keys(App.resources.file.sample);
|
||||
sampleKeys.forEach((sampleKey) => file[sampleKey].should.eql(true));
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should upload a new file in the root dir', (done) => {
|
||||
const file =
|
||||
'https://cdn.zapier.com/storage/files/f6679cf77afeaf6b8426de8d7b9642fc.pdf';
|
||||
const name = `Test-${new Date().getTime()}-ç€is-æ.pdf`;
|
||||
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
file,
|
||||
name,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.file.create.operation.perform, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('file');
|
||||
result.should.not.have.property('folder');
|
||||
result.name.should.containEql(name);
|
||||
result._parent.should.eql('');
|
||||
result._path.should.containEql(`/${name}`);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should upload an utf8-named file', (done) => {
|
||||
// CODE TIP: It's a good idea to write multiple tests cases around creates
|
||||
// to verify some of the common edges:
|
||||
// * Non-ascii data
|
||||
// * Records associated with the right relationships (i.e. a file in a folder, an owner of a lead)
|
||||
// * Critical defaults work as expected
|
||||
// * Dates & times are set correctly
|
||||
done();
|
||||
});
|
||||
|
||||
it('should upload a new file in the root dir, renaming it, because another one exists', (done) => {
|
||||
// An example of an edge case where a naming collision in OneDrive could
|
||||
// cause an overwrite or an auto-renaming of the file, so we want to test
|
||||
// and make sure we get the behavior we want (auto-renaming)
|
||||
done();
|
||||
});
|
||||
|
||||
it('should find a file in the root dir', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
name: 'sample',
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.file.search.operation.perform, bundle)
|
||||
.then((results) => {
|
||||
results.length.should.above(0);
|
||||
results[0].should.have.property('id');
|
||||
results[0].should.have.property('file');
|
||||
results[0].should.not.have.property('folder');
|
||||
results[0].name.should.containEql('sample');
|
||||
results[0]._parent.should.eql('');
|
||||
results[0]._path.should.containEql('/sample');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should find a file in a child dir', (done) => {
|
||||
// An example scenario that could be worth testing if the HTTP request for
|
||||
// a child folder is sufficiently different from a root folder
|
||||
done();
|
||||
});
|
||||
|
||||
it('should find a unicode file in a child dir', (done) => {
|
||||
// CODE TIP: Like with creates, adding multiple tests around the common
|
||||
// edge cases of searching is a good idea
|
||||
done();
|
||||
});
|
||||
});
|
||||
247
vendor/zapier-platform/example-apps/onedrive/test/resources/folder.js
vendored
Normal file
247
vendor/zapier-platform/example-apps/onedrive/test/resources/folder.js
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const testUtils = require('../test-utils');
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
const TEST_RESOURCES = testUtils.TEST_RESOURCES;
|
||||
|
||||
describe('Folder Resource', () => {
|
||||
before(testUtils.globalBeforeSetup);
|
||||
|
||||
it('should get an existing root folder', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
id: TEST_RESOURCES.folder.id,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.folder.get.operation.perform, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('folder');
|
||||
result.should.not.have.property('file');
|
||||
result.name.should.eql(TEST_RESOURCES.root.name);
|
||||
result._path.should.eql(TEST_RESOURCES.root.path);
|
||||
result._parent.should.eql(TEST_RESOURCES.root.parent);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should get an existing child folder', (done) => {
|
||||
// An example scenario that could be worth testing if the HTTP request for
|
||||
// a child folder is sufficiently different from a root folder
|
||||
done();
|
||||
});
|
||||
|
||||
it('should list folders in root dir', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
},
|
||||
meta: {
|
||||
prefill: false,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.folder.list.operation.perform, bundle)
|
||||
.then((results) => {
|
||||
results.length.should.above(0);
|
||||
results.forEach((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('folder');
|
||||
result.should.not.have.property('file');
|
||||
});
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should list folders without parents on no prefill', (done) => {
|
||||
// CODE TIP: When reusing a list operation to power a trigger and a
|
||||
// dynmaic dropdown (prefill), it's a good idea to test both scenarios if the
|
||||
// output from the operation varies between the two contexts
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: TEST_RESOURCES.childFolder.path,
|
||||
},
|
||||
meta: {
|
||||
prefill: false,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.folder.list.operation.perform, bundle)
|
||||
.then((results) => {
|
||||
results.length.should.above(0);
|
||||
|
||||
let foundParents = false;
|
||||
|
||||
results.forEach((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('folder');
|
||||
result.should.not.have.property('file');
|
||||
if (result.id < 0) {
|
||||
foundParents = true;
|
||||
}
|
||||
});
|
||||
|
||||
foundParents.should.eql(false);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should list folders with parents on prefill', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: TEST_RESOURCES.childFolder.path,
|
||||
},
|
||||
meta: {
|
||||
prefill: true,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.folder.list.operation.perform, bundle)
|
||||
.then((results) => {
|
||||
results.length.should.above(0);
|
||||
|
||||
let foundParents = false;
|
||||
|
||||
results.forEach((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('folder');
|
||||
result.should.not.have.property('file');
|
||||
if (result.id < 0) {
|
||||
foundParents = true;
|
||||
}
|
||||
});
|
||||
|
||||
foundParents.should.eql(true);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should not miss any fields from the sample', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
},
|
||||
meta: {
|
||||
prefill: false,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.folder.list.operation.perform, bundle)
|
||||
.then((results) => {
|
||||
results.length.should.above(0);
|
||||
results.forEach((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('folder');
|
||||
result.should.not.have.property('file');
|
||||
});
|
||||
const folder = results[0];
|
||||
const sampleKeys = Object.keys(App.resources.folder.sample);
|
||||
sampleKeys.forEach((sampleKey) => folder[sampleKey].should.eql(true));
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should create a new folder in the root dir', (done) => {
|
||||
const name = `Test-${new Date().getTime()}`;
|
||||
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
name,
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.folder.create.operation.perform, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('id');
|
||||
result.should.have.property('folder');
|
||||
result.should.not.have.property('file');
|
||||
result.name.should.containEql(name);
|
||||
result._parent.should.eql('');
|
||||
result._path.should.containEql(`/${name}`);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should create a new folder in a child dir', (done) => {
|
||||
// An example scenario that could be worth testing if the HTTP request for
|
||||
// a child folder is sufficiently different from a root folder
|
||||
done();
|
||||
});
|
||||
|
||||
it('should find a folder in the root dir', (done) => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: process.env.ACCESS_TOKEN,
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
inputData: {
|
||||
folder: '',
|
||||
name: 'Documents',
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.resources.folder.search.operation.perform, bundle)
|
||||
.then((results) => {
|
||||
results.length.should.above(0);
|
||||
results[0].should.have.property('id');
|
||||
results[0].should.have.property('folder');
|
||||
results[0].should.not.have.property('file');
|
||||
results[0].name.should.containEql('Documents');
|
||||
results[0]._parent.should.eql('');
|
||||
results[0]._path.should.containEql('/Documents');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('should find a folder in a child dir', (done) => {
|
||||
// An example scenario that could be worth testing if the HTTP request for
|
||||
// a child folder is sufficiently different from a root folder
|
||||
done();
|
||||
});
|
||||
});
|
||||
83
vendor/zapier-platform/example-apps/onedrive/test/test-utils.js
vendored
Normal file
83
vendor/zapier-platform/example-apps/onedrive/test/test-utils.js
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
const globalBeforeSetup = (done) => {
|
||||
zapier.tools.env.inject();
|
||||
|
||||
if (
|
||||
!process.env.CLIENT_ID ||
|
||||
!process.env.CLIENT_SECRET ||
|
||||
!process.env.REFRESH_TOKEN
|
||||
) {
|
||||
throw new Error(
|
||||
'Setup your .environment file (or use `export`) according to the README.',
|
||||
);
|
||||
}
|
||||
|
||||
// ACCESS_TOKEN expires very quickly, so we get a new one per test run from REFRESH_TOKEN
|
||||
if (!process.env.ACCESS_TOKEN) {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: '',
|
||||
refresh_token: process.env.REFRESH_TOKEN,
|
||||
accountType: 'personal',
|
||||
},
|
||||
};
|
||||
|
||||
appTester(App.authentication.oauth2Config.refreshAccessToken, bundle)
|
||||
.then((result) => {
|
||||
result.should.have.property('access_token');
|
||||
result.should.have.property('refresh_token');
|
||||
process.env.ACCESS_TOKEN = result.access_token;
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
// Below is a set of files the app can use to test different scenarios.
|
||||
// To make the test suite run, add files to your account in the given folders,
|
||||
// then fill in the IDs of files from your account. The `id` is visible in the
|
||||
// URL when accessing the file in the browser, for example:
|
||||
// URL: https://onedrive.live.com/?cid=697LD7B578EB1372&id=697LD7B578EB1372%21716
|
||||
// ID: 697LD7B578EB1372!716
|
||||
const TEST_RESOURCES = {
|
||||
root: {
|
||||
id: '',
|
||||
name: 'sample.pdf',
|
||||
path: '/sample.pdf',
|
||||
parent: '',
|
||||
},
|
||||
nested: {
|
||||
id: '',
|
||||
name: 'test.txt',
|
||||
path: '/Documents/Testing/test.txt',
|
||||
parent: '/Documents/Testing',
|
||||
},
|
||||
|
||||
folder: {
|
||||
id: '',
|
||||
name: 'Documents',
|
||||
path: '/Documents',
|
||||
parent: '',
|
||||
},
|
||||
childFolder: {
|
||||
id: '',
|
||||
name: 'Testing',
|
||||
path: '/Documents/Testing',
|
||||
parent: '/Documents',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
globalBeforeSetup,
|
||||
TEST_RESOURCES,
|
||||
};
|
||||
68
vendor/zapier-platform/example-apps/onedrive/test/utils.js
vendored
Normal file
68
vendor/zapier-platform/example-apps/onedrive/test/utils.js
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
|
||||
require('should');
|
||||
|
||||
const utils = require('../utils');
|
||||
const extractParentsFromPath = utils.extractParentsFromPath;
|
||||
const getStringByteSize = utils.getStringByteSize;
|
||||
|
||||
describe('Utils', () => {
|
||||
it('should extract parents from a folder path', () => {
|
||||
let parents = extractParentsFromPath('/Some/Kind of /Test');
|
||||
|
||||
parents.length.should.eql(3);
|
||||
|
||||
parents[0].id.should.eql(-1);
|
||||
parents[0].name.should.eql('/');
|
||||
parents[0]._path.should.eql('');
|
||||
parents[0].should.have.property('folder');
|
||||
parents[0].should.not.have.property('file');
|
||||
|
||||
parents[1].id.should.eql(-2);
|
||||
parents[1].name.should.eql('Some');
|
||||
parents[1]._path.should.eql('/Some');
|
||||
parents[1].should.have.property('folder');
|
||||
parents[1].should.not.have.property('file');
|
||||
|
||||
parents[2].id.should.eql(-3);
|
||||
parents[2].name.should.eql('Kind of ');
|
||||
parents[2]._path.should.eql('/Some/Kind of ');
|
||||
parents[2].should.have.property('folder');
|
||||
parents[2].should.not.have.property('file');
|
||||
|
||||
parents = extractParentsFromPath('/Zapier');
|
||||
|
||||
parents.length.should.eql(1);
|
||||
|
||||
parents[0].id.should.eql(-1);
|
||||
parents[0].name.should.eql('/');
|
||||
parents[0]._path.should.eql('');
|
||||
parents[0].should.have.property('folder');
|
||||
parents[0].should.not.have.property('file');
|
||||
});
|
||||
|
||||
it('should correctly compute the byte size of strings', () => {
|
||||
const tests = [
|
||||
{
|
||||
string: 'asd',
|
||||
expected: 3,
|
||||
},
|
||||
{
|
||||
string: '@€£¶‰dfg',
|
||||
expected: 14,
|
||||
},
|
||||
{
|
||||
string: '÷¶[]÷d()ß-°ú',
|
||||
expected: 18,
|
||||
},
|
||||
{
|
||||
string: 'ç€is-æ',
|
||||
expected: 10,
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach((test) =>
|
||||
getStringByteSize(test.string).should.eql(test.expected),
|
||||
);
|
||||
});
|
||||
});
|
||||
138
vendor/zapier-platform/example-apps/onedrive/utils.js
vendored
Normal file
138
vendor/zapier-platform/example-apps/onedrive/utils.js
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const fetch = require('node-fetch');
|
||||
const contentDisposition = require('content-disposition');
|
||||
|
||||
// CODE TIP: This function is used to process the response of several endpoints
|
||||
// on the OneDrive API. We intentionally don't set it up as an `afterResposne`
|
||||
// handler because not *all* calls need it (i.e. the auth test and file create),
|
||||
// so we break it out and share the code this way instead.
|
||||
const parseResponse = (z, type, response) => {
|
||||
let results = [];
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
results = response.data;
|
||||
|
||||
// OneDrive puts the contents of lists inside .value property
|
||||
if (!_.isArray(results) && _.isArray(results.value)) {
|
||||
results = results.value;
|
||||
}
|
||||
} else {
|
||||
throw new z.errors.Error(response.content, null, response.status);
|
||||
}
|
||||
|
||||
// Only return files or folders, according to type
|
||||
if (_.isArray(results)) {
|
||||
results = results.filter((result) => result[type]);
|
||||
} else {
|
||||
if (!results[type]) {
|
||||
results = {};
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
const handleError = (error) => {
|
||||
if (typeof error === 'string') {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
|
||||
const extractParentsFromPath = (path) => {
|
||||
const parts = path.split('/');
|
||||
const results = [];
|
||||
|
||||
parts.splice(parts.length - 1); // Last is the current directory, so we remove it
|
||||
|
||||
let i = parts.length - 1;
|
||||
|
||||
while (parts.length > 0) {
|
||||
const name = parts.splice(i)[0];
|
||||
|
||||
const result = {
|
||||
id: (i + 1) * -1,
|
||||
name: name === '' ? '/' : name,
|
||||
_path: parts.join('/') + (name === '' ? name : `/${name}`),
|
||||
folder: {},
|
||||
};
|
||||
|
||||
results.push(result);
|
||||
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
results.reverse();
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
// UX TIP: Sometimes it can be helpful to translate raw values from an API into
|
||||
// terms that end users are familiar with. In this example, OneDrive returns
|
||||
// parent and the path of a file with a '/drive/root:' that end users of OneDrive
|
||||
// never see. To help it read better in a dynamic dropdown, we clean that off
|
||||
const cleanupPaths = (results) => {
|
||||
// We can get a single object here as well
|
||||
if (!_.isArray(results)) {
|
||||
results._parent = _.get(results, 'parentReference.path', '').replace(
|
||||
'/drive/root:',
|
||||
'',
|
||||
);
|
||||
results._path = `${results._parent}/${results.name}`;
|
||||
return results;
|
||||
}
|
||||
|
||||
// Adds easier to reference paths, cleaning up the "/drive/root:" clutter
|
||||
return results.map((result) => {
|
||||
result._parent = _.get(result, 'parentReference.path', '').replace(
|
||||
'/drive/root:',
|
||||
'',
|
||||
);
|
||||
result._path = `${result._parent}/${result.name}`;
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
const getFileDetailsFromRequest = (url) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const fileDetails = {
|
||||
filename: '',
|
||||
size: 0,
|
||||
content: '',
|
||||
contentType: '',
|
||||
};
|
||||
|
||||
fetch(url)
|
||||
.then((response) => {
|
||||
fileDetails.size = response.headers.get('content-length');
|
||||
fileDetails.contentType = response.headers.get('content-type');
|
||||
const disposition = response.headers.get('content-disposition');
|
||||
|
||||
if (disposition) {
|
||||
fileDetails.filename =
|
||||
contentDisposition.parse(disposition).parameters.filename;
|
||||
}
|
||||
|
||||
return response.buffer();
|
||||
})
|
||||
.then((content) => {
|
||||
fileDetails.content = content;
|
||||
|
||||
return resolve(fileDetails);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
|
||||
const getStringByteSize = (string) => Buffer.byteLength(string, 'utf8');
|
||||
|
||||
module.exports = {
|
||||
parseResponse,
|
||||
handleError,
|
||||
extractParentsFromPath,
|
||||
cleanupPaths,
|
||||
getFileDetailsFromRequest,
|
||||
getStringByteSize,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue