Skip to content

Commit 54acffb

Browse files
Merge pull request #7 from directus/readme-update
Readme update and adjustments to trigger request and node IO
2 parents d87521f + cf03060 commit 54acffb

7 files changed

Lines changed: 162 additions & 228 deletions

File tree

README.md

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ The package is published as `@directus/n8n-nodes-directus` on npm.
1818
npm install @directus/n8n-nodes-directus
1919
```
2020

21-
**Note**: This package is not yet published to npm. For development and testing, see the [Development](#development) section below.
22-
2321
## Usage
2422

2523
### Getting Started
@@ -143,64 +141,8 @@ npm run build
143141

144142
4. **Create workflows**: Use the Directus nodes in your workflows
145143

146-
### Project Structure
147-
148-
```
149-
├── credentials/ # Directus API credentials
150-
├── nodes/ # n8n nodes (Directus and DirectusTrigger)
151-
├── __tests__/ # Test files
152-
├── dist/ # Built/compiled files (generated)
153-
└── package.json # Package configuration
154-
```
155-
156-
### Available Commands
157-
158-
```bash
159-
# Development
160-
npm run build # Build the project (TypeScript compilation + assets)
161-
npm run dev # Watch mode for TypeScript compilation
162-
npm run dev:n8n # Start n8n with your node loaded for testing
163-
npm run build:n8n # Build nodes and credentials using n8n-node CLI
164-
165-
# Code Quality
166-
npm run lint # Check code style (repo root; tests are ignored by config)
167-
npm run lintfix # Fix code style issues
168-
npm run format # Format code using Prettier
169-
170-
# Testing
171-
npm run test # Run test suite
172-
npm run test:watch # Run tests in watch mode
173-
npm run test:coverage # Run tests with coverage report
174-
175-
# Publishing
176-
npm run release # Publish to npm using n8n-node CLI
177-
```
178-
179144
### Testing
180145

181-
#### Basic Node Testing
182-
183-
1. **Start n8n with your node loaded**:
184-
185-
```bash
186-
npm run dev:n8n
187-
```
188-
189-
2. **Access n8n**: Open http://localhost:5678 in your browser
190-
191-
3. **Configure credentials**:
192-
- Go to **Credentials****Add Credential**
193-
- Search for "Directus API" and add your credentials
194-
- Test the connection
195-
196-
4. **Test operations**:
197-
- Create a new workflow
198-
- Add a Directus node
199-
- Test various operations:
200-
- **Items**: Create, Get, Update, Delete items in collections
201-
- **Users**: Invite, Get, Update, Delete users
202-
- **Files**: Upload (requires binary data from previous node), Import (from URL), Get, Update, Delete files
203-
204146
#### Webhook Testing (Requires ngrok)
205147

206148
For testing the **Directus Trigger** node, you need to expose n8n via a public URL since Directus cannot reach localhost:
@@ -246,32 +188,7 @@ For testing the **Directus Trigger** node, you need to expose n8n via a public U
246188

247189
**Note**: The manual URL replacement step is required because Directus cannot reach localhost URLs directly.
248190

249-
### Troubleshooting
250-
251-
#### Common Issues
252-
253-
1. **n8n not starting**:
254-
- Ensure Node.js 22+ is installed
255-
- Run `pnpm install` to install dependencies
256-
- Check if port 5678 is available
257-
258-
2. **Node not appearing in n8n**:
259-
- Run `npm run build` first
260-
- Restart `npm run dev:n8n`
261-
- Check the terminal for any error messages
262-
263-
3. **Webhook not triggering**:
264-
- Ensure ngrok is running and accessible
265-
- Verify the webhook URL in Directus flows
266-
- Check n8n workflow is activated
267-
- Test the ngrok URL directly in browser
268-
269-
4. **Build errors**:
270-
- Run `npm run lint` to check for code issues
271-
- Run `npm run lintfix` to auto-fix issues
272-
- Ensure TypeScript compilation passes
273-
274-
#### Getting Help
191+
### Getting Help
275192

276193
- Check the [GitHub Issues](https://github.com/directus/n8n-nodes-directus/issues) for known problems
277194
- Run `pnpm test` to verify everything works

__tests__/Directus.node.test.ts

Lines changed: 55 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ describe('Directus Node', () => {
217217
.mockReturnValueOnce('user')
218218
.mockReturnValueOnce('update')
219219
.mockReturnValueOnce('user-1')
220-
.mockReturnValueOnce({ fields: { field: [{ name: 'email', value: '[email protected]' }] } });
220+
.mockReturnValueOnce({
221+
fields: { field: [{ name: 'email', value: '[email protected]' }] },
222+
});
221223

222224
mockExecuteFunctions.helpers.httpRequest.mockResolvedValue({
223225
data: { id: 'user-1', email: '[email protected]' },
@@ -244,74 +246,74 @@ describe('Directus Node', () => {
244246

245247
describe('File Operations', () => {
246248
it('should handle file upload operations', async () => {
247-
mockExecuteFunctions.getNodeParameter
248-
.mockReturnValueOnce('file')
249-
.mockReturnValueOnce('upload');
250-
251-
const mockBinaryData = {
252-
file: {
253-
data: Buffer.from('test file content').toString('base64'),
249+
mockExecuteFunctions.getNodeParameter
250+
.mockReturnValueOnce('file')
251+
.mockReturnValueOnce('upload');
252+
253+
const mockBinaryData = {
254+
file: {
255+
data: Buffer.from('test file content').toString('base64'),
256+
fileName: 'test.txt',
257+
mimeType: 'text/plain',
258+
},
259+
};
260+
mockExecuteFunctions.getInputData.mockReturnValue([{ binary: mockBinaryData }]);
261+
mockExecuteFunctions.helpers.assertBinaryData.mockReturnValue({
254262
fileName: 'test.txt',
255263
mimeType: 'text/plain',
256-
},
257-
};
258-
mockExecuteFunctions.getInputData.mockReturnValue([{ binary: mockBinaryData }]);
259-
mockExecuteFunctions.helpers.assertBinaryData.mockReturnValue({
260-
fileName: 'test.txt',
261-
mimeType: 'text/plain',
262-
});
263-
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(
264-
Buffer.from('test file content'),
265-
);
266-
mockExecuteFunctions.helpers.request = vi.fn().mockResolvedValue({
267-
data: { id: 'file-1', filename_download: 'test.txt' },
264+
});
265+
mockExecuteFunctions.helpers.getBinaryDataBuffer.mockResolvedValue(
266+
Buffer.from('test file content'),
267+
);
268+
mockExecuteFunctions.helpers.request = vi.fn().mockResolvedValue({
269+
data: { id: 'file-1', filename_download: 'test.txt' },
270+
});
271+
272+
const result = await node.execute.call(mockExecuteFunctions);
273+
274+
expect(result[0][0].json).toHaveProperty('id', 'file-1');
275+
expect(mockExecuteFunctions.helpers.request).toHaveBeenCalled();
268276
});
269277

270-
const result = await node.execute.call(mockExecuteFunctions);
278+
it('should handle file import operations', async () => {
279+
mockExecuteFunctions.getNodeParameter
280+
.mockReturnValueOnce('file')
281+
.mockReturnValueOnce('import')
282+
.mockReturnValueOnce('https://example.com/image.jpg');
271283

272-
expect(result[0][0].json).toHaveProperty('id', 'file-1');
273-
expect(mockExecuteFunctions.helpers.request).toHaveBeenCalled();
274-
});
284+
mockExecuteFunctions.helpers.httpRequest.mockResolvedValue({
285+
data: { id: 'file-2', filename_download: 'image.jpg' },
286+
});
275287

276-
it('should handle file import operations', async () => {
277-
mockExecuteFunctions.getNodeParameter
278-
.mockReturnValueOnce('file')
279-
.mockReturnValueOnce('import')
280-
.mockReturnValueOnce('https://example.com/image.jpg');
288+
const result = await node.execute.call(mockExecuteFunctions);
281289

282-
mockExecuteFunctions.helpers.httpRequest.mockResolvedValue({
283-
data: { id: 'file-2', filename_download: 'image.jpg' },
290+
expect(result[0][0].json).toHaveProperty('id', 'file-2');
284291
});
285292

286-
const result = await node.execute.call(mockExecuteFunctions);
293+
it('should handle file get operations', async () => {
294+
mockExecuteFunctions.getNodeParameter
295+
.mockReturnValueOnce('file')
296+
.mockReturnValueOnce('get')
297+
.mockReturnValueOnce('file-3');
287298

288-
expect(result[0][0].json).toHaveProperty('id', 'file-2');
289-
});
299+
mockExecuteFunctions.helpers.httpRequest.mockResolvedValue({
300+
data: { id: 'file-3', filename_download: 'document.pdf' },
301+
});
290302

291-
it('should handle file get operations', async () => {
292-
mockExecuteFunctions.getNodeParameter
293-
.mockReturnValueOnce('file')
294-
.mockReturnValueOnce('get')
295-
.mockReturnValueOnce('file-3');
303+
const result = await node.execute.call(mockExecuteFunctions);
296304

297-
mockExecuteFunctions.helpers.httpRequest.mockResolvedValue({
298-
data: { id: 'file-3', filename_download: 'document.pdf' },
305+
expect(result[0][0].json).toHaveProperty('id', 'file-3');
299306
});
300307

301-
const result = await node.execute.call(mockExecuteFunctions);
302-
303-
expect(result[0][0].json).toHaveProperty('id', 'file-3');
304-
});
305-
306-
it('should handle file delete operations', async () => {
307-
mockExecuteFunctions.getNodeParameter
308-
.mockReturnValueOnce('file')
309-
.mockReturnValueOnce('delete')
310-
.mockReturnValueOnce('file-4');
308+
it('should handle file delete operations', async () => {
309+
mockExecuteFunctions.getNodeParameter
310+
.mockReturnValueOnce('file')
311+
.mockReturnValueOnce('delete')
312+
.mockReturnValueOnce('file-4');
311313

312-
mockExecuteFunctions.helpers.httpRequest.mockResolvedValue({});
314+
mockExecuteFunctions.helpers.httpRequest.mockResolvedValue({});
313315

314-
const result = await node.execute.call(mockExecuteFunctions);
316+
const result = await node.execute.call(mockExecuteFunctions);
315317

316318
expect(result[0][0].json).toEqual({ deleted: true, id: 'file-4' });
317319
});

__tests__/DirectusTrigger.node.test.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('DirectusTrigger Node', () => {
2929
.mockReturnValueOnce('create')
3030
.mockReturnValueOnce('users');
3131
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({});
32-
mockWebhookFunctions.helpers.httpRequest
32+
mockWebhookFunctions.helpers.httpRequestWithAuthentication
3333
.mockResolvedValueOnce({ data: { data: [] } })
3434
.mockResolvedValueOnce({ data: { id: 'flow-id' } })
3535
.mockResolvedValueOnce({ data: { id: 'operation-id' } })
@@ -38,7 +38,7 @@ describe('DirectusTrigger Node', () => {
3838
const result = await node.webhookMethods!.default!.create.call(mockWebhookFunctions);
3939

4040
expect(result).toBe(true);
41-
expect(mockWebhookFunctions.helpers.httpRequest).toHaveBeenCalledTimes(4);
41+
expect(mockWebhookFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledTimes(4);
4242
});
4343

4444
it('should create webhook flow with script filter for user.update', async () => {
@@ -47,7 +47,7 @@ describe('DirectusTrigger Node', () => {
4747
.mockReturnValueOnce('update')
4848
.mockReturnValueOnce('directus_users');
4949
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({});
50-
mockWebhookFunctions.helpers.httpRequest
50+
mockWebhookFunctions.helpers.httpRequestWithAuthentication
5151
.mockResolvedValueOnce({ data: { data: [] } })
5252
.mockResolvedValueOnce({ data: { id: 'flow-id' } })
5353
.mockResolvedValueOnce({ data: { id: 'webhook-op-id' } })
@@ -57,19 +57,19 @@ describe('DirectusTrigger Node', () => {
5757
const result = await node.webhookMethods!.default!.create.call(mockWebhookFunctions);
5858

5959
expect(result).toBe(true);
60-
expect(mockWebhookFunctions.helpers.httpRequest).toHaveBeenCalledTimes(5);
60+
expect(mockWebhookFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledTimes(5);
6161
// Verify script operation was created with correct code
62-
const scriptCall = mockWebhookFunctions.helpers.httpRequest.mock.calls.find(
63-
(call: any[]) => call[0]?.body?.type === 'exec',
62+
const scriptCall = mockWebhookFunctions.helpers.httpRequestWithAuthentication.mock.calls.find(
63+
(call: any[]) => call[1]?.body?.type === 'exec',
6464
);
6565
expect(scriptCall).toBeDefined();
66-
expect(scriptCall[0].body.options.code).toContain('last_page');
66+
expect(scriptCall?.[1]?.body.options.code).toContain('last_page');
6767
});
6868

6969
it('should delete webhook flow', async () => {
7070
const staticData = { flowId: 'flow-1' };
7171
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue(staticData);
72-
mockWebhookFunctions.helpers.httpRequest.mockResolvedValue({});
72+
mockWebhookFunctions.helpers.httpRequestWithAuthentication.mockResolvedValue({});
7373
mockWebhookFunctions.getCredentials.mockResolvedValue({
7474
url: 'https://example.com',
7575
token: 'test-token',
@@ -78,7 +78,8 @@ describe('DirectusTrigger Node', () => {
7878
const result = await node.webhookMethods!.default!.delete.call(mockWebhookFunctions);
7979

8080
expect(result).toBe(true);
81-
expect(mockWebhookFunctions.helpers.httpRequest).toHaveBeenCalledWith(
81+
expect(mockWebhookFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
82+
'directusApi',
8283
expect.objectContaining({
8384
method: 'DELETE',
8485
}),

__tests__/helpers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ export function createMockWebhookFunctions(
8686
id: 'flow-id-123',
8787
},
8888
}),
89+
httpRequestWithAuthentication: vi.fn<any>().mockResolvedValue({
90+
data: {
91+
data: [{ id: 1, name: 'Test User' }],
92+
id: 'flow-id-123',
93+
},
94+
}),
8995
},
9096
getCredentials: vi.fn<any>().mockResolvedValue(mockDirectusCredentials()),
9197
getWorkflowStaticData: vi.fn(() => ({ flowId: undefined })),

nodes/Directus/Directus.node.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
INodeExecutionData,
44
INodeType,
55
INodeTypeDescription,
6+
NodeConnectionTypes,
67
NodeOperationError,
78
IHttpRequestOptions,
89
IDataObject,
@@ -48,8 +49,8 @@ export class Directus implements INodeType {
4849
defaults: {
4950
name: 'Directus',
5051
},
51-
inputs: ['main'],
52-
outputs: ['main'],
52+
inputs: [NodeConnectionTypes.Main],
53+
outputs: [NodeConnectionTypes.Main],
5354
usableAsTool: true,
5455
credentials: [
5556
{

0 commit comments

Comments
 (0)