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
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' },
|
||||
],
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue