@aiteza/n8n-nodes-aiteza 0.1.1 → 0.2.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.
@@ -0,0 +1,5 @@
1
+ import type { INodeType, INodeTypeDescription, IWebhookFunctions, IWebhookResponseData } from 'n8n-workflow';
2
+ export declare class AitezaTrigger implements INodeType {
3
+ description: INodeTypeDescription;
4
+ webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
5
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AitezaTrigger = void 0;
4
+ class AitezaTrigger {
5
+ description = {
6
+ displayName: 'AITEZA Trigger',
7
+ name: 'aitezaTrigger',
8
+ icon: 'file:aiteza.svg',
9
+ group: ['trigger'],
10
+ version: 1,
11
+ description: 'Starts the workflow when an AITEZA event is received via webhook. ' +
12
+ 'Passes the caller\'s auth token to all downstream AITEZA nodes so they act on behalf of the triggering user.',
13
+ defaults: { name: 'AITEZA Trigger' },
14
+ inputs: [],
15
+ outputs: ['main'],
16
+ // No credentials required – the trigger just receives webhooks.
17
+ // Downstream AITEZA nodes still need credentials (for baseUrl), but will
18
+ // use the token from this trigger for actual authentication.
19
+ credentials: [],
20
+ webhooks: [
21
+ {
22
+ name: 'default',
23
+ httpMethod: 'POST',
24
+ responseMode: '={{$parameter["responseMode"]}}',
25
+ path: '={{$parameter["path"]}}',
26
+ isFullPath: true,
27
+ },
28
+ ],
29
+ properties: [
30
+ {
31
+ displayName: 'Path',
32
+ name: 'path',
33
+ type: 'string',
34
+ default: 'aiteza',
35
+ placeholder: 'aiteza',
36
+ required: true,
37
+ description: 'The webhook path to listen on. The full URL will be /webhook/{path}.',
38
+ },
39
+ {
40
+ displayName: 'Response Mode',
41
+ name: 'responseMode',
42
+ type: 'options',
43
+ options: [
44
+ {
45
+ name: 'When Last Node Finishes',
46
+ value: 'lastNode',
47
+ description: 'Respond with data from the last node in the workflow',
48
+ },
49
+ {
50
+ name: 'Immediately',
51
+ value: 'onReceived',
52
+ description: 'Respond immediately with 200 OK',
53
+ },
54
+ ],
55
+ default: 'lastNode',
56
+ description: 'When and how to respond to the webhook',
57
+ },
58
+ ],
59
+ };
60
+ async webhook() {
61
+ const body = this.getBodyData();
62
+ const headers = this.getHeaderData();
63
+ const authHeader = headers.authorization || headers.Authorization || '';
64
+ const authToken = authHeader.replace(/^Bearer\s+/i, '');
65
+ const outputData = { ...body };
66
+ if (authToken)
67
+ outputData._authToken = authToken;
68
+ return {
69
+ workflowData: [this.helpers.returnJsonArray(outputData)],
70
+ };
71
+ }
72
+ }
73
+ exports.AitezaTrigger = AitezaTrigger;
@@ -1,11 +1,15 @@
1
1
  import type { IDataObject, IExecuteFunctions, IHttpRequestMethods, IHttpRequestOptions, ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
2
- export declare function aitezaApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject | IDataObject[], qs?: IDataObject, extraOpts?: Partial<IHttpRequestOptions>): Promise<any>;
3
- export declare function aitezaApiRequestFullResponse(this: IExecuteFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: IDataObject, extraOpts?: Partial<IHttpRequestOptions>): Promise<{
2
+ export interface AitezaAuthContext {
3
+ baseUrl: string;
4
+ triggerToken?: string;
5
+ }
6
+ export declare function getUpstreamAuthToken(ef: IExecuteFunctions): string | undefined;
7
+ export declare function aitezaApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject | IDataObject[], qs?: IDataObject, extraOpts?: Partial<IHttpRequestOptions>, authCtx?: AitezaAuthContext): Promise<any>;
8
+ export declare function aitezaApiRequestFullResponse(this: IExecuteFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: IDataObject, extraOpts?: Partial<IHttpRequestOptions>, authCtx?: AitezaAuthContext): Promise<{
4
9
  body: any;
5
10
  headers: Record<string, string>;
6
11
  }>;
7
12
  export declare function loadDatarooms(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
8
- /** Same as loadDatarooms but with a leading "None" option for optional fields. */
9
13
  export declare function loadDataroomsOptional(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
10
14
  export declare function loadModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
11
15
  export declare function loadChats(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getUpstreamAuthToken = getUpstreamAuthToken;
3
4
  exports.aitezaApiRequest = aitezaApiRequest;
4
5
  exports.aitezaApiRequestFullResponse = aitezaApiRequestFullResponse;
5
6
  exports.loadDatarooms = loadDatarooms;
@@ -13,20 +14,62 @@ exports.loadStandaloneFiles = loadStandaloneFiles;
13
14
  exports.loadStandaloneImages = loadStandaloneImages;
14
15
  exports.validateRequiredField = validateRequiredField;
15
16
  const n8n_workflow_1 = require("n8n-workflow");
17
+ function getUpstreamAuthToken(ef) {
18
+ try {
19
+ const items = ef.getInputData();
20
+ for (const item of items) {
21
+ const token = item.json?._authToken;
22
+ if (token)
23
+ return token;
24
+ }
25
+ }
26
+ catch {
27
+ // getInputData may not be available in every context
28
+ }
29
+ return undefined;
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // Internal helpers
33
+ // ---------------------------------------------------------------------------
34
+ async function resolveBaseUrl(ctx, authCtx) {
35
+ if (authCtx?.baseUrl)
36
+ return authCtx.baseUrl;
37
+ const credentials = await ctx.getCredentials('aitezaOAuth2Api');
38
+ return credentials.baseUrl.replace(/\/+$/, '');
39
+ }
40
+ async function executeRequest(ctx, options, authCtx) {
41
+ if (authCtx?.triggerToken) {
42
+ options.headers = {
43
+ ...(options.headers ?? {}),
44
+ Authorization: `Bearer ${authCtx.triggerToken}`,
45
+ };
46
+ return ctx.helpers.httpRequest(options);
47
+ }
48
+ return ctx.helpers.httpRequestWithAuthentication.call(ctx, 'aitezaOAuth2Api', options);
49
+ }
50
+ /** Convert API response items to n8n dropdown options. */
51
+ function toOptions(items, nameField = 'name') {
52
+ return items.map((item) => ({
53
+ name: item[nameField] ?? item.id,
54
+ value: item.id,
55
+ }));
56
+ }
57
+ /** Extract array from API response (handles both raw arrays and paginated `{ content: [...] }`). */
58
+ function extractItems(data) {
59
+ return Array.isArray(data) ? data : data?.content ?? [];
60
+ }
16
61
  // ---------------------------------------------------------------------------
17
62
  // Generic authenticated request helper
18
63
  // ---------------------------------------------------------------------------
19
- async function aitezaApiRequest(method, endpoint, body = {}, qs = {}, extraOpts = {}) {
20
- const credentials = await this.getCredentials('aitezaOAuth2Api');
21
- const baseUrl = credentials.baseUrl.replace(/\/+$/, '');
64
+ async function aitezaApiRequest(method, endpoint, body = {}, qs = {}, extraOpts = {}, authCtx) {
65
+ const baseUrl = await resolveBaseUrl(this, authCtx);
22
66
  const options = {
23
67
  method,
24
68
  url: `${baseUrl}${endpoint}`,
25
69
  json: true,
26
70
  ...extraOpts,
27
71
  };
28
- // Only set qs when there are actual query-string values.
29
- // Setting qs: {} can cause n8n to strip query params already in the URL.
72
+ // Only set qs when there are actual values – empty qs can strip URL params.
30
73
  if (Object.keys(qs).length > 0) {
31
74
  options.qs = qs;
32
75
  }
@@ -34,33 +77,28 @@ async function aitezaApiRequest(method, endpoint, body = {}, qs = {}, extraOpts
34
77
  options.body = body;
35
78
  }
36
79
  else if (method === 'DELETE' && Object.keys(body).length > 0) {
37
- // Some AITEZA DELETE endpoints accept a body
38
80
  options.body = body;
39
81
  }
40
82
  try {
41
- return await this.helpers.httpRequestWithAuthentication.call(this, 'aitezaOAuth2Api', options);
83
+ return await executeRequest(this, options, authCtx);
42
84
  }
43
85
  catch (error) {
44
86
  const statusCode = error?.statusCode ?? error?.httpCode ?? error?.response?.status;
45
- let message = error.message ?? 'Unknown error';
46
- if (statusCode === 401) {
47
- message = 'OAuth2 token expired or invalid – check your AITEZA credentials';
48
- }
49
- else if (statusCode === 403) {
50
- message = 'Insufficient permissions for this resource';
51
- }
52
- else if (statusCode === 404) {
53
- message = 'Resource not found';
54
- }
55
- throw new n8n_workflow_1.NodeApiError(this.getNode(), error, { message });
87
+ const messages = {
88
+ 401: 'OAuth2 token expired or invalid – check your AITEZA credentials',
89
+ 403: 'Insufficient permissions for this resource',
90
+ 404: 'Resource not found',
91
+ };
92
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
93
+ message: messages[statusCode] ?? error.message ?? 'Unknown error',
94
+ });
56
95
  }
57
96
  }
58
97
  // ---------------------------------------------------------------------------
59
98
  // Full-response variant (gives access to response headers, e.g. X-Chat-Id)
60
99
  // ---------------------------------------------------------------------------
61
- async function aitezaApiRequestFullResponse(method, endpoint, body = {}, qs = {}, extraOpts = {}) {
62
- const credentials = await this.getCredentials('aitezaOAuth2Api');
63
- const baseUrl = credentials.baseUrl.replace(/\/+$/, '');
100
+ async function aitezaApiRequestFullResponse(method, endpoint, body = {}, qs = {}, extraOpts = {}, authCtx) {
101
+ const baseUrl = await resolveBaseUrl(this, authCtx);
64
102
  const options = {
65
103
  method,
66
104
  url: `${baseUrl}${endpoint}`,
@@ -73,7 +111,7 @@ async function aitezaApiRequestFullResponse(method, endpoint, body = {}, qs = {}
73
111
  options.body = body;
74
112
  }
75
113
  try {
76
- return (await this.helpers.httpRequestWithAuthentication.call(this, 'aitezaOAuth2Api', options));
114
+ return (await executeRequest(this, options, authCtx));
77
115
  }
78
116
  catch (error) {
79
117
  throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
@@ -84,94 +122,62 @@ async function aitezaApiRequestFullResponse(method, endpoint, body = {}, qs = {}
84
122
  // ---------------------------------------------------------------------------
85
123
  async function loadDatarooms() {
86
124
  try {
87
- // Embed query params directly in the URL because empty-string qs values
88
- // are silently stripped by n8n's HTTP library.
89
- const data = await aitezaApiRequest.call(this, 'GET', '/api/dataroom/search?name=&sortBy=recentlyUsed');
90
- const items = Array.isArray(data) ? data : data?.content ?? [];
91
- if (items.length === 0) {
125
+ const data = await aitezaApiRequest.call(this, 'GET', '/api/dataroom/search?q=&sortBy=recentlyUsed');
126
+ const items = extractItems(data);
127
+ if (items.length === 0)
92
128
  return [{ name: '(No datarooms found)', value: '' }];
93
- }
94
- return items.map((d) => ({
95
- name: d.name ?? d.id,
96
- value: d.id,
97
- }));
129
+ return toOptions(items);
98
130
  }
99
131
  catch (error) {
100
132
  return [{ name: `Error loading datarooms: ${error.message ?? 'unknown'}`, value: '' }];
101
133
  }
102
134
  }
103
- /** Same as loadDatarooms but with a leading "None" option for optional fields. */
104
135
  async function loadDataroomsOptional() {
105
136
  const options = await loadDatarooms.call(this);
106
137
  return [{ name: '— None —', value: '' }, ...options];
107
138
  }
108
139
  async function loadModels() {
109
140
  const data = await aitezaApiRequest.call(this, 'GET', '/api/models');
110
- const items = Array.isArray(data) ? data : [];
111
- return items.map((m) => ({
112
- name: m.name ?? m.id,
113
- value: m.id,
114
- }));
141
+ return toOptions(extractItems(data));
115
142
  }
116
143
  async function loadChats() {
117
144
  const data = await aitezaApiRequest.call(this, 'GET', '/api/chat', {}, { size: 100 });
118
- const items = Array.isArray(data) ? data : data?.content ?? [];
119
- return items.map((c) => ({
120
- name: c.name ?? c.id,
121
- value: c.id,
122
- }));
145
+ return toOptions(extractItems(data));
123
146
  }
124
147
  async function loadFilesInDataroom() {
125
148
  const dataroomId = this.getCurrentNodeParameter('dataroomId');
126
149
  if (!dataroomId)
127
150
  return [];
128
151
  const data = await aitezaApiRequest.call(this, 'GET', `/api/dataroom/${dataroomId}/files`, {}, { size: 100 });
129
- const items = Array.isArray(data) ? data : data?.content ?? [];
130
- return items.map((f) => ({
131
- name: f.name ?? f.id,
132
- value: f.id,
133
- }));
152
+ return toOptions(extractItems(data));
134
153
  }
135
154
  async function loadImagesInDataroom() {
136
155
  const dataroomId = this.getCurrentNodeParameter('dataroomId');
137
156
  if (!dataroomId)
138
157
  return [];
139
158
  const data = await aitezaApiRequest.call(this, 'GET', `/api/dataroom/${dataroomId}/images`, {}, { size: 100 });
140
- const items = Array.isArray(data) ? data : data?.content ?? [];
141
- return items.map((img) => ({
142
- name: img.name ?? img.id,
143
- value: img.id,
144
- }));
159
+ return toOptions(extractItems(data));
145
160
  }
146
161
  async function loadWebSourcesInDataroom() {
147
162
  const dataroomId = this.getCurrentNodeParameter('dataroomId');
148
163
  if (!dataroomId)
149
164
  return [];
150
165
  const data = await aitezaApiRequest.call(this, 'GET', `/api/dataroom/${dataroomId}/websites`, {}, { size: 100 });
151
- const items = Array.isArray(data) ? data : data?.content ?? [];
152
- return items.map((ws) => ({
166
+ return extractItems(data).map((ws) => ({
153
167
  name: (ws.name || ws.url) ?? ws.id,
154
168
  value: ws.id,
155
169
  }));
156
170
  }
157
171
  async function loadStandaloneFiles() {
158
172
  const data = await aitezaApiRequest.call(this, 'GET', '/api/files', {}, { size: 100 });
159
- const items = Array.isArray(data) ? data : data?.content ?? [];
160
- return items.map((f) => ({
161
- name: f.name ?? f.id,
162
- value: f.id,
163
- }));
173
+ return toOptions(extractItems(data));
164
174
  }
165
175
  async function loadStandaloneImages() {
166
176
  const data = await aitezaApiRequest.call(this, 'GET', '/api/images', {}, { size: 100 });
167
- const items = Array.isArray(data) ? data : data?.content ?? [];
168
- return items.map((img) => ({
169
- name: img.name ?? img.id,
170
- value: img.id,
171
- }));
177
+ return toOptions(extractItems(data));
172
178
  }
173
179
  // ---------------------------------------------------------------------------
174
- // Validation helpers
180
+ // Validation helper
175
181
  // ---------------------------------------------------------------------------
176
182
  function validateRequiredField(ef, value, fieldName) {
177
183
  if (value === undefined || value === null || value === '') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiteza/n8n-nodes-aiteza",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "n8n Community Node for the AITEZA REST API (Datarooms, Files, Chat, Search, Workflows)",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
@@ -33,7 +33,8 @@
33
33
  "dist/credentials/AitezaOAuth2Api.credentials.js"
34
34
  ],
35
35
  "nodes": [
36
- "dist/nodes/Aiteza/Aiteza.node.js"
36
+ "dist/nodes/Aiteza/Aiteza.node.js",
37
+ "dist/nodes/Aiteza/AitezaTrigger.node.js"
37
38
  ]
38
39
  },
39
40
  "devDependencies": {