Skip to content

Commit

Permalink
Add enhanced Google nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
schroedan committed Mar 4, 2024
1 parent 148b3a6 commit e577263
Show file tree
Hide file tree
Showing 21 changed files with 2,781 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/publish-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
tags:
- 'clockify-enhanced-*'
- 'google-enhanced-*'

env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
30 changes: 30 additions & 0 deletions nodes/google-enhanced/.eslintrc.json
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"]
}
]
}
}
]
}
52 changes: 52 additions & 0 deletions nodes/google-enhanced/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# @skriptfabrik/n8n-nodes-google-enhanced

[![NPM Version](https://img.shields.io/npm/v/@skriptfabrik/n8n-nodes-google-enhanced)](https://www.npmjs.com/package/@skriptfabrik/n8n-nodes-google-enhanced)
[![NPM Downloads](https://img.shields.io/npm/dt/@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)
11 changes: 11 additions & 0 deletions nodes/google-enhanced/jest.config.ts
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',
};
43 changes: 43 additions & 0 deletions nodes/google-enhanced/package.json
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"
}
}
43 changes: 43 additions & 0 deletions nodes/google-enhanced/project.json
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": []
}
1 change: 1 addition & 0 deletions nodes/google-enhanced/src/index.ts
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 nodes/google-enhanced/src/nodes/GenericFunctions.spec.ts
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);
});
});
Loading

0 comments on commit e577263

Please sign in to comment.