first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const fileOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
description: 'Copy a file',
|
||||
action: 'Copy a file',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a file',
|
||||
action: 'Delete a file',
|
||||
},
|
||||
{
|
||||
name: 'Download',
|
||||
value: 'download',
|
||||
description: 'Download a file',
|
||||
action: 'Download a file',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a file',
|
||||
action: 'Get a file',
|
||||
},
|
||||
{
|
||||
name: 'Rename',
|
||||
value: 'rename',
|
||||
description: 'Rename a file',
|
||||
action: 'Rename a file',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: 'Search a file',
|
||||
action: 'Search a file',
|
||||
},
|
||||
{
|
||||
name: 'Share',
|
||||
value: 'share',
|
||||
description: 'Share a file',
|
||||
action: 'Share a file',
|
||||
},
|
||||
{
|
||||
name: 'Upload',
|
||||
value: 'upload',
|
||||
description: 'Upload a file up to 4MB in size',
|
||||
action: 'Upload a file',
|
||||
},
|
||||
],
|
||||
default: 'upload',
|
||||
},
|
||||
];
|
||||
|
||||
export const fileFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:copy */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['copy'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['copy'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
"The new name for the copy. If this isn't provided, the same name will be used as the original.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Parent Reference',
|
||||
name: 'parentReference',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Parent Reference',
|
||||
description:
|
||||
'Reference to the parent item the copy will be created in <a href="https://docs.microsoft.com/en-us/onedrive/developer/rest-api/resources/itemreference?view=odsp-graph-online"> Details </a>',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['copy'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Drive ID',
|
||||
name: 'driveId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Identifier of the drive instance that contains the item',
|
||||
},
|
||||
{
|
||||
displayName: 'Drive Type',
|
||||
name: 'driveType',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Identifies the type of drive',
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Identifier of the item in the drive',
|
||||
},
|
||||
{
|
||||
displayName: 'List ID',
|
||||
name: 'listId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Identifier of the list',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The name of the item being referenced',
|
||||
},
|
||||
{
|
||||
displayName: 'Path',
|
||||
name: 'path',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Path that can be used to navigate to the item',
|
||||
},
|
||||
{
|
||||
displayName: 'Share ID',
|
||||
name: 'shareId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Identifier for a shared resource that can be accessed via the Shares API',
|
||||
},
|
||||
{
|
||||
displayName: 'Site ID',
|
||||
name: 'siteId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Identifier of the site',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['delete'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Field ID',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:download */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['download'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['download'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Field ID',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:rename */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Item ID',
|
||||
name: 'itemId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['rename'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the file',
|
||||
},
|
||||
{
|
||||
displayName: 'New Name',
|
||||
name: 'newName',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['rename'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'New name for file',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:search */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['search'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'The query text used to search for items. Values may be matched across several fields including filename, metadata, and file content.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:share */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'File ID',
|
||||
name: 'fileId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['share'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'View',
|
||||
value: 'view',
|
||||
},
|
||||
{
|
||||
name: 'Edit',
|
||||
value: 'edit',
|
||||
},
|
||||
{
|
||||
name: 'Embed',
|
||||
value: 'embed',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['share'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The type of sharing link to create',
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Anonymous',
|
||||
value: 'anonymous',
|
||||
},
|
||||
{
|
||||
name: 'Organization',
|
||||
value: 'organization',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['share'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The type of sharing link to create',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:upload */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The name the file should be saved as',
|
||||
},
|
||||
{
|
||||
displayName: 'Parent ID',
|
||||
name: 'parentId',
|
||||
required: true,
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the parent folder that will contain the file',
|
||||
},
|
||||
{
|
||||
displayName: 'Binary File',
|
||||
name: 'binaryData',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
description: 'Whether the data to upload should be taken from binary field',
|
||||
},
|
||||
{
|
||||
displayName: 'File Content',
|
||||
name: 'fileContent',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
binaryData: [false],
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
placeholder: '',
|
||||
description: 'The text content of the file',
|
||||
},
|
||||
{
|
||||
displayName: 'Input Binary Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
binaryData: [true],
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
placeholder: '',
|
||||
hint: 'The name of the input binary field containing the file to be written',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,224 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const folderOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a folder',
|
||||
action: 'Create a folder',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a folder',
|
||||
action: 'Delete a folder',
|
||||
},
|
||||
{
|
||||
name: 'Get Children',
|
||||
value: 'getChildren',
|
||||
description: 'Get items inside a folder',
|
||||
action: 'Get items in a folder',
|
||||
},
|
||||
{
|
||||
name: 'Rename',
|
||||
value: 'rename',
|
||||
description: 'Rename a folder',
|
||||
action: 'Rename a folder',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: 'Search a folder',
|
||||
action: 'Search a folder',
|
||||
},
|
||||
{
|
||||
name: 'Share',
|
||||
value: 'share',
|
||||
description: 'Share a folder',
|
||||
action: 'Share a folder',
|
||||
},
|
||||
],
|
||||
default: 'getChildren',
|
||||
},
|
||||
];
|
||||
|
||||
export const folderFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
required: true,
|
||||
type: 'string',
|
||||
placeholder: '/Pictures/2021',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['create'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The name or path of the folder',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['create'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Field',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Parent Folder ID',
|
||||
name: 'parentFolderId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'ID of the folder you want to crate the new folder in',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:getChildren/delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folderId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['delete', 'getChildren'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:rename */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Item ID',
|
||||
name: 'itemId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['rename'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the folder',
|
||||
},
|
||||
{
|
||||
displayName: 'New Name',
|
||||
name: 'newName',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['rename'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'New name for folder',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:search */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['search'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'The query text used to search for items. Values may be matched across several fields including filename, metadata, and file content.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:share */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folderId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['share'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'File ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'View',
|
||||
value: 'view',
|
||||
},
|
||||
{
|
||||
name: 'Edit',
|
||||
value: 'edit',
|
||||
},
|
||||
{
|
||||
name: 'Embed',
|
||||
value: 'embed',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['share'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The type of sharing link to create',
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Anonymous',
|
||||
value: 'anonymous',
|
||||
},
|
||||
{
|
||||
name: 'Organization',
|
||||
value: 'organization',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['share'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The type of sharing link to create',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,185 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IDataObject,
|
||||
JsonObject,
|
||||
IHttpRequestMethods,
|
||||
IRequestOptions,
|
||||
IPollFunctions,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
export async function microsoftApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
resource: string,
|
||||
|
||||
body: any = {},
|
||||
qs: IDataObject = {},
|
||||
uri?: string,
|
||||
headers: IDataObject = {},
|
||||
option: IDataObject = { json: true },
|
||||
): Promise<any> {
|
||||
const credentials = await this.getCredentials('microsoftOneDriveOAuth2Api');
|
||||
const baseUrl = (
|
||||
typeof credentials.graphApiBaseUrl === 'string' && credentials.graphApiBaseUrl !== ''
|
||||
? credentials.graphApiBaseUrl
|
||||
: 'https://graph.microsoft.com'
|
||||
).replace(/\/+$/, '');
|
||||
const options: IRequestOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: uri || `${baseUrl}/v1.0/me${resource}`,
|
||||
};
|
||||
try {
|
||||
Object.assign(options, option);
|
||||
if (Object.keys(headers).length !== 0) {
|
||||
options.headers = Object.assign({}, options.headers, headers);
|
||||
}
|
||||
if (Object.keys(qs).length === 0) {
|
||||
delete options.qs;
|
||||
}
|
||||
if (Object.keys(body as IDataObject).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
return await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveOAuth2Api', options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
export async function microsoftApiRequestAllItems(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||
propertyName: string,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
|
||||
body: any = {},
|
||||
query: IDataObject = {},
|
||||
): Promise<any> {
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
let uri: string | undefined;
|
||||
query.$top = 100;
|
||||
|
||||
do {
|
||||
responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri);
|
||||
uri = responseData['@odata.nextLink'];
|
||||
if (uri?.includes('$top')) {
|
||||
delete query.$top;
|
||||
}
|
||||
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
|
||||
} while (responseData['@odata.nextLink'] !== undefined);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function microsoftApiRequestAllItemsSkip(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||
propertyName: string,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
|
||||
body: any = {},
|
||||
query: IDataObject = {},
|
||||
): Promise<any> {
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
query.$top = 100;
|
||||
query.$skip = 0;
|
||||
|
||||
do {
|
||||
responseData = await microsoftApiRequest.call(this, method, endpoint, body, query);
|
||||
query.$skip += query.$top;
|
||||
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
|
||||
} while (responseData.value.length !== 0);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function microsoftApiRequestAllItemsDelta(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||
link: string,
|
||||
lastDate: DateTime,
|
||||
eventType: string,
|
||||
): Promise<any> {
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
let deltaLink: string = '';
|
||||
let uri: string = link;
|
||||
|
||||
do {
|
||||
responseData = (await microsoftApiRequest.call(this, 'GET', '', {}, {}, uri)) as IDataObject;
|
||||
uri = responseData['@odata.nextLink'] as string;
|
||||
|
||||
for (const value of responseData.value as IDataObject[]) {
|
||||
if (value.fileSystemInfo as IDataObject) {
|
||||
const updatedTimeStamp = (value.fileSystemInfo as IDataObject)
|
||||
?.lastModifiedDateTime as string;
|
||||
const createdTimeStamp = (value.fileSystemInfo as IDataObject)?.createdDateTime as string;
|
||||
if (eventType === 'created') {
|
||||
if (DateTime.fromISO(createdTimeStamp) >= lastDate) {
|
||||
returnData.push(value);
|
||||
}
|
||||
}
|
||||
if (eventType === 'updated') {
|
||||
if (
|
||||
DateTime.fromISO(updatedTimeStamp) >= lastDate &&
|
||||
DateTime.fromISO(createdTimeStamp) < lastDate
|
||||
) {
|
||||
returnData.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//returnData.push.apply(returnData, responseData.value as IDataObject[]);
|
||||
deltaLink = (responseData['@odata.deltaLink'] as string) || '';
|
||||
} while (responseData['@odata.nextLink'] !== undefined);
|
||||
|
||||
return { deltaLink, returnData };
|
||||
}
|
||||
|
||||
export async function getPath(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
|
||||
itemId: string,
|
||||
): Promise<string> {
|
||||
const credentials = await this.getCredentials('microsoftOneDriveOAuth2Api');
|
||||
const baseUrl = (
|
||||
typeof credentials.graphApiBaseUrl === 'string' && credentials.graphApiBaseUrl !== ''
|
||||
? credentials.graphApiBaseUrl
|
||||
: 'https://graph.microsoft.com'
|
||||
).replace(/\/+$/, '');
|
||||
const responseData = (await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
{},
|
||||
{},
|
||||
`${baseUrl}/v1.0/me/drive/items/${itemId}`,
|
||||
)) as IDataObject;
|
||||
if (responseData.folder) {
|
||||
return (responseData?.parentReference as IDataObject)?.path + `/${responseData?.name}`;
|
||||
} else {
|
||||
const workflow = this.getWorkflow();
|
||||
const node = this.getNode();
|
||||
this.logger.error(
|
||||
`There was a problem in '${node.name}' node in workflow '${workflow.id}': 'Item to watch is not a folder'`,
|
||||
{
|
||||
node: node.name,
|
||||
workflowId: workflow.id,
|
||||
error: 'Item to watch is not a folder',
|
||||
},
|
||||
);
|
||||
throw new NodeApiError(this.getNode(), {
|
||||
error: 'Item to watch is not a folder',
|
||||
} as JsonObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.microsoftOneDrive",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Data & Storage"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/microsoft/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.microsoftonedrive/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "Hey founders! Your business doesn't need you to operate",
|
||||
"icon": " 🖥️",
|
||||
"url": "https://n8n.io/blog/your-business-doesnt-need-you-to-operate/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
import { IncomingMessage } from 'http';
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { fileFields, fileOperations } from './FileDescription';
|
||||
import { folderFields, folderOperations } from './FolderDescription';
|
||||
import { microsoftApiRequest, microsoftApiRequestAllItems } from './GenericFunctions';
|
||||
|
||||
export class MicrosoftOneDrive implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Microsoft OneDrive',
|
||||
name: 'microsoftOneDrive',
|
||||
icon: 'file:oneDrive.svg',
|
||||
group: ['input'],
|
||||
version: [1, 1.1],
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Microsoft OneDrive API',
|
||||
schemaPath: 'Microsoft/OneDrive',
|
||||
defaults: {
|
||||
name: 'Microsoft OneDrive',
|
||||
},
|
||||
usableAsTool: true,
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'microsoftOneDriveOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'File',
|
||||
value: 'file',
|
||||
},
|
||||
{
|
||||
name: 'Folder',
|
||||
value: 'folder',
|
||||
},
|
||||
],
|
||||
default: 'file',
|
||||
},
|
||||
...fileOperations,
|
||||
...fileFields,
|
||||
...folderOperations,
|
||||
...folderFields,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const length = items.length;
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
for (let i = 0; i < length; i++) {
|
||||
try {
|
||||
if (resource === 'file') {
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_copy?view=odsp-graph-online
|
||||
if (operation === 'copy') {
|
||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
const parentReference = this.getNodeParameter('parentReference', i) as IDataObject;
|
||||
const body: IDataObject = {};
|
||||
if (parentReference) {
|
||||
body.parentReference = { ...parentReference };
|
||||
}
|
||||
if (additionalFields.name) {
|
||||
body.name = additionalFields.name as string;
|
||||
}
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${fileId}/copy`,
|
||||
body,
|
||||
{},
|
||||
undefined,
|
||||
{},
|
||||
{ json: true, resolveWithFullResponse: true },
|
||||
);
|
||||
responseData = { location: responseData.headers.location };
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online
|
||||
if (operation === 'delete') {
|
||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
||||
responseData = await microsoftApiRequest.call(this, 'DELETE', `/drive/items/${fileId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online
|
||||
if (operation === 'download') {
|
||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
||||
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`);
|
||||
|
||||
const fileName = responseData.name;
|
||||
const downloadUrl = responseData['@microsoft.graph.downloadUrl'];
|
||||
|
||||
if (responseData.file === undefined) {
|
||||
throw new NodeApiError(this.getNode(), responseData as JsonObject, {
|
||||
message: 'The ID you provided does not belong to a file.',
|
||||
});
|
||||
}
|
||||
|
||||
let mimeType: string | undefined;
|
||||
if (responseData.file.mimeType) {
|
||||
mimeType = responseData.file.mimeType;
|
||||
}
|
||||
|
||||
try {
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${fileId}/content`,
|
||||
{},
|
||||
{},
|
||||
undefined,
|
||||
{},
|
||||
{ encoding: null, resolveWithFullResponse: true },
|
||||
);
|
||||
} catch (error) {
|
||||
if (downloadUrl) {
|
||||
responseData = await this.helpers.httpRequest({
|
||||
method: 'GET',
|
||||
url: downloadUrl,
|
||||
returnFullResponse: true,
|
||||
encoding: 'arraybuffer',
|
||||
json: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: items[i].json,
|
||||
binary: {},
|
||||
};
|
||||
|
||||
if (mimeType === undefined && responseData.headers['content-type']) {
|
||||
mimeType = responseData.headers['content-type'];
|
||||
}
|
||||
|
||||
if (items[i].binary !== undefined) {
|
||||
// Create a shallow copy of the binary data so that the old
|
||||
// data references which do not get changed still stay behind
|
||||
// but the incoming data does not get changed.
|
||||
Object.assign(newItem.binary!, items[i].binary);
|
||||
}
|
||||
|
||||
items[i] = newItem;
|
||||
|
||||
let data;
|
||||
if (responseData?.body instanceof IncomingMessage) {
|
||||
data = responseData.body;
|
||||
} else {
|
||||
data = Buffer.from(responseData.body as Buffer);
|
||||
}
|
||||
|
||||
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(
|
||||
data,
|
||||
fileName as string,
|
||||
mimeType,
|
||||
);
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get?view=odsp-graph-online
|
||||
if (operation === 'get') {
|
||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
||||
responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`);
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online
|
||||
if (operation === 'search') {
|
||||
const query = this.getNodeParameter('query', i) as string;
|
||||
responseData = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/root/search(q='${query}')`,
|
||||
);
|
||||
responseData = responseData.filter((item: IDataObject) => item.file);
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createlink?view=odsp-graph-online
|
||||
if (operation === 'share') {
|
||||
const fileId = this.getNodeParameter('fileId', i) as string;
|
||||
const type = this.getNodeParameter('type', i) as string;
|
||||
const scope = this.getNodeParameter('scope', i) as string;
|
||||
const body: IDataObject = {
|
||||
type,
|
||||
scope,
|
||||
};
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${fileId}/createLink`,
|
||||
body,
|
||||
);
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online#example-upload-a-new-file
|
||||
if (operation === 'upload') {
|
||||
const parentId = this.getNodeParameter('parentId', i) as string;
|
||||
const isBinaryData = this.getNodeParameter('binaryData', i);
|
||||
const fileName = this.getNodeParameter('fileName', i) as string;
|
||||
|
||||
if (isBinaryData) {
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0);
|
||||
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
|
||||
const body = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
||||
let encodedFilename;
|
||||
|
||||
if (nodeVersion >= 1.1) {
|
||||
if (fileName !== '') {
|
||||
encodedFilename = encodeURIComponent(fileName);
|
||||
} else if (binaryData.fileName !== undefined) {
|
||||
encodedFilename = encodeURIComponent(binaryData.fileName);
|
||||
}
|
||||
} else {
|
||||
if (fileName !== '') {
|
||||
encodedFilename = encodeURIComponent(fileName);
|
||||
}
|
||||
|
||||
if (binaryData.fileName !== undefined) {
|
||||
encodedFilename = encodeURIComponent(binaryData.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PUT',
|
||||
`/drive/items/${parentId}:/${encodedFilename}:/content`,
|
||||
body,
|
||||
{},
|
||||
undefined,
|
||||
{ 'Content-Type': binaryData.mimeType, 'Content-length': body.length },
|
||||
{},
|
||||
);
|
||||
|
||||
responseData = JSON.parse(responseData as string);
|
||||
} else {
|
||||
const body = this.getNodeParameter('fileContent', i) as string;
|
||||
if (fileName === '') {
|
||||
throw new NodeOperationError(this.getNode(), 'File name must be set!', {
|
||||
itemIndex: i,
|
||||
});
|
||||
}
|
||||
const encodedFilename = encodeURIComponent(fileName);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PUT',
|
||||
`/drive/items/${parentId}:/${encodedFilename}:/content`,
|
||||
body,
|
||||
{},
|
||||
undefined,
|
||||
{ 'Content-Type': 'text/plain' },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'folder') {
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children?view=odsp-graph-online
|
||||
if (operation === 'create') {
|
||||
const names = (this.getNodeParameter('name', i) as string)
|
||||
.split('/')
|
||||
.filter((s) => s.trim() !== '');
|
||||
const options = this.getNodeParameter('options', i);
|
||||
let parentFolderId = options.parentFolderId ? options.parentFolderId : null;
|
||||
for (const name of names) {
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
folder: {},
|
||||
};
|
||||
let endpoint = '/drive/root/children';
|
||||
if (parentFolderId) {
|
||||
endpoint = `/drive/items/${parentFolderId}/children`;
|
||||
}
|
||||
responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body);
|
||||
if (!responseData.id) {
|
||||
break;
|
||||
}
|
||||
parentFolderId = responseData.id;
|
||||
}
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online
|
||||
if (operation === 'delete') {
|
||||
const folderId = this.getNodeParameter('folderId', i) as string;
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'DELETE',
|
||||
`/drive/items/${folderId}`,
|
||||
);
|
||||
responseData = { success: true };
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online
|
||||
if (operation === 'getChildren') {
|
||||
const folderId = this.getNodeParameter('folderId', i) as string;
|
||||
responseData = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${folderId}/children`,
|
||||
);
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online
|
||||
if (operation === 'search') {
|
||||
const query = this.getNodeParameter('query', i) as string;
|
||||
responseData = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/root/search(q='${query}')`,
|
||||
);
|
||||
responseData = responseData.filter((item: IDataObject) => item.folder);
|
||||
}
|
||||
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createlink?view=odsp-graph-online
|
||||
if (operation === 'share') {
|
||||
const folderId = this.getNodeParameter('folderId', i) as string;
|
||||
const type = this.getNodeParameter('type', i) as string;
|
||||
const scope = this.getNodeParameter('scope', i) as string;
|
||||
const body: IDataObject = {
|
||||
type,
|
||||
scope,
|
||||
};
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${folderId}/createLink`,
|
||||
body,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (resource === 'file' || resource === 'folder') {
|
||||
if (operation === 'rename') {
|
||||
const itemId = this.getNodeParameter('itemId', i) as string;
|
||||
const newName = this.getNodeParameter('newName', i) as string;
|
||||
const body = { name: newName };
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${itemId}`,
|
||||
body,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
if (resource === 'file' && operation === 'download') {
|
||||
items[i].json = { error: error.message };
|
||||
} else {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
if (resource === 'file' && operation === 'download') {
|
||||
// For file downloads the files get attached to the existing items
|
||||
return [items];
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.microsoftOneDriveTrigger",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Data & Storage"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/microsoft/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.microsoftonedrivetrigger/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "Hey founders! Your business doesn't need you to operate",
|
||||
"icon": " 🖥️",
|
||||
"url": "https://n8n.io/blog/your-business-doesnt-need-you-to-operate/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import {
|
||||
type IPollFunctions,
|
||||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
NodeConnectionTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { getPath, microsoftApiRequest, microsoftApiRequestAllItemsDelta } from './GenericFunctions';
|
||||
import { triggerDescription } from './TriggerDescription';
|
||||
|
||||
export class MicrosoftOneDriveTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Microsoft OneDrive Trigger',
|
||||
name: 'microsoftOneDriveTrigger',
|
||||
icon: 'file:oneDrive.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Trigger for Microsoft OneDrive API.',
|
||||
subtitle: '={{($parameter["event"])}}',
|
||||
defaults: {
|
||||
name: 'Microsoft OneDrive Trigger',
|
||||
},
|
||||
credentials: [
|
||||
{
|
||||
name: 'microsoftOneDriveOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
polling: true,
|
||||
inputs: [],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
properties: [...triggerDescription],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {},
|
||||
};
|
||||
|
||||
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
|
||||
const workflowData = this.getWorkflowStaticData('node');
|
||||
let responseData: IDataObject[];
|
||||
|
||||
const credentials = await this.getCredentials('microsoftOneDriveOAuth2Api');
|
||||
const baseUrl = (
|
||||
typeof credentials.graphApiBaseUrl === 'string' && credentials.graphApiBaseUrl !== ''
|
||||
? credentials.graphApiBaseUrl
|
||||
: 'https://graph.microsoft.com'
|
||||
).replace(/\/+$/, '');
|
||||
|
||||
const lastLink: string =
|
||||
(workflowData.LastLink as string) || `${baseUrl}/v1.0/me/drive/root/delta?token=latest`;
|
||||
|
||||
const now = DateTime.now().toUTC();
|
||||
const start = DateTime.fromISO(workflowData.lastTimeChecked as string) || now;
|
||||
const end = now;
|
||||
const event = this.getNodeParameter('event', 'fileCreated') as string;
|
||||
const watch = this.getNodeParameter('watch', 'anyFile') as string;
|
||||
const watchFolder = (this.getNodeParameter('watchFolder', false) as boolean) || false;
|
||||
const folderChild = (this.getNodeParameter('options.folderChild', false) as boolean) || false;
|
||||
|
||||
let eventType = 'created';
|
||||
let eventResource = 'file';
|
||||
if (event.includes('Updated')) {
|
||||
eventType = 'updated';
|
||||
}
|
||||
if (event.includes('folder')) {
|
||||
eventResource = 'folder';
|
||||
}
|
||||
try {
|
||||
if (this.getMode() === 'manual') {
|
||||
responseData = (
|
||||
await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
{},
|
||||
{},
|
||||
`${baseUrl}/v1.0/me/drive/root/delta`,
|
||||
)
|
||||
).value as IDataObject[];
|
||||
} else {
|
||||
const response: IDataObject = (await microsoftApiRequestAllItemsDelta.call(
|
||||
this,
|
||||
lastLink,
|
||||
start,
|
||||
eventType,
|
||||
)) as IDataObject;
|
||||
responseData = response.returnData as IDataObject[];
|
||||
workflowData.LastLink = response.deltaLink;
|
||||
}
|
||||
|
||||
workflowData.lastTimeChecked = end.toISO();
|
||||
if (watch === 'selectedFile') {
|
||||
const fileId = (
|
||||
this.getNodeParameter('fileId', '', {
|
||||
extractValue: true,
|
||||
}) as string
|
||||
).replace('%21', '!');
|
||||
if (fileId) {
|
||||
responseData = responseData.filter((item: IDataObject) => item.id === fileId);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!folderChild &&
|
||||
(watch === 'oneSelectedFolder' || watch === 'selectedFolder' || watchFolder)
|
||||
) {
|
||||
const folderId = (
|
||||
this.getNodeParameter('folderId', '', {
|
||||
extractValue: true,
|
||||
}) as string
|
||||
).replace('%21', '!');
|
||||
if (folderId) {
|
||||
if (watch === 'oneSelectedFolder') {
|
||||
responseData = responseData.filter((item: IDataObject) => item.id === folderId);
|
||||
} else {
|
||||
responseData = responseData.filter(
|
||||
(item: IDataObject) => (item.parentReference as IDataObject).id === folderId,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (folderChild && (watch === 'selectedFolder' || watchFolder)) {
|
||||
const folderId = (
|
||||
this.getNodeParameter('folderId', '', {
|
||||
extractValue: true,
|
||||
}) as string
|
||||
).replace('%21', '!');
|
||||
const folderPath = await getPath.call(this, folderId);
|
||||
|
||||
responseData = responseData.filter((item: IDataObject) => {
|
||||
const path = (item.parentReference as IDataObject)?.path as string;
|
||||
return typeof path === 'string' && path.startsWith(folderPath);
|
||||
});
|
||||
}
|
||||
responseData = responseData.filter((item: IDataObject) => item[eventResource]);
|
||||
if (!responseData?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const simplify = this.getNodeParameter('simple') as boolean;
|
||||
if (simplify) {
|
||||
responseData = responseData.map((x) => ({
|
||||
id: x.id,
|
||||
createdDateTime: (x.fileSystemInfo as IDataObject)?.createdDateTime,
|
||||
lastModifiedDateTime: (x.fileSystemInfo as IDataObject)?.lastModifiedDateTime,
|
||||
name: x.name,
|
||||
webUrl: x.webUrl,
|
||||
size: x.size,
|
||||
path: (x.parentReference as IDataObject)?.path || '',
|
||||
mimeType: (x.file as IDataObject)?.mimeType || '',
|
||||
}));
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(responseData)];
|
||||
} catch (error) {
|
||||
if (this.getMode() === 'manual' || !workflowData.lastTimeChecked) {
|
||||
throw error;
|
||||
}
|
||||
const workflow = this.getWorkflow();
|
||||
const node = this.getNode();
|
||||
this.logger.error(
|
||||
`There was a problem in '${node.name}' node in workflow '${workflow.id}': '${error.description}'`,
|
||||
{
|
||||
node: node.name,
|
||||
workflowId: workflow.id,
|
||||
error,
|
||||
},
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { MICROSOFT_DRIVE_FILE_URL_REGEX, MICROSOFT_DRIVE_FOLDER_URL_REGEX } from './constants';
|
||||
|
||||
export const fileRLC: INodeProperties = {
|
||||
displayName: 'File',
|
||||
name: 'fileId',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'id', value: '' },
|
||||
required: true,
|
||||
modes: [
|
||||
{
|
||||
displayName: 'Link',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder:
|
||||
'e.g. https://onedrive.live.com/edit.aspx?resid=170B5C65E30736A3!257&cid=170b5c65e30736a3&CT=1708697995542&OR=ItemsView',
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: MICROSOFT_DRIVE_FILE_URL_REGEX,
|
||||
},
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: MICROSOFT_DRIVE_FILE_URL_REGEX,
|
||||
errorMessage: 'Not a valid Microsoft Drive File URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. 170B5C65E30736A3!257',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[a-zA-Z0-9\\!%21]{5,}',
|
||||
errorMessage: 'Not a valid Microsoft Drive File ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
url: '=https://onedrive.live.com/?id={{$value}}',
|
||||
},
|
||||
],
|
||||
description:
|
||||
"The file to operate on. The 'By URL' option only accepts URLs that start with 'https://onedrive.live.com'.",
|
||||
};
|
||||
|
||||
export const folderRLC: INodeProperties = {
|
||||
displayName: 'Folder',
|
||||
name: 'folderId',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'id', value: '', cachedResultName: '' },
|
||||
required: true,
|
||||
modes: [
|
||||
{
|
||||
displayName: 'Link',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. https://onedrive.live.com/?id=170B5C65E30736A3%21103&cid=170B5C65E30736A3',
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: MICROSOFT_DRIVE_FOLDER_URL_REGEX,
|
||||
},
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: MICROSOFT_DRIVE_FOLDER_URL_REGEX,
|
||||
errorMessage: 'Not a valid Microsoft Drive Folder URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. 170B5C65E30736A3%21136',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[a-zA-Z0-9\\!%21]{5,}',
|
||||
errorMessage: 'Not a valid Microsoft Drive Folder ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
url: '=https://onedrive.live.com/?id={{$value}}',
|
||||
},
|
||||
],
|
||||
description:
|
||||
"The folder to operate on. The 'By URL' option only accepts URLs that start with 'https://onedrive.live.com'.",
|
||||
};
|
||||
|
||||
export const triggerDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Trigger On',
|
||||
name: 'event',
|
||||
type: 'options',
|
||||
default: 'fileCreated',
|
||||
options: [
|
||||
{
|
||||
name: 'File Created',
|
||||
value: 'fileCreated',
|
||||
description: 'When a new file is created',
|
||||
},
|
||||
{
|
||||
name: 'File Updated',
|
||||
value: 'fileUpdated',
|
||||
description: 'When an existing file is modified',
|
||||
},
|
||||
{
|
||||
name: 'Folder Created',
|
||||
value: 'folderCreated',
|
||||
description: 'When a new folder is created',
|
||||
},
|
||||
{
|
||||
name: 'Folder Updated',
|
||||
value: 'folderUpdated',
|
||||
description: 'When an existing folder is modified',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simple',
|
||||
description: 'Whether to return a simplified version of the response instead of the raw data',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Watch Folder',
|
||||
name: 'watchFolder',
|
||||
description:
|
||||
'Whether to watch for the created file in a given folder, rather than the entire OneDrive',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: ['fileCreated'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Watch',
|
||||
name: 'watch',
|
||||
description: 'How to select which file to watch',
|
||||
type: 'options',
|
||||
default: 'anyFile',
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: ['fileUpdated'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Any File',
|
||||
value: 'anyFile',
|
||||
description: 'Watch for updated files in the entire OneDrive',
|
||||
},
|
||||
{
|
||||
name: 'Inside a Folder',
|
||||
value: 'selectedFolder',
|
||||
description: 'Watch for updated files inside a selected folder',
|
||||
},
|
||||
{
|
||||
name: 'A Selected File',
|
||||
value: 'selectedFile',
|
||||
description: 'Watch a specific file for updates',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Watch Folder',
|
||||
name: 'watchFolder',
|
||||
description:
|
||||
'Whether to watch for the created folder in a given folder, rather than the entire OneDrive',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: ['folderCreated'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Watch',
|
||||
name: 'watch',
|
||||
description: 'How to select which folder to watch',
|
||||
type: 'options',
|
||||
default: 'anyFolder',
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: ['folderUpdated'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Any Folder',
|
||||
value: 'anyFolder',
|
||||
description: 'Watch for updated folders in the entire OneDrive',
|
||||
},
|
||||
{
|
||||
name: 'Inside a Folder',
|
||||
value: 'selectedFolder',
|
||||
description: 'Watch for updated folders inside a selected folder',
|
||||
},
|
||||
{
|
||||
name: 'A Selected Folder',
|
||||
value: 'oneSelectedFolder',
|
||||
description: 'Watch a specific folder for updates',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
...fileRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: ['fileUpdated'],
|
||||
watch: ['selectedFile'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...folderRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
watch: ['selectedFolder', 'oneSelectedFolder'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...folderRLC,
|
||||
displayOptions: {
|
||||
show: {
|
||||
watchFolder: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
watch: ['selectedFolder'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Watch Nested Folders',
|
||||
name: 'folderChild',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to look for modified files/folders in all nested folders, rather than only direct descendants',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
watchFolder: [true],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Watch Nested Folders',
|
||||
name: 'folderChild',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to look for modified files/folders in all nested folders, rather than only direct descendants',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hashes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"quickXorHash": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fileSystemInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string"
|
||||
},
|
||||
"driveType": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 7
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@microsoft.graph.downloadUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"@odata.context": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"application": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"cTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"eTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hashes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"quickXorHash": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fileSystemInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string"
|
||||
},
|
||||
"driveType": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scope": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@microsoft.graph.downloadUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"@odata.context": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"application": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"cTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"eTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hashes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"quickXorHash": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fileSystemInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"application": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string"
|
||||
},
|
||||
"driveType": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fileSystemInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string"
|
||||
},
|
||||
"driveType": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@odata.context": {
|
||||
"type": "string"
|
||||
},
|
||||
"hasPassword": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"link": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"preventsDownload": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"webHtml": {
|
||||
"type": "string"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"shareId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 2
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@microsoft.graph.downloadUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"@odata.context": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"application": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"cTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"eTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hashes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"quickXorHash": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fileSystemInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"application": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string"
|
||||
},
|
||||
"driveType": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@odata.context": {
|
||||
"type": "string"
|
||||
},
|
||||
"@odata.etag": {
|
||||
"type": "string"
|
||||
},
|
||||
"commentSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"commentingDisabled": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"isDisabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"application": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"cTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"eTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileSystemInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"folder": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"childCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"view": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sortBy": {
|
||||
"type": "string"
|
||||
},
|
||||
"sortOrder": {
|
||||
"type": "string"
|
||||
},
|
||||
"viewType": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"application": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string"
|
||||
},
|
||||
"driveType": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"sharepointIds": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"listId": {
|
||||
"type": "string"
|
||||
},
|
||||
"listItemUniqueId": {
|
||||
"type": "string"
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string"
|
||||
},
|
||||
"siteUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"tenantId": {
|
||||
"type": "string"
|
||||
},
|
||||
"webId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 5
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@microsoft.graph.downloadUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"cTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"eTag": {
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hashes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"quickXorHash": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fileSystemInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"application": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string"
|
||||
},
|
||||
"driveType": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scope": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileSystemInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"folder": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"childCount": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string"
|
||||
},
|
||||
"driveType": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"siteId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@odata.context": {
|
||||
"type": "string"
|
||||
},
|
||||
"hasPassword": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"link": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"preventsDownload": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"shareId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"webUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const MICROSOFT_DRIVE_FILE_URL_REGEX =
|
||||
'https:\\/\\/onedrive.live.com(?:\\/.*?(?:\\&|\\?)(?:id=|resid=))(.+?)(?:\\&.*)';
|
||||
|
||||
export const MICROSOFT_DRIVE_FOLDER_URL_REGEX =
|
||||
'https:\\/\\/onedrive.live.com(?:\\/.*id=)(.+)(?:\\&cid.*)';
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 81 81"><use xlink:href="#a" x=".5" y=".5"/><symbol id="a" overflow="visible"><g stroke="none"><path fill="#094ab2" d="M6.667 0Q0 0 0 6.667v66.667Q0 80 6.667 80h66.667Q80 80 80 73.333V6.667Q80 0 73.333 0z"/><path d="M60.256 42.023c2.87.405 4.965 2.975 4.832 5.929 0 2.594-1.421 5.655-4.832 5.655H32.574c-5.306 0-7.232-3.706-7.232-7.524 0-6.444 6.979-6.621 6.979-6.621s.569-6.992 6.664-8.41c3.806-1.113 7.875.477 9.98 3.899a8.2 8.2 0 0 1 7.058-.258c2.812 1.257 4.518 4.212 4.232 7.33z"/><path fill="#f9f9f9" d="M23.583 45.605c0-7.082 7.412-8.139 7.412-8.139.707-4.108 3.699-7.435 7.682-8.54 4.021-1.286 8.404.189 10.856 3.653a6.3 6.3 0 0 1 3.635-.737c-.513-4.177-3.247-7.736-7.126-9.277-5.141-2.153-11.058.026-13.618 5.015a8.4 8.4 0 0 0-8.126-.16c-3.181 1.763-5.002 5.282-4.619 8.924-3.675.323-6.438 3.523-6.254 7.242.114 3.817 3.139 6.892 6.92 7.034h4.65a9.1 9.1 0 0 1-1.413-5.015z"/><path fill-opacity=".19" d="m.84 2.69 77.753 75.816A4.4 4.4 0 0 0 80 75.279V4.398A4.39 4.39 0 0 0 75.612 0H4.885A4.39 4.39 0 0 0 .84 2.69"/></g></symbol></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,295 @@
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
|
||||
import { microsoftApiRequest, getPath } from '../GenericFunctions';
|
||||
|
||||
describe('Microsoft OneDrive GenericFunctions', () => {
|
||||
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
let mockNode: INode;
|
||||
let mockRequestOAuth2: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
mockRequestOAuth2 = jest.fn();
|
||||
mockExecuteFunctions.helpers.requestOAuth2 = mockRequestOAuth2;
|
||||
|
||||
mockNode = {
|
||||
id: 'test-node',
|
||||
name: 'Test OneDrive Node',
|
||||
type: 'n8n-nodes-base.microsoftOneDrive',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
};
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
|
||||
mockExecuteFunctions.getWorkflow.mockReturnValue({
|
||||
id: 'test-workflow-id',
|
||||
} as any);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('microsoftApiRequest', () => {
|
||||
describe('graphApiBaseUrl from credentials', () => {
|
||||
it('should use base URL from credentials', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.us',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.us/v1.0/me/drive',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to default when credentials.graphApiBaseUrl is empty', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: '',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/drive',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to default when credentials.graphApiBaseUrl is undefined', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/drive',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should strip trailing slashes from base URL using regex', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com/',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/drive',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should strip multiple trailing slashes from base URL', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com///',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/drive',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use US Government cloud endpoint', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.us',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.us/v1.0/me/drive',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use US Government DOD cloud endpoint', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://dod-graph.microsoft.us',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://dod-graph.microsoft.us/v1.0/me/drive',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use China cloud endpoint', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://microsoftgraph.chinacloudapi.cn',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://microsoftgraph.chinacloudapi.cn/v1.0/me/drive',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPath', () => {
|
||||
describe('graphApiBaseUrl from credentials', () => {
|
||||
it('should use base URL from credentials', async () => {
|
||||
const mockResponse = {
|
||||
name: 'test-item',
|
||||
folder: {},
|
||||
parentReference: { path: '/drive/root:/folder' },
|
||||
};
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.us',
|
||||
});
|
||||
|
||||
await getPath.call(mockExecuteFunctions, 'item-id-123');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.us/v1.0/me/drive/items/item-id-123',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to default when credentials.graphApiBaseUrl is empty', async () => {
|
||||
const mockResponse = {
|
||||
name: 'test-item',
|
||||
folder: {},
|
||||
parentReference: { path: '/drive/root:/folder' },
|
||||
};
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: '',
|
||||
});
|
||||
|
||||
await getPath.call(mockExecuteFunctions, 'item-id-123');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/drive/items/item-id-123',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should strip trailing slashes from base URL using regex', async () => {
|
||||
const mockResponse = {
|
||||
name: 'test-item',
|
||||
folder: {},
|
||||
parentReference: { path: '/drive/root:/folder' },
|
||||
};
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com/',
|
||||
});
|
||||
|
||||
await getPath.call(mockExecuteFunctions, 'item-id-123');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftOneDriveOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/drive/items/item-id-123',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { IncomingMessage } from 'http';
|
||||
import type { MockProxy } from 'jest-mock-extended';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { type IHttpRequestMethods, type IExecuteFunctions, ApplicationError } from 'n8n-workflow';
|
||||
|
||||
import * as genericFunctions from '../../GenericFunctions';
|
||||
import { MicrosoftOneDrive } from '../../MicrosoftOneDrive.node';
|
||||
|
||||
jest.mock('../../GenericFunctions', () => ({
|
||||
...jest.requireActual('../../GenericFunctions'),
|
||||
microsoftApiRequest: jest.fn(async function (_: IHttpRequestMethods, resource: string) {
|
||||
if (resource === '/drive/items/fileID') {
|
||||
return {
|
||||
name: 'MyFile',
|
||||
'@microsoft.graph.downloadUrl': 'https://test.com/file',
|
||||
file: {
|
||||
mimeType: 'image/png',
|
||||
},
|
||||
};
|
||||
}
|
||||
if (resource === '/drive/items/fileID/content') {
|
||||
throw new ApplicationError('Error');
|
||||
}
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Test MicrosoftOneDrive, file > download', () => {
|
||||
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||
let microsoftOneDrive: MicrosoftOneDrive;
|
||||
const httpRequest = jest.fn(async () => ({ body: mock<IncomingMessage>() }));
|
||||
const prepareBinaryData = jest.fn(async () => ({ data: 'testBinary' }));
|
||||
|
||||
const mockNode = {
|
||||
id: 'test-node-id',
|
||||
name: 'Microsoft OneDrive Test',
|
||||
type: 'n8n-nodes-base.microsoftOneDrive',
|
||||
typeVersion: 1.1,
|
||||
position: [0, 0] as [number, number],
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||
microsoftOneDrive = new MicrosoftOneDrive();
|
||||
|
||||
mockExecuteFunctions.helpers = {
|
||||
httpRequest,
|
||||
prepareBinaryData,
|
||||
returnJsonArray: jest.fn((data) => [data]),
|
||||
constructExecutionMetaData: jest.fn((data) => data),
|
||||
} as any;
|
||||
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call helpers.httpRequest when request to /drive/items/{fileId}/content fails', async () => {
|
||||
const items = [{ json: { data: 'test' } }];
|
||||
mockExecuteFunctions.getInputData.mockReturnValue(items);
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
|
||||
if (key === 'resource') return 'file';
|
||||
if (key === 'operation') return 'download';
|
||||
if (key === 'fileId') return 'fileID';
|
||||
if (key === 'binaryPropertyName') return 'data';
|
||||
});
|
||||
|
||||
const result = await microsoftOneDrive.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(2);
|
||||
expect(httpRequest).toHaveBeenCalledTimes(1);
|
||||
expect(httpRequest).toHaveBeenCalledWith({
|
||||
encoding: 'arraybuffer',
|
||||
json: false,
|
||||
method: 'GET',
|
||||
returnFullResponse: true,
|
||||
url: 'https://test.com/file',
|
||||
});
|
||||
expect(prepareBinaryData).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual([
|
||||
[{ binary: { data: { data: 'testBinary' } }, json: { data: 'test' } }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,165 @@
|
||||
import type { MockProxy } from 'jest-mock-extended';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, IBinaryData } from 'n8n-workflow';
|
||||
|
||||
import * as genericFunctions from '../../GenericFunctions';
|
||||
import { MicrosoftOneDrive } from '../../MicrosoftOneDrive.node';
|
||||
|
||||
jest.mock('../../GenericFunctions', () => ({
|
||||
...jest.requireActual('../../GenericFunctions'),
|
||||
microsoftApiRequest: jest.fn(async function () {
|
||||
return JSON.stringify({
|
||||
id: 'uploadedFileId',
|
||||
name: 'uploadedFile.txt',
|
||||
});
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Test MicrosoftOneDrive, file > upload', () => {
|
||||
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||
let microsoftOneDrive: MicrosoftOneDrive;
|
||||
const getBinaryDataBuffer = jest.fn(async () => Buffer.from('test content'));
|
||||
|
||||
const mockNode = {
|
||||
id: 'test-node-id',
|
||||
name: 'Microsoft OneDrive Test',
|
||||
type: 'n8n-nodes-base.microsoftOneDrive',
|
||||
typeVersion: 1.1,
|
||||
position: [0, 0] as [number, number],
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||
microsoftOneDrive = new MicrosoftOneDrive();
|
||||
|
||||
mockExecuteFunctions.helpers = {
|
||||
assertBinaryData: jest.fn(() => {
|
||||
return {
|
||||
data: 'base64data',
|
||||
mimeType: 'text/plain',
|
||||
fileName: 'binaryFileName.txt',
|
||||
} as IBinaryData;
|
||||
}),
|
||||
getBinaryDataBuffer,
|
||||
returnJsonArray: jest.fn((data) => [data]),
|
||||
constructExecutionMetaData: jest.fn((data) => data),
|
||||
} as any;
|
||||
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should use filename from node parameters even when binary has a filename (version 1.1+)', async () => {
|
||||
const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }];
|
||||
mockExecuteFunctions.getInputData.mockReturnValue(items);
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
|
||||
if (key === 'resource') return 'file';
|
||||
if (key === 'operation') return 'upload';
|
||||
if (key === 'parentId') return 'parentFolderId';
|
||||
if (key === 'fileName') return 'customFileName.txt';
|
||||
if (key === 'binaryData') return true;
|
||||
if (key === 'binaryPropertyName') return 'data';
|
||||
});
|
||||
|
||||
await microsoftOneDrive.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'PUT',
|
||||
'/drive/items/parentFolderId:/customFileName.txt:/content',
|
||||
expect.any(Buffer),
|
||||
{},
|
||||
undefined,
|
||||
{ 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) },
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it('should use binary filename when node parameter filename is empty', async () => {
|
||||
const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }];
|
||||
mockExecuteFunctions.getInputData.mockReturnValue(items);
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
|
||||
if (key === 'resource') return 'file';
|
||||
if (key === 'operation') return 'upload';
|
||||
if (key === 'parentId') return 'parentFolderId';
|
||||
if (key === 'fileName') return '';
|
||||
if (key === 'binaryData') return true;
|
||||
if (key === 'binaryPropertyName') return 'data';
|
||||
});
|
||||
|
||||
await microsoftOneDrive.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'PUT',
|
||||
'/drive/items/parentFolderId:/binaryFileName.txt:/content',
|
||||
expect.any(Buffer),
|
||||
{},
|
||||
undefined,
|
||||
{ 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) },
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it('should properly encode special characters in filename from node parameters', async () => {
|
||||
const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }];
|
||||
mockExecuteFunctions.getInputData.mockReturnValue(items);
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
|
||||
if (key === 'resource') return 'file';
|
||||
if (key === 'operation') return 'upload';
|
||||
if (key === 'parentId') return 'parentFolderId';
|
||||
if (key === 'fileName') return 'file with spaces & special.txt';
|
||||
if (key === 'binaryData') return true;
|
||||
if (key === 'binaryPropertyName') return 'data';
|
||||
});
|
||||
|
||||
await microsoftOneDrive.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'PUT',
|
||||
'/drive/items/parentFolderId:/file%20with%20spaces%20%26%20special.txt:/content',
|
||||
expect.any(Buffer),
|
||||
{},
|
||||
undefined,
|
||||
{ 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) },
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it('should always use binary filename even when node parameter has a filename (version 1 - legacy bug)', async () => {
|
||||
mockExecuteFunctions.getNode.mockReturnValue({
|
||||
...mockNode,
|
||||
typeVersion: 1,
|
||||
});
|
||||
|
||||
const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }];
|
||||
mockExecuteFunctions.getInputData.mockReturnValue(items);
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
|
||||
if (key === 'resource') return 'file';
|
||||
if (key === 'operation') return 'upload';
|
||||
if (key === 'parentId') return 'parentFolderId';
|
||||
if (key === 'fileName') return 'customFileName.txt';
|
||||
if (key === 'binaryData') return true;
|
||||
if (key === 'binaryPropertyName') return 'data';
|
||||
});
|
||||
|
||||
await microsoftOneDrive.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||
// In version 1, binary filename always overwrites the parameter filename
|
||||
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'PUT',
|
||||
'/drive/items/parentFolderId:/binaryFileName.txt:/content',
|
||||
expect.any(Buffer),
|
||||
{},
|
||||
undefined,
|
||||
{ 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) },
|
||||
{},
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user