-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from skriptfabrik/google-cloud-storage-enhanced
Add enhanced Google nodes
- Loading branch information
Showing
21 changed files
with
2,781 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"extends": ["../../.eslintrc.base.json"], | ||
"ignorePatterns": ["!**/*"], | ||
"overrides": [ | ||
{ | ||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.ts", "*.tsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.js", "*.jsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.json"], | ||
"parser": "jsonc-eslint-parser", | ||
"rules": { | ||
"@nx/dependency-checks": [ | ||
"error", | ||
{ | ||
"ignoredDependencies": ["express", "jest-mock-extended"] | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# @skriptfabrik/n8n-nodes-google-enhanced | ||
|
||
[](https://www.npmjs.com/package/@skriptfabrik/n8n-nodes-google-enhanced) | ||
[](https://www.npmjs.com/package/@skriptfabrik/n8n-nodes-google-enhanced) | ||
|
||
> Enhanced Google community nodes for your [n8n](https://n8n.io/) workflows | ||
This is an n8n community node. It lets you use [Google actions](https://docs.n8n.io/integrations/builtin/app-nodes/) with | ||
service account credentials in your n8n workflows. Although the authentication method recommended by n8n is OAuth2, this | ||
is not suitable in an environment where no user interaction is intended. | ||
|
||
[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform. | ||
|
||
[Installation](#installation) | ||
[Operations](#operations) | ||
[Credentials](#credentials) | ||
[Compatibility](#compatibility) | ||
[Resources](#resources) | ||
|
||
## Installation | ||
|
||
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community | ||
nodes documentation. | ||
|
||
1. Go to **Settings > Community Nodes**. | ||
2. Select **Install**. | ||
3. Enter `@skriptfabrik/n8n-nodes-google-enhanced` in **Enter npm package name**. | ||
4. Agree to the [risks](https://docs.n8n.io/integrations/community-nodes/risks/) of using community nodes: select | ||
**I understand the risks of installing unverified code from a public source**. | ||
5. Select **Install**. | ||
|
||
After installing the node, you can use it like any other node. n8n displays the node in search results in the **Nodes** panel. | ||
|
||
## Operations | ||
|
||
It supports these operations with Service Account credentials: | ||
|
||
- Create, delete, get, list, update Google Cloud Storage buckets and objects | ||
|
||
## Credentials | ||
|
||
You can use the built-in [Google credentials](https://docs.n8n.io/integrations/builtin/credentials/google/) to | ||
authenticate with Google. | ||
|
||
## Compatibility | ||
|
||
Tested against n8n version 1.0+. | ||
|
||
## Resources | ||
|
||
- [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/) | ||
- [Google Cloud Storage API (v1)](https://cloud.google.com/storage/docs/json_api/v1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* eslint-disable */ | ||
export default { | ||
displayName: 'n8n-nodes-google-enhanced', | ||
preset: '../../jest.preset.js', | ||
testEnvironment: 'node', | ||
transform: { | ||
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }], | ||
}, | ||
moduleFileExtensions: ['ts', 'js', 'html'], | ||
coverageDirectory: '../../coverage/nodes/google-enhanced', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"name": "@skriptfabrik/n8n-nodes-google-enhanced", | ||
"version": "0.1.0", | ||
"description": "Enhanced Google community nodes for n8n", | ||
"keywords": [ | ||
"google", | ||
"google-cloud-storage", | ||
"n8n", | ||
"n8n-community-node", | ||
"n8n-community-node-package" | ||
], | ||
"license": "MIT", | ||
"homepage": "https://github.com/skriptfabrik/n8n-nodes/blob/main/nodes/google-enhanced/README.md", | ||
"author": { | ||
"name": "skriptfabrik", | ||
"email": "[email protected]" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/skriptfabrik/n8n-nodes.git" | ||
}, | ||
"main": "./src/index.js", | ||
"typings": "./src/index.d.ts", | ||
"type": "commonjs", | ||
"n8n": { | ||
"n8nNodesApiVersion": 1, | ||
"credentials": [], | ||
"nodes": [ | ||
"src/nodes/GoogleCloudStorageEnhanced/GoogleCloudStorageEnhanced.node.js" | ||
] | ||
}, | ||
"dependencies": { | ||
"form-data": "^4.0.0", | ||
"jsonwebtoken": "^9.0.0", | ||
"moment-timezone": "^0.5.28", | ||
"n8n-nodes-base": "^1.29.1", | ||
"n8n-workflow": "^1.29.1", | ||
"tslib": "^2.6.2" | ||
}, | ||
"publishConfig": { | ||
"registry": "https://registry.npmjs.org" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"name": "n8n-nodes-google-enhanced", | ||
"$schema": "../../node_modules/nx/schemas/project-schema.json", | ||
"sourceRoot": "nodes/google-enhanced/src", | ||
"projectType": "library", | ||
"targets": { | ||
"build": { | ||
"executor": "@nx/js:tsc", | ||
"outputs": ["{options.outputPath}"], | ||
"options": { | ||
"outputPath": "dist/nodes/google-enhanced", | ||
"tsConfig": "nodes/google-enhanced/tsconfig.lib.json", | ||
"packageJson": "nodes/google-enhanced/package.json", | ||
"main": "nodes/google-enhanced/src/index.ts", | ||
"assets": [ | ||
"nodes/google-enhanced/src/nodes/*/*.svg", | ||
"nodes/google-enhanced/src/nodes/*/*.json", | ||
"nodes/google-enhanced/*.md" | ||
] | ||
} | ||
}, | ||
"link": { | ||
"executor": "nx:run-commands", | ||
"options": { | ||
"cwd": "dist/nodes/google-enhanced", | ||
"command": "npm link --no-audit" | ||
}, | ||
"dependsOn": ["build"] | ||
}, | ||
"install": { | ||
"executor": "nx:run-commands", | ||
"options": { | ||
"command": "node tools/scripts/install.mjs n8n-nodes-google-enhanced" | ||
}, | ||
"dependsOn": ["link"] | ||
}, | ||
"publish": { | ||
"command": "node tools/scripts/publish.mjs n8n-nodes-google-enhanced {args.ver} {args.tag}", | ||
"dependsOn": ["build"] | ||
} | ||
}, | ||
"tags": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './nodes/GoogleCloudStorageEnhanced/GoogleCloudStorageEnhanced.node'; |
160 changes: 160 additions & 0 deletions
160
nodes/google-enhanced/src/nodes/GenericFunctions.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import { mockClear, mockDeep } from 'jest-mock-extended'; | ||
import * as jwt from 'jsonwebtoken'; | ||
import moment from 'moment-timezone'; | ||
import type { IExecuteFunctions } from 'n8n-workflow'; | ||
import { | ||
createMultipartForm, | ||
parseBodyData, | ||
requestAccessToken, | ||
requestServiceAccount, | ||
} from './GenericFunctions'; | ||
|
||
jest.mock('jsonwebtoken'); | ||
jest.mock('moment-timezone'); | ||
|
||
describe('GenericFunctions', () => { | ||
const executeFunctions = mockDeep<IExecuteFunctions>(); | ||
const mockedJwt = jest.mocked(jwt); | ||
const mockedMoment = jest.mocked(moment); | ||
const momentInstance = mockDeep<moment.Moment>(); | ||
|
||
beforeEach(() => { | ||
mockedMoment.mockReturnValue(momentInstance); | ||
}); | ||
|
||
afterEach(() => { | ||
mockClear(executeFunctions); | ||
mockClear(mockedMoment); | ||
mockClear(momentInstance); | ||
}); | ||
|
||
it('should create multipart form', () => { | ||
const metadata = { | ||
items: '[{"name": "__test__"}]', | ||
}; | ||
|
||
expect( | ||
createMultipartForm(metadata, '__content__', 'application/json', 100), | ||
).toBeDefined(); | ||
}); | ||
|
||
it('should parse body data', () => { | ||
const bodyData = { | ||
validItems: '[{"name": "__test__"}]', | ||
invalidItems: '{name: "__test__"}', | ||
}; | ||
|
||
expect( | ||
parseBodyData(bodyData, ['validItems', 'invalidItems', 'undefinedItems']), | ||
).toEqual({ | ||
validItems: [{ name: '__test__' }], | ||
invalidItems: '{name: "__test__"}', | ||
}); | ||
}); | ||
|
||
it('should request access token', () => { | ||
momentInstance.unix.mockReturnValue(300); | ||
executeFunctions.getCredentials.calledWith('googleApi').mockResolvedValue({ | ||
email: '__email__', | ||
privateKey: '__private_key__', | ||
}); | ||
mockedJwt.sign.mockImplementation(() => '__jwt__'); | ||
executeFunctions.helpers.request.mockResolvedValueOnce({ | ||
access_token: '__access_token__', | ||
}); | ||
|
||
expect( | ||
requestAccessToken.call(executeFunctions, 'googleApi', [ | ||
'https://www.googleapis.com/auth/cloud-platform', | ||
'https://www.googleapis.com/auth/cloud-platform.read-only', | ||
'https://www.googleapis.com/auth/devstorage.full_control', | ||
'https://www.googleapis.com/auth/devstorage.read_only', | ||
'https://www.googleapis.com/auth/devstorage.read_write', | ||
]), | ||
).resolves.toEqual({ | ||
access_token: '__access_token__', | ||
}); | ||
}); | ||
|
||
it('should request access token with delegated email', () => { | ||
momentInstance.unix.mockReturnValue(300); | ||
executeFunctions.getCredentials.calledWith('googleApi').mockResolvedValue({ | ||
delegatedEmail: '__delegated_email__', | ||
privateKey: '__private_key__', | ||
}); | ||
mockedJwt.sign.mockImplementation(() => '__jwt__'); | ||
executeFunctions.helpers.request.mockResolvedValueOnce({ | ||
access_token: '__access_token__', | ||
}); | ||
|
||
expect( | ||
requestAccessToken.call(executeFunctions, 'googleApi', [ | ||
'https://www.googleapis.com/auth/cloud-platform', | ||
'https://www.googleapis.com/auth/cloud-platform.read-only', | ||
'https://www.googleapis.com/auth/devstorage.full_control', | ||
'https://www.googleapis.com/auth/devstorage.read_only', | ||
'https://www.googleapis.com/auth/devstorage.read_write', | ||
]), | ||
).resolves.toEqual({ | ||
access_token: '__access_token__', | ||
}); | ||
}); | ||
|
||
it('should request service account', () => { | ||
const returnData = { | ||
kind: 'storage#bucket', | ||
selfLink: 'https://www.googleapis.com/storage/v1/b/__bucket__', | ||
id: '__bucket__', | ||
name: '__bucket__', | ||
projectNumber: '1234567890', | ||
metageneration: '2', | ||
location: 'EU', | ||
storageClass: 'STANDARD', | ||
etag: 'CAI=', | ||
timeCreated: '2024-02-26T12:29:08.082Z', | ||
updated: '2024-02-26T12:55:47.601Z', | ||
iamConfiguration: { | ||
bucketPolicyOnly: { | ||
enabled: true, | ||
lockedTime: '2024-05-26T12:29:08.082Z', | ||
}, | ||
uniformBucketLevelAccess: { | ||
enabled: true, | ||
lockedTime: '2024-05-26T12:29:08.082Z', | ||
}, | ||
publicAccessPrevention: 'enforced', | ||
}, | ||
locationType: 'multi-region', | ||
rpo: 'DEFAULT', | ||
}; | ||
|
||
momentInstance.unix.mockReturnValue(300); | ||
executeFunctions.getCredentials.calledWith('googleApi').mockResolvedValue({ | ||
email: '__email__', | ||
privateKey: '__private_key__', | ||
}); | ||
mockedJwt.sign.mockImplementation(() => '__jwt__'); | ||
executeFunctions.helpers.request.mockResolvedValueOnce({ | ||
access_token: '__access_token__', | ||
}); | ||
executeFunctions.helpers.request.mockResolvedValueOnce(returnData); | ||
|
||
expect( | ||
requestServiceAccount.call( | ||
executeFunctions, | ||
'googleApi', | ||
{ | ||
baseURL: 'https://storage.googleapis.com/storage/v1', | ||
url: '/b/__bucket__', | ||
}, | ||
[ | ||
'https://www.googleapis.com/auth/cloud-platform', | ||
'https://www.googleapis.com/auth/cloud-platform.read-only', | ||
'https://www.googleapis.com/auth/devstorage.full_control', | ||
'https://www.googleapis.com/auth/devstorage.read_only', | ||
'https://www.googleapis.com/auth/devstorage.read_write', | ||
], | ||
), | ||
).resolves.toEqual(returnData); | ||
}); | ||
}); |
Oops, something went wrong.