n8n-nodes-twenty-pro 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +78 -0
  2. package/gulpfile.js +38 -0
  3. package/package.json +55 -0
  4. package/src/credentials/TwentyProApi.credentials.ts +27 -0
  5. package/src/index.ts +2 -0
  6. package/src/nodes/actions/ApiKey/TwentyProApiKey.node.ts +84 -0
  7. package/src/nodes/actions/ApiKey/operations/create.ts +14 -0
  8. package/src/nodes/actions/ApiKey/operations/delete.ts +12 -0
  9. package/src/nodes/actions/ApiKey/operations/get.ts +12 -0
  10. package/src/nodes/actions/ApiKey/operations/getMany.ts +14 -0
  11. package/src/nodes/actions/ApiKey/operations/update.ts +15 -0
  12. package/src/nodes/actions/Attachment/TwentyProAttachment.node.ts +85 -0
  13. package/src/nodes/actions/Attachment/operations/create.ts +14 -0
  14. package/src/nodes/actions/Attachment/operations/delete.ts +12 -0
  15. package/src/nodes/actions/Attachment/operations/get.ts +12 -0
  16. package/src/nodes/actions/Attachment/operations/getMany.ts +14 -0
  17. package/src/nodes/actions/Attachment/operations/update.ts +15 -0
  18. package/src/nodes/actions/CalendarEvent/TwentyProCalendarEvent.node.ts +85 -0
  19. package/src/nodes/actions/CalendarEvent/operations/create.ts +14 -0
  20. package/src/nodes/actions/CalendarEvent/operations/delete.ts +12 -0
  21. package/src/nodes/actions/CalendarEvent/operations/get.ts +12 -0
  22. package/src/nodes/actions/CalendarEvent/operations/getMany.ts +14 -0
  23. package/src/nodes/actions/CalendarEvent/operations/update.ts +15 -0
  24. package/src/nodes/actions/Company/TwentyProCompany.node.ts +111 -0
  25. package/src/nodes/actions/Company/operations/create.ts +14 -0
  26. package/src/nodes/actions/Company/operations/delete.ts +12 -0
  27. package/src/nodes/actions/Company/operations/get.ts +12 -0
  28. package/src/nodes/actions/Company/operations/getMany.ts +14 -0
  29. package/src/nodes/actions/Company/operations/update.ts +15 -0
  30. package/src/nodes/actions/CustomObject/TwentyProCustomObject.node.ts +84 -0
  31. package/src/nodes/actions/CustomObject/operations/create.ts +14 -0
  32. package/src/nodes/actions/CustomObject/operations/delete.ts +12 -0
  33. package/src/nodes/actions/CustomObject/operations/get.ts +12 -0
  34. package/src/nodes/actions/CustomObject/operations/getMany.ts +14 -0
  35. package/src/nodes/actions/CustomObject/operations/update.ts +15 -0
  36. package/src/nodes/actions/Message/TwentyProMessage.node.ts +84 -0
  37. package/src/nodes/actions/Message/operations/create.ts +14 -0
  38. package/src/nodes/actions/Message/operations/delete.ts +12 -0
  39. package/src/nodes/actions/Message/operations/get.ts +12 -0
  40. package/src/nodes/actions/Message/operations/getMany.ts +14 -0
  41. package/src/nodes/actions/Message/operations/update.ts +15 -0
  42. package/src/nodes/actions/Note/TwentyProNote.node.ts +97 -0
  43. package/src/nodes/actions/Note/operations/create.ts +14 -0
  44. package/src/nodes/actions/Note/operations/delete.ts +12 -0
  45. package/src/nodes/actions/Note/operations/get.ts +12 -0
  46. package/src/nodes/actions/Note/operations/getMany.ts +14 -0
  47. package/src/nodes/actions/Note/operations/update.ts +15 -0
  48. package/src/nodes/actions/Opportunity/TwentyProOpportunity.node.ts +89 -0
  49. package/src/nodes/actions/Opportunity/operations/create.ts +14 -0
  50. package/src/nodes/actions/Opportunity/operations/delete.ts +12 -0
  51. package/src/nodes/actions/Opportunity/operations/get.ts +12 -0
  52. package/src/nodes/actions/Opportunity/operations/getMany.ts +14 -0
  53. package/src/nodes/actions/Opportunity/operations/update.ts +15 -0
  54. package/src/nodes/actions/Person/TwentyProPerson.node.ts +93 -0
  55. package/src/nodes/actions/Person/operations/create.ts +14 -0
  56. package/src/nodes/actions/Person/operations/delete.ts +12 -0
  57. package/src/nodes/actions/Person/operations/get.ts +12 -0
  58. package/src/nodes/actions/Person/operations/getMany.ts +14 -0
  59. package/src/nodes/actions/Person/operations/update.ts +15 -0
  60. package/src/nodes/actions/Task/TwentyProTask.node.ts +89 -0
  61. package/src/nodes/actions/Task/operations/create.ts +14 -0
  62. package/src/nodes/actions/Task/operations/delete.ts +12 -0
  63. package/src/nodes/actions/Task/operations/get.ts +12 -0
  64. package/src/nodes/actions/Task/operations/getMany.ts +14 -0
  65. package/src/nodes/actions/Task/operations/update.ts +15 -0
  66. package/src/nodes/actions/View/TwentyProView.node.ts +84 -0
  67. package/src/nodes/actions/View/operations/create.ts +14 -0
  68. package/src/nodes/actions/View/operations/delete.ts +12 -0
  69. package/src/nodes/actions/View/operations/get.ts +12 -0
  70. package/src/nodes/actions/View/operations/getMany.ts +14 -0
  71. package/src/nodes/actions/View/operations/update.ts +15 -0
  72. package/src/nodes/actions/Webhook/TwentyProWebhook.node.ts +84 -0
  73. package/src/nodes/actions/Webhook/operations/create.ts +14 -0
  74. package/src/nodes/actions/Webhook/operations/delete.ts +12 -0
  75. package/src/nodes/actions/Webhook/operations/get.ts +12 -0
  76. package/src/nodes/actions/Webhook/operations/getMany.ts +14 -0
  77. package/src/nodes/actions/Webhook/operations/update.ts +15 -0
  78. package/src/nodes/actions/Workflow/TwentyProWorkflow.node.ts +85 -0
  79. package/src/nodes/actions/Workflow/operations/create.ts +14 -0
  80. package/src/nodes/actions/Workflow/operations/delete.ts +12 -0
  81. package/src/nodes/actions/Workflow/operations/get.ts +12 -0
  82. package/src/nodes/actions/Workflow/operations/getMany.ts +14 -0
  83. package/src/nodes/actions/Workflow/operations/update.ts +15 -0
  84. package/src/nodes/shared/FieldTransformers.ts +245 -0
  85. package/src/nodes/shared/RecordLocator.ts +68 -0
  86. package/src/nodes/shared/SchemaDiscovery.ts +95 -0
  87. package/src/nodes/shared/TwentyProApi.client.ts +156 -0
  88. package/src/nodes/shared/twentyPro.svg +6 -0
  89. package/src/nodes/shared/types.ts +59 -0
  90. package/src/nodes/triggers/ApiKey/TwentyProApiKeyTrigger.node.ts +50 -0
  91. package/src/nodes/triggers/Attachment/TwentyProAttachmentTrigger.node.ts +50 -0
  92. package/src/nodes/triggers/CalendarEvent/TwentyProCalendarEventTrigger.node.ts +50 -0
  93. package/src/nodes/triggers/Company/TwentyProCompanyTrigger.node.ts +50 -0
  94. package/src/nodes/triggers/CustomObject/TwentyProCustomObjectTrigger.node.ts +50 -0
  95. package/src/nodes/triggers/Message/TwentyProMessageTrigger.node.ts +50 -0
  96. package/src/nodes/triggers/Note/TwentyProNoteTrigger.node.ts +50 -0
  97. package/src/nodes/triggers/Opportunity/TwentyProOpportunityTrigger.node.ts +50 -0
  98. package/src/nodes/triggers/Person/TwentyProPersonTrigger.node.ts +50 -0
  99. package/src/nodes/triggers/Task/TwentyProTaskTrigger.node.ts +50 -0
  100. package/src/nodes/triggers/View/TwentyProViewTrigger.node.ts +50 -0
  101. package/src/nodes/triggers/Webhook/TwentyProWebhookTrigger.node.ts +50 -0
  102. package/src/nodes/triggers/Workflow/TwentyProWorkflowTrigger.node.ts +50 -0
  103. package/tsconfig.json +19 -0
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # n8n-nodes-twenty-pro
2
+
3
+ Comprehensive n8n community node for [Twenty CRM](https://twenty.com) — open-source CRM.
4
+
5
+ **13 action nodes + 13 trigger nodes** for complete Twenty CRM integration.
6
+
7
+ ## Features
8
+
9
+ - **13 resource-specific nodes** — Person, Company, Opportunity, Note, Task, CalendarEvent, Message, Attachment, Workflow, View, Webhook, ApiKey, CustomObject
10
+ - **13 webhook trigger nodes** — one per resource (record created/updated/deleted)
11
+ - **Dynamic schema discovery** — custom objects/fields auto-detected
12
+ - **Complex field types** — FullName, Links, Currency, Address, Emails, Phones
13
+ - **Full CRUD** — Create, Get, Update, Delete, Get Many on every resource
14
+ - **Auto-retry** — exponential backoff on rate limits and network errors
15
+ - **Record locator** — search by name, paste URL, or enter ID
16
+
17
+ ## Installation
18
+
19
+ In n8n, go to **Settings > Community Nodes > Install** and enter:
20
+
21
+ ```
22
+ n8n-nodes-twenty-pro
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ 1. In Twenty CRM: **Settings > Developers > API Keys** > Create API Key
28
+ 2. In n8n: Add **Twenty Pro API** credential
29
+ 3. Enter: **API Key** (from step 1), **Domain** (your Twenty URL, e.g. `https://app.twenty.com`)
30
+
31
+ **Important:** Do NOT include `/graphql` or `/rest` in the domain.
32
+
33
+ ## Nodes
34
+
35
+ ### Action Nodes
36
+
37
+ | Node | Operations | Fields |
38
+ |------|-----------|--------|
39
+ | Twenty Person | Create, Get, Update, Delete, Get Many | First Name, Last Name, Email, Phone, Job Title, City, LinkedIn, X |
40
+ | Twenty Company | Create, Get, Update, Delete, Get Many | Name, Domain, LinkedIn, X, Address (6 fields), Revenue, Employees |
41
+ | Twenty Opportunity | Create, Get, Update, Delete, Get Many | Name, Amount, Stage, Close Date |
42
+ | Twenty Note | Create, Get, Update, Delete, Get Many | Title, Content |
43
+ | Twenty Task | Create, Get, Update, Delete, Get Many | Title, Due Date, Priority, Assignee ID |
44
+ | Twenty Calendar Event | Create, Get, Update, Delete, Get Many | Title, Starts At, Ends At |
45
+ | Twenty Message | Create, Get, Update, Delete, Get Many | Subject, Body |
46
+ | Twenty Attachment | Create, Get, Update, Delete, Get Many | Name, MIME Type, Attachment URL |
47
+ | Twenty Workflow | Create, Get, Update, Delete, Get Many | Name, Status, Trigger Type |
48
+ | Twenty View | Create, Get, Update, Delete, Get Many | Name, Object Type |
49
+ | Twenty Webhook | Create, Get, Update, Delete, Get Many | Target URL, Event Types |
50
+ | Twenty API Key | Create, Get, Update, Delete, Get Many | Name, Expires At |
51
+ | Twenty Custom Object | Create, Get, Update, Delete, Get Many | Dynamic (based on schema) |
52
+
53
+ ### Trigger Nodes
54
+
55
+ Every resource has a corresponding trigger node: Twenty [Resource] Trigger.
56
+
57
+ Events per trigger: Record Created / Record Updated / Record Deleted
58
+
59
+ ## API
60
+
61
+ Uses **REST** for all data operations. **GraphQL** only for schema introspection (custom field discovery).
62
+
63
+ - Requires Twenty CRM v1.4.0+
64
+ - Requires n8n v1.0.0+
65
+ - Node.js 18.10+
66
+
67
+ ## Development
68
+
69
+ ```bash
70
+ git clone <repo>
71
+ cd n8n-nodes-twenty-pro
72
+ npm install
73
+ npm run build
74
+ ```
75
+
76
+ ## License
77
+
78
+ MIT
package/gulpfile.js ADDED
@@ -0,0 +1,38 @@
1
+ const { src, dest, series } = require('gulp');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ // Copy SVG icon to every directory that contains a .node.js file
6
+ function copySvg(done) {
7
+ const svgSrc = 'src/nodes/shared/twentyPro.svg';
8
+ if (!fs.existsSync(svgSrc)) {
9
+ console.warn('twentyPro.svg not found, skipping copy');
10
+ return done();
11
+ }
12
+ const distNodes = 'dist/nodes';
13
+ const entries = fs.readdirSync(distNodes, { withFileTypes: true });
14
+ const dirs = [];
15
+ for (const entry of entries) {
16
+ if (!entry.isDirectory()) continue;
17
+ const sub = entry.name; // actions or triggers
18
+ const subPath = path.join(distNodes, sub);
19
+ for (const resource of fs.readdirSync(subPath)) {
20
+ const resourcePath = path.join(subPath, resource);
21
+ if (fs.statSync(resourcePath).isDirectory()) {
22
+ dirs.push(resourcePath);
23
+ }
24
+ }
25
+ }
26
+ for (const dir of dirs) {
27
+ fs.copyFileSync(svgSrc, path.join(dir, 'twentyPro.svg'));
28
+ }
29
+ console.log(`Copied twentyPro.svg to ${dirs.length} node directories`);
30
+ done();
31
+ }
32
+
33
+ function build() {
34
+ return src('dist/**/*.js')
35
+ .pipe(dest('dist/'));
36
+ }
37
+
38
+ exports.build = series(build, copySvg);
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "n8n-nodes-twenty-pro",
3
+ "version": "0.1.0",
4
+ "description": "Comprehensive n8n community node for Twenty CRM with 32 resource nodes + 32 trigger nodes",
5
+ "keywords": ["n8n", "n8n-community-node", "twenty", "twenty-crm", "crm"],
6
+ "license": "MIT",
7
+ "author": { "name": "Suheb", "email": "suheb@example.com" },
8
+ "engines": { "node": ">=18.10.0" },
9
+ "n8n": {
10
+ "n8nNodesApiVersion": 1,
11
+ "credentials": ["dist/credentials/TwentyProApi.credentials.js"],
12
+ "nodes": [
13
+ "dist/nodes/actions/Person/TwentyProPerson.node.js",
14
+ "dist/nodes/actions/Company/TwentyProCompany.node.js",
15
+ "dist/nodes/actions/Opportunity/TwentyProOpportunity.node.js",
16
+ "dist/nodes/actions/Note/TwentyProNote.node.js",
17
+ "dist/nodes/actions/Task/TwentyProTask.node.js",
18
+ "dist/nodes/actions/CalendarEvent/TwentyProCalendarEvent.node.js",
19
+ "dist/nodes/actions/Message/TwentyProMessage.node.js",
20
+ "dist/nodes/actions/Attachment/TwentyProAttachment.node.js",
21
+ "dist/nodes/actions/Workflow/TwentyProWorkflow.node.js",
22
+ "dist/nodes/actions/View/TwentyProView.node.js",
23
+ "dist/nodes/actions/Webhook/TwentyProWebhook.node.js",
24
+ "dist/nodes/actions/ApiKey/TwentyProApiKey.node.js",
25
+ "dist/nodes/actions/CustomObject/TwentyProCustomObject.node.js",
26
+ "dist/nodes/triggers/Person/TwentyProPersonTrigger.node.js",
27
+ "dist/nodes/triggers/Company/TwentyProCompanyTrigger.node.js",
28
+ "dist/nodes/triggers/Opportunity/TwentyProOpportunityTrigger.node.js",
29
+ "dist/nodes/triggers/Note/TwentyProNoteTrigger.node.js",
30
+ "dist/nodes/triggers/Task/TwentyProTaskTrigger.node.js",
31
+ "dist/nodes/triggers/CalendarEvent/TwentyProCalendarEventTrigger.node.js",
32
+ "dist/nodes/triggers/Message/TwentyProMessageTrigger.node.js",
33
+ "dist/nodes/triggers/Attachment/TwentyProAttachmentTrigger.node.js",
34
+ "dist/nodes/triggers/Workflow/TwentyProWorkflowTrigger.node.js",
35
+ "dist/nodes/triggers/View/TwentyProViewTrigger.node.js",
36
+ "dist/nodes/triggers/Webhook/TwentyProWebhookTrigger.node.js",
37
+ "dist/nodes/triggers/ApiKey/TwentyProApiKeyTrigger.node.js",
38
+ "dist/nodes/triggers/CustomObject/TwentyProCustomObjectTrigger.node.js"
39
+ ]
40
+ },
41
+ "scripts": {
42
+ "build": "tsc && gulp build",
43
+ "dev": "tsc -w",
44
+ "lint": "tslint -p tsconfig.json -c tslint.json",
45
+ "lint:fix": "tslint --fix -p tsconfig.json -c tslint.json"
46
+ },
47
+ "peerDependencies": { "n8n-workflow": "*" },
48
+ "devDependencies": {
49
+ "@types/node": "^18.10.0",
50
+ "gulp": "^4.0.2",
51
+ "n8n-node-dev": "^2.9.1",
52
+ "tslint": "^6.1.3",
53
+ "typescript": "^5.0.0"
54
+ }
55
+ }
@@ -0,0 +1,27 @@
1
+ import { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+
3
+ export class TwentyProApi implements ICredentialType {
4
+ name = 'twentyProApi';
5
+ displayName = 'Twenty Pro API';
6
+ documentationUrl = 'https://twenty.com/developers';
7
+ properties: INodeProperties[] = [
8
+ {
9
+ displayName: 'API Key',
10
+ name: 'apiKey',
11
+ type: 'string',
12
+ typeOptions: { password: true },
13
+ required: true,
14
+ default: '',
15
+ description: 'API key from Twenty CRM (Settings → Developers → API Keys)',
16
+ },
17
+ {
18
+ displayName: 'Domain',
19
+ name: 'domain',
20
+ type: 'string',
21
+ required: true,
22
+ default: '',
23
+ placeholder: 'https://app.twenty.com',
24
+ description: 'Your Twenty CRM instance URL. Do NOT include /graphql or /rest.',
25
+ },
26
+ ];
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ // n8n-nodes-twenty-pro - comprehensive Twenty CRM integration for n8n
2
+ export * from './credentials/TwentyProApi.credentials';
@@ -0,0 +1,84 @@
1
+ import { IDataObject, INodeType, INodeTypeDescription, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import * as create from './operations/create';
3
+ import * as get from './operations/get';
4
+ import * as update from './operations/update';
5
+ import * as del from './operations/delete';
6
+ import * as getMany from './operations/getMany';
7
+
8
+ export class TwentyProApiKey implements INodeType {
9
+ description: INodeTypeDescription = {
10
+ displayName: 'Twenty API Key',
11
+ name: 'twentyProApiKey',
12
+ icon: 'file:twentyPro.svg',
13
+ group: ['transform'],
14
+ version: 1,
15
+ subtitle: '={{$parameter["operation"]}}',
16
+ description: 'Manage API keys in Twenty CRM',
17
+ defaults: { name: 'Twenty API Key' },
18
+ inputs: ['main'],
19
+ outputs: ['main'],
20
+ credentials: [{ name: 'twentyProApi', required: true }],
21
+ properties: [
22
+ {
23
+ displayName: 'Operation',
24
+ name: 'operation',
25
+ type: 'options',
26
+ noDataExpression: true,
27
+ required: true,
28
+ default: 'create',
29
+ options: [
30
+ { name: 'Create', value: 'create', description: 'Create a new API key' },
31
+ { name: 'Get', value: 'get', description: 'Get an API key by ID' },
32
+ { name: 'Update', value: 'update', description: 'Update an API key' },
33
+ { name: 'Delete', value: 'delete', description: 'Delete an API key' },
34
+ { name: 'Get Many', value: 'getMany', description: 'List API keys' },
35
+ ],
36
+ },
37
+ {
38
+ displayName: 'Record ID',
39
+ name: 'recordId',
40
+ type: 'string',
41
+ default: '',
42
+ required: true,
43
+ displayOptions: { show: { operation: ['get', 'update', 'delete'] } },
44
+ placeholder: 'abc-123-def-456',
45
+ },
46
+ {
47
+ displayName: 'Fields',
48
+ name: 'fields',
49
+ placeholder: 'Add Field',
50
+ type: 'fixedCollection',
51
+ default: {},
52
+ typeOptions: { multipleValues: true },
53
+ displayOptions: { show: { operation: ['create', 'update'] } },
54
+ options: [
55
+ {
56
+ name: 'fieldValues',
57
+ displayName: 'Field',
58
+ values: [
59
+ { displayName: 'Name', name: 'name', type: 'string', default: '' },
60
+ { displayName: 'Expires At', name: 'expiresAt', type: 'string', default: '' },
61
+ ],
62
+ },
63
+ ],
64
+ },
65
+ { displayName: 'Return All', name: 'returnAll', type: 'boolean', default: false, displayOptions: { show: { operation: ['getMany'] } } },
66
+ { displayName: 'Limit', name: 'limit', type: 'number', default: 50, displayOptions: { show: { operation: ['getMany'], returnAll: [false] } }, typeOptions: { minValue: 1, maxValue: 100 } },
67
+ ],
68
+ };
69
+
70
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
71
+ const items = this.getInputData();
72
+ const operation = this.getNode().parameters.operation as string;
73
+ let result: INodeExecutionData[];
74
+ switch (operation) {
75
+ case 'create': result = await create.execute.call(this, items); break;
76
+ case 'get': result = await get.execute.call(this, items); break;
77
+ case 'update': result = await update.execute.call(this, items); break;
78
+ case 'delete': result = await del.execute.call(this, items); break;
79
+ case 'getMany': result = await getMany.execute.call(this, items); break;
80
+ default: throw new Error(`Unknown operation: ${operation}`);
81
+ }
82
+ return [result];
83
+ }
84
+ }
@@ -0,0 +1,14 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+ import { transformApiKeyFields } from '../../../shared/FieldTransformers';
4
+
5
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
6
+ const returnData: INodeExecutionData[] = [];
7
+ for (let i = 0; i < items.length; i++) {
8
+ const fields = this.getNode().parameters.fields as Record<string, unknown> || {};
9
+ const transformed = transformApiKeyFields(fields);
10
+ const response = await apiRequest.call(this, 'POST', '/apiKeys', transformed);
11
+ returnData.push({ json: response as IDataObject });
12
+ }
13
+ return returnData;
14
+ }
@@ -0,0 +1,12 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+
4
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
5
+ const returnData: INodeExecutionData[] = [];
6
+ for (let i = 0; i < items.length; i++) {
7
+ const id = this.getNode().parameters.recordId as string;
8
+ await apiRequest.call(this, 'DELETE', `/apiKeys/${id}`);
9
+ returnData.push({ json: { success: true, id } as IDataObject });
10
+ }
11
+ return returnData;
12
+ }
@@ -0,0 +1,12 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+
4
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
5
+ const returnData: INodeExecutionData[] = [];
6
+ for (let i = 0; i < items.length; i++) {
7
+ const id = this.getNode().parameters.recordId as string;
8
+ const response = await apiRequest.call(this, 'GET', `/apiKeys/${id}`);
9
+ returnData.push({ json: response as IDataObject });
10
+ }
11
+ return returnData;
12
+ }
@@ -0,0 +1,14 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+
4
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
5
+ const returnAll = this.getNode().parameters.returnAll as boolean;
6
+ const limit = this.getNode().parameters.limit as number;
7
+ const qs: Record<string, string> = {};
8
+ if (!returnAll) { qs.limit = String(limit); }
9
+ const response = await apiRequest.call(this, 'GET', '/apiKeys', undefined, qs) as IDataObject;
10
+ const data = response.data as IDataObject | undefined;
11
+ const dataKeys = data ? Object.keys(data) : [];
12
+ const items_arr = dataKeys.length > 0 ? ((data![dataKeys[0]] as IDataObject[]) ?? []) : [];
13
+ return items_arr.map((item) => ({ json: item }));
14
+ }
@@ -0,0 +1,15 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+ import { transformApiKeyFields } from '../../../shared/FieldTransformers';
4
+
5
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
6
+ const returnData: INodeExecutionData[] = [];
7
+ for (let i = 0; i < items.length; i++) {
8
+ const id = this.getNode().parameters.recordId as string;
9
+ const fields = this.getNode().parameters.fields as Record<string, unknown> || {};
10
+ const transformed = transformApiKeyFields(fields);
11
+ const response = await apiRequest.call(this, 'PATCH', `/apiKeys/${id}`, transformed);
12
+ returnData.push({ json: response as IDataObject });
13
+ }
14
+ return returnData;
15
+ }
@@ -0,0 +1,85 @@
1
+ import { IDataObject, INodeType, INodeTypeDescription, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import * as create from './operations/create';
3
+ import * as get from './operations/get';
4
+ import * as update from './operations/update';
5
+ import * as del from './operations/delete';
6
+ import * as getMany from './operations/getMany';
7
+
8
+ export class TwentyProAttachment implements INodeType {
9
+ description: INodeTypeDescription = {
10
+ displayName: 'Twenty Attachment',
11
+ name: 'twentyProAttachment',
12
+ icon: 'file:twentyPro.svg',
13
+ group: ['transform'],
14
+ version: 1,
15
+ subtitle: '={{$parameter["operation"]}}',
16
+ description: 'Manage attachments in Twenty CRM',
17
+ defaults: { name: 'Twenty Attachment' },
18
+ inputs: ['main'],
19
+ outputs: ['main'],
20
+ credentials: [{ name: 'twentyProApi', required: true }],
21
+ properties: [
22
+ {
23
+ displayName: 'Operation',
24
+ name: 'operation',
25
+ type: 'options',
26
+ noDataExpression: true,
27
+ required: true,
28
+ default: 'create',
29
+ options: [
30
+ { name: 'Create', value: 'create', description: 'Create a new attachment' },
31
+ { name: 'Get', value: 'get', description: 'Get an attachment by ID' },
32
+ { name: 'Update', value: 'update', description: 'Update an attachment' },
33
+ { name: 'Delete', value: 'delete', description: 'Delete an attachment' },
34
+ { name: 'Get Many', value: 'getMany', description: 'List attachments' },
35
+ ],
36
+ },
37
+ {
38
+ displayName: 'Record ID',
39
+ name: 'recordId',
40
+ type: 'string',
41
+ default: '',
42
+ required: true,
43
+ displayOptions: { show: { operation: ['get', 'update', 'delete'] } },
44
+ placeholder: 'abc-123-def-456',
45
+ },
46
+ {
47
+ displayName: 'Fields',
48
+ name: 'fields',
49
+ placeholder: 'Add Field',
50
+ type: 'fixedCollection',
51
+ default: {},
52
+ typeOptions: { multipleValues: true },
53
+ displayOptions: { show: { operation: ['create', 'update'] } },
54
+ options: [
55
+ {
56
+ name: 'fieldValues',
57
+ displayName: 'Field',
58
+ values: [
59
+ { displayName: 'Name', name: 'name', type: 'string', default: '' },
60
+ { displayName: 'Mime Type', name: 'mimeType', type: 'string', default: '' },
61
+ { displayName: 'Attachment Url', name: 'attachmentUrl', type: 'string', default: '' },
62
+ ],
63
+ },
64
+ ],
65
+ },
66
+ { displayName: 'Return All', name: 'returnAll', type: 'boolean', default: false, displayOptions: { show: { operation: ['getMany'] } } },
67
+ { displayName: 'Limit', name: 'limit', type: 'number', default: 50, displayOptions: { show: { operation: ['getMany'], returnAll: [false] } }, typeOptions: { minValue: 1, maxValue: 100 } },
68
+ ],
69
+ };
70
+
71
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
72
+ const items = this.getInputData();
73
+ const operation = this.getNode().parameters.operation as string;
74
+ let result: INodeExecutionData[];
75
+ switch (operation) {
76
+ case 'create': result = await create.execute.call(this, items); break;
77
+ case 'get': result = await get.execute.call(this, items); break;
78
+ case 'update': result = await update.execute.call(this, items); break;
79
+ case 'delete': result = await del.execute.call(this, items); break;
80
+ case 'getMany': result = await getMany.execute.call(this, items); break;
81
+ default: throw new Error(`Unknown operation: ${operation}`);
82
+ }
83
+ return [result];
84
+ }
85
+ }
@@ -0,0 +1,14 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+ import { transformAttachmentFields } from '../../../shared/FieldTransformers';
4
+
5
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
6
+ const returnData: INodeExecutionData[] = [];
7
+ for (let i = 0; i < items.length; i++) {
8
+ const fields = this.getNode().parameters.fields as Record<string, unknown> || {};
9
+ const transformed = transformAttachmentFields(fields);
10
+ const response = await apiRequest.call(this, 'POST', '/attachments', transformed);
11
+ returnData.push({ json: response as IDataObject });
12
+ }
13
+ return returnData;
14
+ }
@@ -0,0 +1,12 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+
4
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
5
+ const returnData: INodeExecutionData[] = [];
6
+ for (let i = 0; i < items.length; i++) {
7
+ const id = this.getNode().parameters.recordId as string;
8
+ await apiRequest.call(this, 'DELETE', `/attachments/${id}`);
9
+ returnData.push({ json: { success: true, id } as IDataObject });
10
+ }
11
+ return returnData;
12
+ }
@@ -0,0 +1,12 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+
4
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
5
+ const returnData: INodeExecutionData[] = [];
6
+ for (let i = 0; i < items.length; i++) {
7
+ const id = this.getNode().parameters.recordId as string;
8
+ const response = await apiRequest.call(this, 'GET', `/attachments/${id}`);
9
+ returnData.push({ json: response as IDataObject });
10
+ }
11
+ return returnData;
12
+ }
@@ -0,0 +1,14 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+
4
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
5
+ const returnAll = this.getNode().parameters.returnAll as boolean;
6
+ const limit = this.getNode().parameters.limit as number;
7
+ const qs: Record<string, string> = {};
8
+ if (!returnAll) { qs.limit = String(limit); }
9
+ const response = await apiRequest.call(this, 'GET', '/attachments', undefined, qs) as IDataObject;
10
+ const data = response.data as IDataObject | undefined;
11
+ const dataKeys = data ? Object.keys(data) : [];
12
+ const items_arr = dataKeys.length > 0 ? ((data![dataKeys[0]] as IDataObject[]) ?? []) : [];
13
+ return items_arr.map((item) => ({ json: item }));
14
+ }
@@ -0,0 +1,15 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+ import { transformAttachmentFields } from '../../../shared/FieldTransformers';
4
+
5
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
6
+ const returnData: INodeExecutionData[] = [];
7
+ for (let i = 0; i < items.length; i++) {
8
+ const id = this.getNode().parameters.recordId as string;
9
+ const fields = this.getNode().parameters.fields as Record<string, unknown> || {};
10
+ const transformed = transformAttachmentFields(fields);
11
+ const response = await apiRequest.call(this, 'PATCH', `/attachments/${id}`, transformed);
12
+ returnData.push({ json: response as IDataObject });
13
+ }
14
+ return returnData;
15
+ }
@@ -0,0 +1,85 @@
1
+ import { IDataObject, INodeType, INodeTypeDescription, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import * as create from './operations/create';
3
+ import * as get from './operations/get';
4
+ import * as update from './operations/update';
5
+ import * as del from './operations/delete';
6
+ import * as getMany from './operations/getMany';
7
+
8
+ export class TwentyProCalendarEvent implements INodeType {
9
+ description: INodeTypeDescription = {
10
+ displayName: 'Twenty Calendar Event',
11
+ name: 'twentyProCalendarEvent',
12
+ icon: 'file:twentyPro.svg',
13
+ group: ['transform'],
14
+ version: 1,
15
+ subtitle: '={{$parameter["operation"]}}',
16
+ description: 'Manage calendar events in Twenty CRM',
17
+ defaults: { name: 'Twenty Calendar Event' },
18
+ inputs: ['main'],
19
+ outputs: ['main'],
20
+ credentials: [{ name: 'twentyProApi', required: true }],
21
+ properties: [
22
+ {
23
+ displayName: 'Operation',
24
+ name: 'operation',
25
+ type: 'options',
26
+ noDataExpression: true,
27
+ required: true,
28
+ default: 'create',
29
+ options: [
30
+ { name: 'Create', value: 'create', description: 'Create a new calendar event' },
31
+ { name: 'Get', value: 'get', description: 'Get a calendar event by ID' },
32
+ { name: 'Update', value: 'update', description: 'Update a calendar event' },
33
+ { name: 'Delete', value: 'delete', description: 'Delete a calendar event' },
34
+ { name: 'Get Many', value: 'getMany', description: 'List calendar events' },
35
+ ],
36
+ },
37
+ {
38
+ displayName: 'Record ID',
39
+ name: 'recordId',
40
+ type: 'string',
41
+ default: '',
42
+ required: true,
43
+ displayOptions: { show: { operation: ['get', 'update', 'delete'] } },
44
+ placeholder: 'abc-123-def-456',
45
+ },
46
+ {
47
+ displayName: 'Fields',
48
+ name: 'fields',
49
+ placeholder: 'Add Field',
50
+ type: 'fixedCollection',
51
+ default: {},
52
+ typeOptions: { multipleValues: true },
53
+ displayOptions: { show: { operation: ['create', 'update'] } },
54
+ options: [
55
+ {
56
+ name: 'fieldValues',
57
+ displayName: 'Field',
58
+ values: [
59
+ { displayName: 'Title', name: 'title', type: 'string', default: '' },
60
+ { displayName: 'Starts At', name: 'startsAt', type: 'string', default: '' },
61
+ { displayName: 'Ends At', name: 'endsAt', type: 'string', default: '' },
62
+ ],
63
+ },
64
+ ],
65
+ },
66
+ { displayName: 'Return All', name: 'returnAll', type: 'boolean', default: false, displayOptions: { show: { operation: ['getMany'] } } },
67
+ { displayName: 'Limit', name: 'limit', type: 'number', default: 50, displayOptions: { show: { operation: ['getMany'], returnAll: [false] } }, typeOptions: { minValue: 1, maxValue: 100 } },
68
+ ],
69
+ };
70
+
71
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
72
+ const items = this.getInputData();
73
+ const operation = this.getNode().parameters.operation as string;
74
+ let result: INodeExecutionData[];
75
+ switch (operation) {
76
+ case 'create': result = await create.execute.call(this, items); break;
77
+ case 'get': result = await get.execute.call(this, items); break;
78
+ case 'update': result = await update.execute.call(this, items); break;
79
+ case 'delete': result = await del.execute.call(this, items); break;
80
+ case 'getMany': result = await getMany.execute.call(this, items); break;
81
+ default: throw new Error(`Unknown operation: ${operation}`);
82
+ }
83
+ return [result];
84
+ }
85
+ }
@@ -0,0 +1,14 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+ import { transformCalendarEventFields } from '../../../shared/FieldTransformers';
4
+
5
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
6
+ const returnData: INodeExecutionData[] = [];
7
+ for (let i = 0; i < items.length; i++) {
8
+ const fields = this.getNode().parameters.fields as Record<string, unknown> || {};
9
+ const transformed = transformCalendarEventFields(fields);
10
+ const response = await apiRequest.call(this, 'POST', '/calendarEvents', transformed);
11
+ returnData.push({ json: response as IDataObject });
12
+ }
13
+ return returnData;
14
+ }
@@ -0,0 +1,12 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+
4
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
5
+ const returnData: INodeExecutionData[] = [];
6
+ for (let i = 0; i < items.length; i++) {
7
+ const id = this.getNode().parameters.recordId as string;
8
+ await apiRequest.call(this, 'DELETE', `/calendarEvents/${id}`);
9
+ returnData.push({ json: { success: true, id } as IDataObject });
10
+ }
11
+ return returnData;
12
+ }
@@ -0,0 +1,12 @@
1
+ import { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ import { apiRequest } from '../../../shared/TwentyProApi.client';
3
+
4
+ export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]): Promise<INodeExecutionData[]> {
5
+ const returnData: INodeExecutionData[] = [];
6
+ for (let i = 0; i < items.length; i++) {
7
+ const id = this.getNode().parameters.recordId as string;
8
+ const response = await apiRequest.call(this, 'GET', `/calendarEvents/${id}`);
9
+ returnData.push({ json: response as IDataObject });
10
+ }
11
+ return returnData;
12
+ }