├── .eslintrc.json
├── .eslintrc.prepublish.js
├── .github
└── FUNDING.yml
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── DEBUG.md
├── LICENSE
├── README.md
├── api-docs-v2.35.0.json
├── api-docs-v2.35.3.json
├── api-docs-v2.36.0.json
├── api-docs-v2.36.3.json
├── api-docs-v2.37.0.beta.json
├── credentials
└── HuduApi.credentials.ts
├── gulpfile.js
├── index.ts
├── package.json
├── src
└── nodes
│ └── Hudu
│ ├── Hudu.node.json
│ ├── Hudu.node.ts
│ ├── descriptions
│ ├── HuduAssetCustomFieldDescription.ts
│ ├── HuduAssetLinkFieldDescription.ts
│ ├── HuduAssetStandardFieldDescription.ts
│ ├── activity_logs.description.ts
│ ├── api_info.description.ts
│ ├── articles.description.ts
│ ├── asset_layout_fields.description.ts
│ ├── asset_layouts.description.ts
│ ├── asset_passwords.description.ts
│ ├── assets.description.ts
│ ├── cards.description.ts
│ ├── companies.description.ts
│ ├── expirations.description.ts
│ ├── folders.description.ts
│ ├── index.ts
│ ├── ip_addresses.description.ts
│ ├── list_options.description.ts
│ ├── lists.description.ts
│ ├── magic_dash.description.ts
│ ├── matchers.description.ts
│ ├── networks.description.ts
│ ├── passwordFolders.descriptions.ts
│ ├── procedure_tasks.description.ts
│ ├── procedures.description.ts
│ ├── public_photos.description.ts
│ ├── rack_storage_items.description.ts
│ ├── rack_storages.description.ts
│ ├── relations.description.ts
│ ├── resources.ts
│ ├── uploads.description.ts
│ ├── users.description.ts
│ ├── vlan_zones.description.ts
│ ├── vlans.description.ts
│ └── websites.description.ts
│ ├── docs
│ └── FILTERS.md
│ ├── hudu.svg
│ ├── optionLoaders
│ ├── asset_layouts
│ │ ├── getAssetLayoutFieldValues.ts
│ │ ├── getAssetLayoutFields.ts
│ │ ├── getAssetLayouts.ts
│ │ ├── getAssetLinkFields.ts
│ │ ├── getCustomFieldsLayoutFields.ts
│ │ └── index.ts
│ ├── assets
│ │ ├── getAssetCustomFields.ts
│ │ ├── getAssets.ts
│ │ ├── getAssetsForCompany.ts
│ │ ├── getLinkableAssets.ts
│ │ └── index.ts
│ ├── companies
│ │ ├── getCompanies.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── lists
│ │ ├── getLists.ts
│ │ └── index.ts
│ └── users
│ │ ├── getUsers.ts
│ │ └── index.ts
│ ├── resources
│ ├── activity_logs
│ │ ├── activity_logs.handler.ts
│ │ └── activity_logs.types.ts
│ ├── api_info
│ │ ├── api_info.handler.ts
│ │ └── api_info.types.ts
│ ├── articles
│ │ ├── articles.handler.ts
│ │ └── articles.types.ts
│ ├── assetCustomField
│ │ ├── assetCustomField.handler.ts
│ │ └── assetCustomField.types.ts
│ ├── assetLinkField
│ │ ├── assetLinkField.handler.ts
│ │ └── assetLinkField.types.ts
│ ├── assetStandardField
│ │ ├── assetStandardField.handler.ts
│ │ └── assetStandardField.types.ts
│ ├── asset_layout_fields
│ │ ├── asset_layout_fields.handler.ts
│ │ └── asset_layout_fields.types.ts
│ ├── asset_layouts
│ │ ├── asset_layouts.handler.ts
│ │ └── asset_layouts.types.ts
│ ├── asset_passwords
│ │ ├── asset_passwords.handler.ts
│ │ └── asset_passwords.types.ts
│ ├── assets
│ │ ├── assets.handler.ts
│ │ └── assets.types.ts
│ ├── cards
│ │ ├── cards.handler.ts
│ │ └── cards.types.ts
│ ├── companies
│ │ ├── companies.handler.ts
│ │ └── companies.types.ts
│ ├── expirations
│ │ ├── expirations.handler.ts
│ │ └── expirations.types.ts
│ ├── folders
│ │ ├── folders.handler.ts
│ │ └── folders.types.ts
│ ├── index.ts
│ ├── ip_addresses
│ │ ├── ip_addresses.handler.ts
│ │ └── ip_addresses.types.ts
│ ├── list_options
│ │ ├── list_options.handler.ts
│ │ └── list_options.types.ts
│ ├── lists
│ │ ├── lists.handler.ts
│ │ └── lists.types.ts
│ ├── magic_dash
│ │ ├── magic_dash.handler.ts
│ │ └── magic_dash.types.ts
│ ├── matchers
│ │ ├── matchers.handler.ts
│ │ └── matchers.types.ts
│ ├── networks
│ │ ├── networks.handler.ts
│ │ └── networks.types.ts
│ ├── password_folders
│ │ ├── password_folders.handler.ts
│ │ └── password_folders.types.ts
│ ├── procedure_tasks
│ │ ├── procedure_tasks.handler.ts
│ │ └── procedure_tasks.types.ts
│ ├── procedures
│ │ ├── procedures.handler.ts
│ │ └── procedures.types.ts
│ ├── public_photos
│ │ ├── public_photos.handler.ts
│ │ └── public_photos.types.ts
│ ├── rack_storage_items
│ │ ├── rack_storage_items.handler.ts
│ │ └── rack_storage_items.types.ts
│ ├── rack_storages
│ │ ├── rack_storages.handler.ts
│ │ └── rack_storages.types.ts
│ ├── relations
│ │ ├── relations.handler.ts
│ │ └── relations.types.ts
│ ├── uploads
│ │ ├── uploads.handler.ts
│ │ └── uploads.types.ts
│ ├── users
│ │ ├── users.handler.ts
│ │ └── users.types.ts
│ ├── vlan_zones
│ │ ├── vlan_zones.handler.ts
│ │ └── vlan_zones.types.ts
│ ├── vlans
│ │ ├── vlans.handler.ts
│ │ └── vlans.types.ts
│ └── websites
│ │ ├── websites.handler.ts
│ │ └── websites.types.ts
│ └── utils
│ ├── constants.ts
│ ├── dateUtils.ts
│ ├── debugConfig.ts
│ ├── filterUtils.ts
│ ├── formatters.ts
│ ├── index.ts
│ ├── operations
│ ├── archive.ts
│ ├── cards.ts
│ ├── companies.ts
│ ├── create.ts
│ ├── delete.ts
│ ├── get.ts
│ ├── getAll.ts
│ ├── getCompanyIdForAsset.ts
│ ├── index.ts
│ ├── magic_dash.ts
│ ├── matchers.ts
│ ├── procedures.ts
│ ├── systemInfo.ts
│ └── update.ts
│ ├── requestUtils.ts
│ ├── types.ts
│ └── validation.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "parserOptions": {
4 | "project": "./tsconfig.json",
5 | "extraFileExtensions": [".json"]
6 | },
7 | "extends": [
8 | "plugin:n8n-nodes-base/nodes"
9 | ],
10 | "plugins": ["eslint-plugin-n8n-nodes-base"],
11 | "rules": {
12 | "n8n-nodes-base/node-param-default-missing": "off",
13 | "n8n-nodes-base/node-param-description-wrong-for-return-all": "off",
14 | "n8n-nodes-base/node-class-description-inputs-wrong-regular-node": "off",
15 | "n8n-nodes-base/node-param-display-name-wrong-for-update-fields": "off"
16 | },
17 | "overrides": [
18 | {
19 | "files": ["*.json"],
20 | "parser": "jsonc-eslint-parser"
21 | },
22 | {
23 | "files": ["*.js"],
24 | "parser": "espree"
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/.eslintrc.prepublish.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['.eslintrc.js'],
3 | rules: {
4 | 'n8n-nodes-base/node-param-resource-with-plural-option': 'off'
5 | }
6 | };
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 | buy_me_a_coffee: msoukhomlinov
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 | package-lock.json
7 | .package-lock.json
8 | pnpm-lock.yaml
9 | yarn.lock
10 | .npm
11 | .npmrc
12 |
13 | # Build outputs
14 | dist/
15 | build/
16 | *.tsbuildinfo
17 | *.map
18 | *.d.ts
19 |
20 | # IDE and editor
21 | .idea/
22 | .project
23 | .classpath
24 | .c9/
25 | *.launch
26 | .settings/
27 | *.sublime-workspace
28 | .sublime-*
29 | .vscode/*
30 | *.swp
31 | *.swo
32 | *.suo
33 | *.ntvs*
34 | *.njsproj
35 | *.sln
36 |
37 | # Environment variables
38 | .env
39 | .env.*
40 | !.env.example
41 | .env.test
42 |
43 | # OS specific
44 | .DS_Store
45 | Thumbs.db
46 |
47 | # Cache
48 | .cache/
49 | .eslintcache
50 |
51 | # Temporary files
52 | temp/
53 | tmp/
54 | .tmp/
55 | .temp/
56 | *.tmp
57 |
58 | # Cursor specific (since you're using Cursor IDE)
59 | .cursorcache
60 | .cursorconfig
61 | .cursorignore
62 | .cursor/
63 | .cursor-rules/
64 | .cursor-search-cache
65 | .cursor-settings.json
66 | .cursor-keybindings.json
67 | .cursor-snippets/
68 | .cursor-prompts/
69 | .cursorrules
70 | .cursorignore
71 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "bracketSpacing": true,
5 | "tabWidth": 2,
6 | "semi": true,
7 | "arrowParens": "always"
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Max Soukhomlinov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/credentials/HuduApi.credentials.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | IAuthenticateGeneric,
3 | ICredentialTestRequest,
4 | ICredentialType,
5 | INodeProperties,
6 | } from 'n8n-workflow';
7 |
8 | export class HuduApi implements ICredentialType {
9 | name = 'huduApi';
10 | displayName = 'Hudu API';
11 | properties: INodeProperties[] = [
12 | {
13 | displayName: 'Base URL',
14 | name: 'baseUrl',
15 | type: 'string',
16 | default: '',
17 | placeholder: 'https://your-hudu-instance',
18 | required: true,
19 | },
20 | {
21 | displayName: 'API Key',
22 | name: 'apiKey',
23 | type: 'string',
24 | typeOptions: {
25 | password: true,
26 | },
27 | default: '',
28 | required: true,
29 | },
30 | ];
31 |
32 | authenticate: IAuthenticateGeneric = {
33 | type: 'generic',
34 | properties: {
35 | headers: {
36 | 'x-api-key': '={{$credentials.apiKey}}',
37 | },
38 | },
39 | };
40 |
41 | test: ICredentialTestRequest = {
42 | request: {
43 | baseURL: '={{$credentials.baseUrl}}',
44 | url: '/api/v1/companies',
45 | method: 'GET',
46 | },
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const clean = require('gulp-clean');
3 |
4 | // Clean dist folder
5 | function cleanDist() {
6 | return gulp.src('dist', {
7 | read: false,
8 | allowEmpty: true
9 | })
10 | .pipe(clean());
11 | }
12 |
13 | // Copy credentials to dist
14 | function copyCredentials() {
15 | return gulp.src('credentials/**/*')
16 | .pipe(gulp.dest('dist/credentials'));
17 | }
18 |
19 | // Copy node files to dist (including icons and supporting folders)
20 | function copyNodeFiles() {
21 | return gulp.src('src/nodes/Hudu/**/*', { base: 'src' })
22 | .pipe(gulp.dest('dist'));
23 | }
24 |
25 | // Move compiled files to correct locations and cleanup
26 | function moveCompiledFiles() {
27 | return gulp.src('dist/src/nodes/Hudu/**/*.{js,js.map,json}', { base: 'dist/src' })
28 | .pipe(gulp.dest('dist'))
29 | .on('end', () => {
30 | gulp.src('dist/src', { read: false, allowEmpty: true })
31 | .pipe(clean());
32 | });
33 | }
34 |
35 | // Organize the build tasks
36 | exports['copy:files'] = gulp.series(copyCredentials, copyNodeFiles);
37 | exports.cleanDist = cleanDist;
38 | exports.move = moveCompiledFiles;
39 | exports.default = gulp.series(cleanDist, copyCredentials, copyNodeFiles, moveCompiledFiles);
40 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import type { INodeType } from 'n8n-workflow';
2 | import { Hudu } from './src/nodes/Hudu/Hudu.node';
3 |
4 | // Export the instantiated class
5 | const huduNode = new Hudu();
6 | export { huduNode as Hudu };
7 |
8 | export const nodes: INodeType[] = [huduNode];
9 |
10 | export const credentials = [
11 | {
12 | name: 'huduApi',
13 | displayName: 'Hudu API',
14 | },
15 | ];
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "n8n-nodes-hudu",
3 | "version": "1.3.5",
4 | "description": "This n8n custom node facilitates integration with Hudu's API.",
5 | "keywords": [
6 | "n8n-community-node-package",
7 | "n8n-node-hudu",
8 | "hudu",
9 | "hudu-api",
10 | "knowledgebase-management",
11 | "n8n-custom-node",
12 | "workflow-automation",
13 | "data-operations"
14 | ],
15 | "license": "MIT",
16 | "homepage": "https://github.com/msoukhomlinov/n8n-nodes-hudu",
17 | "author": {
18 | "name": "Max Soukhomlinov",
19 | "email": "maxs@intellectit.com.au"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/msoukhomlinov/n8n-nodes-hudu.git"
24 | },
25 | "main": "dist/index.js",
26 | "types": "dist/index.d.ts",
27 | "scripts": {
28 | "build": "gulp cleanDist && tsc && gulp copy:files && gulp move",
29 | "dev": "tsc --watch",
30 | "format": "prettier src nodes credentials --write",
31 | "lint": "eslint \"src/**/*.ts\" *.ts package.json",
32 | "lintfix": "eslint \"src/**/*.ts\" *.ts package.json --fix",
33 | "prepublishOnly": "npm run build",
34 | "clean": "gulp cleanDist"
35 | },
36 | "files": [
37 | "dist"
38 | ],
39 | "n8n": {
40 | "n8nNodesApiVersion": 1,
41 | "credentials": [
42 | "dist/credentials/HuduApi.credentials.js"
43 | ],
44 | "nodes": [
45 | "dist/nodes/Hudu/Hudu.node.js"
46 | ]
47 | },
48 | "engines": {
49 | "n8n": ">=1.69.2"
50 | },
51 | "devDependencies": {
52 | "@types/cheerio": "^0.22.35",
53 | "@types/diff": "^7.0.0",
54 | "@types/express": "^4.17.6",
55 | "@types/got": "^9.6.12",
56 | "@types/lodash": "^4.17.14",
57 | "@types/luxon": "^3.4.2",
58 | "@types/node": "^20.17.50",
59 | "@types/node-fetch": "^2.6.11",
60 | "@types/request-promise-native": "~1.0.15",
61 | "@types/turndown": "^5.0.5",
62 | "@typescript-eslint/eslint-plugin": "^5.57.1",
63 | "@typescript-eslint/parser": "~5.45",
64 | "eslint": "^8.37.0",
65 | "eslint-config-prettier": "^8.8.0",
66 | "eslint-plugin-n8n-nodes-base": "^1.16.3",
67 | "eslint-plugin-prettier": "^4.2.1",
68 | "got": "^14.4.5",
69 | "gulp": "^4.0.2",
70 | "gulp-clean": "^0.4.0",
71 | "jsonc-eslint-parser": "^2.4.0",
72 | "n8n-core": "^1.69.2",
73 | "n8n-workflow": "^1.69.2",
74 | "node-fetch": "^2.7.0",
75 | "prettier": "^2.7.1",
76 | "typescript": "^4.9.5"
77 | },
78 | "dependencies": {
79 | "cheerio": "^1.0.0",
80 | "diff": "^5.1.0",
81 | "html-entities": "^2.5.2",
82 | "html-to-markdown": "^1.0.0",
83 | "lodash": "^4.17.21",
84 | "luxon": "^3.4.4",
85 | "parse5": "^7.1.2"
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/Hudu.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "node": "n8n-nodes-base.hudu",
3 | "nodeVersion": "1.0",
4 | "codexVersion": "1.0",
5 | "categories": ["Documentation"],
6 | "resources": {
7 | "credentialDocumentation": [
8 | {
9 | "url": "https://support.hudu.com/hc/en-us/articles/11422780787735-REST-API"
10 | }
11 | ],
12 | "primaryDocumentation": [
13 | {
14 | "url": "https://github.com/msoukhomlinov/n8n-nodes-hudu"
15 | }
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/HuduAssetCustomFieldDescription.ts:
--------------------------------------------------------------------------------
1 | import { INodeProperties } from 'n8n-workflow';
2 |
3 | export const assetCustomFieldProperties: INodeProperties[] = [
4 | {
5 | displayName: 'Operation',
6 | name: 'operation',
7 | type: 'options',
8 | noDataExpression: true,
9 | displayOptions: {
10 | show: {
11 | resource: ['assetCustomField'],
12 | },
13 | },
14 | options: [
15 | {
16 | name: 'Get',
17 | value: 'get',
18 | description: 'Retrieve the value of a specific custom field on a Hudu asset',
19 | action: 'Get an asset custom field value',
20 | },
21 | {
22 | name: 'Update',
23 | value: 'update',
24 | description: 'Modify the value of a specific custom field on a Hudu asset',
25 | action: 'Update an asset custom field value',
26 | },
27 | ],
28 | default: 'get',
29 | },
30 | {
31 | displayName: 'Asset ID',
32 | name: 'assetId',
33 | type: 'string',
34 | required: true,
35 | displayOptions: {
36 | show: {
37 | resource: ['assetCustomField'],
38 | },
39 | },
40 | default: '',
41 | description: 'The ID of the asset',
42 | },
43 | {
44 | displayName: 'Custom Field Name or ID',
45 | name: 'fieldIdentifier',
46 | type: 'options',
47 | required: true,
48 | displayOptions: {
49 | show: {
50 | resource: ['assetCustomField'],
51 | },
52 | },
53 | typeOptions: {
54 | loadOptionsMethod: 'getAssetCustomFields',
55 | loadOptionsDependsOn: ['assetId'],
56 | },
57 | default: '',
58 | description: 'Choose from the list, or specify an ID using an expression',
59 | },
60 | {
61 | displayName: 'Value',
62 | name: 'value',
63 | type: 'string',
64 | default: '',
65 | description: 'The value to set for this field',
66 | displayOptions: {
67 | show: {
68 | resource: ['assetCustomField'],
69 | operation: ['update'],
70 | },
71 | },
72 | },
73 | ];
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/HuduAssetLinkFieldDescription.ts:
--------------------------------------------------------------------------------
1 | import { INodeProperties } from 'n8n-workflow';
2 |
3 | export const assetLinkFieldProperties: INodeProperties[] = [
4 | {
5 | displayName: 'Operation',
6 | name: 'operation',
7 | type: 'options',
8 | noDataExpression: true,
9 | displayOptions: {
10 | show: {
11 | resource: ['assetLinkField'],
12 | },
13 | },
14 | options: [
15 | {
16 | name: 'Get',
17 | value: 'get',
18 | description: 'Retrieve the linked asset(s) for a specific asset link field on a Hudu asset',
19 | action: 'Get an asset link field value',
20 | },
21 | {
22 | name: 'Update',
23 | value: 'update',
24 | description: 'Modify the linked asset(s) for a specific asset link field on a Hudu asset',
25 | action: 'Update an asset link field value',
26 | },
27 | ],
28 | default: 'get',
29 | },
30 | {
31 | displayName: 'Asset ID',
32 | name: 'assetId',
33 | type: 'number',
34 | required: true,
35 | default: undefined,
36 | placeholder: 'Enter the Asset ID',
37 | description: 'The numeric ID of the asset. You can use an expression to specify this dynamically.',
38 | displayOptions: {
39 | show: {
40 | resource: ['assetLinkField'],
41 | },
42 | },
43 | },
44 | {
45 | displayName: 'Link Field Identifier Name or ID',
46 | name: 'fieldIdentifier',
47 | type: 'options',
48 | required: true,
49 | displayOptions: {
50 | show: {
51 | resource: ['assetLinkField'],
52 | },
53 | },
54 | typeOptions: {
55 | loadOptionsMethod: 'getAssetLinkFields',
56 | loadOptionsDependsOn: ['assetId'],
57 | },
58 | default: '',
59 | description: 'Choose from the list, or specify an ID using an expression',
60 | },
61 | {
62 | displayName: 'Linked Asset ID(s)',
63 | name: 'value',
64 | type: 'string',
65 | default: '',
66 | description: 'A comma-separated string of asset IDs to link (e.g., \'123,456\'). You can use an expression to dynamically set linked assets.',
67 | displayOptions: {
68 | show: {
69 | resource: ['assetLinkField'],
70 | operation: ['update'],
71 | },
72 | },
73 | placeholder: 'e.g., 101,202,303',
74 | },
75 | ];
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/HuduAssetStandardFieldDescription.ts:
--------------------------------------------------------------------------------
1 | import { INodeProperties } from 'n8n-workflow';
2 |
3 | export const assetStandardFieldDescription: INodeProperties[] = [
4 | {
5 | displayName: 'Operation',
6 | name: 'operation',
7 | type: 'options',
8 | noDataExpression: true,
9 | displayOptions: {
10 | show: {
11 | resource: ['assetStandardField'],
12 | },
13 | },
14 | options: [
15 | {
16 | name: 'Get',
17 | value: 'get',
18 | description: 'Retrieve the value of a specific standard field from a Hudu asset',
19 | action: 'Get an asset standard field value',
20 | },
21 | {
22 | name: 'Update',
23 | value: 'update',
24 | description: 'Modify the value of a specific standard field for a Hudu asset',
25 | action: 'Update an asset standard field value',
26 | },
27 | ],
28 | default: 'get',
29 | },
30 | {
31 | displayName: 'Asset Name or ID',
32 | name: 'assetId',
33 | type: 'string',
34 | required: true,
35 | displayOptions: {
36 | show: {
37 | resource: ['assetStandardField'],
38 | },
39 | },
40 | default: '',
41 | description: 'Enter the Asset ID. This is a plain input field.',
42 | },
43 | {
44 | displayName: 'Field Identifier Name or ID',
45 | name: 'fieldIdentifier',
46 | type: 'options',
47 | required: true,
48 | displayOptions: {
49 | show: {
50 | resource: ['assetStandardField'],
51 | },
52 | },
53 | typeOptions: {
54 | loadOptionsMethod: 'getStandardAssetFields',
55 | },
56 | default: '',
57 | description: 'Choose from the list, or specify an ID using an expression',
58 | },
59 | {
60 | displayName: 'Value',
61 | name: 'value',
62 | type: 'string',
63 | default: '',
64 | description: 'The new value to assign to the selected standard field. This is only applicable for the Update operation. Ensure the value is compatible with the field\'s expected data type (e.g., true/false for Archived, a number for numeric fields).',
65 | displayOptions: {
66 | show: {
67 | resource: ['assetStandardField'],
68 | operation: ['update'],
69 | },
70 | },
71 | },
72 | ];
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/activity_logs.description.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 | import { HUDU_API_CONSTANTS, ACTIVITY_LOG_ACTIONS, RESOURCE_TYPES } from '../utils/constants';
3 | import { formatTitleCase } from '../utils/formatters';
4 |
5 | export const activityLogsOperations: INodeProperties[] = [
6 | {
7 | displayName: 'Operation',
8 | name: 'operation',
9 | type: 'options',
10 | noDataExpression: true,
11 | displayOptions: {
12 | show: {
13 | resource: ['activity_logs'],
14 | },
15 | },
16 | options: [
17 | {
18 | name: 'Get Many',
19 | value: 'getAll',
20 | description: 'Retrieve a list of activity logs',
21 | action: 'Get many activity logs',
22 | },
23 | {
24 | name: 'Delete',
25 | value: 'delete',
26 | description: 'Delete activity logs',
27 | action: 'Delete activity logs',
28 | },
29 | ],
30 | default: 'getAll',
31 | },
32 | ];
33 |
34 | export const activityLogsFields: INodeProperties[] = [
35 | // Fields for getAll operation
36 | {
37 | displayName: 'Return All ⚠️',
38 | name: 'returnAll',
39 | type: 'boolean',
40 | default: false,
41 | description:
42 | 'Whether to return all results or only up to a given limit. Use with caution - may return very high number of records.',
43 | displayOptions: {
44 | show: {
45 | resource: ['activity_logs'],
46 | operation: ['getAll'],
47 | },
48 | },
49 | },
50 | {
51 | displayName: 'Limit',
52 | name: 'limit',
53 | type: 'number',
54 | default: HUDU_API_CONSTANTS.PAGE_SIZE,
55 | description: 'Max number of results to return',
56 | typeOptions: {
57 | minValue: 1,
58 | },
59 | displayOptions: {
60 | show: {
61 | resource: ['activity_logs'],
62 | operation: ['getAll'],
63 | returnAll: [false],
64 | },
65 | },
66 | },
67 | {
68 | displayName: 'Filters',
69 | name: 'additionalFields',
70 | type: 'collection',
71 | placeholder: 'Add Filter',
72 | default: {},
73 | displayOptions: {
74 | show: {
75 | resource: ['activity_logs'],
76 | operation: ['getAll'],
77 | },
78 | },
79 | description: 'All filters are combined using AND logic',
80 | options: [
81 | {
82 | displayName: 'Action Message',
83 | name: 'action_message',
84 | type: 'options',
85 | options: ACTIVITY_LOG_ACTIONS.map((action) => ({
86 | name: formatTitleCase(action),
87 | value: action,
88 | })),
89 | default: '',
90 | description: 'Filter by exact action message match',
91 | },
92 | {
93 | displayName: 'Resource ID',
94 | name: 'resource_id',
95 | type: 'number',
96 | default: 0,
97 | description: 'Filter by resource ID (must be used together with Resource Type)',
98 | },
99 | {
100 | displayName: 'Resource Type',
101 | name: 'resource_type',
102 | type: 'options',
103 | options: RESOURCE_TYPES.map((type) => ({
104 | name: type,
105 | value: type,
106 | })),
107 | default: '',
108 | description: 'Filter by resource type (must be used together with Resource ID)',
109 | },
110 | {
111 | displayName: 'Start Date',
112 | name: 'start_date',
113 | type: 'dateTime',
114 | default: '',
115 | description: 'Filter logs starting from this date (ISO 8601 format)',
116 | },
117 | {
118 | displayName: 'User Email',
119 | name: 'user_email',
120 | type: 'string',
121 | default: '',
122 | description: 'Filter by exact user email match',
123 | },
124 | {
125 | displayName: 'User Name or ID',
126 | name: 'user_id',
127 | type: 'options',
128 | typeOptions: {
129 | loadOptionsMethod: 'getUsers',
130 | },
131 | default: '',
132 | description: 'Choose from the list, or specify an ID using an expression',
133 | },
134 | ],
135 | },
136 |
137 | // Fields for delete operation
138 | {
139 | displayName: 'Datetime',
140 | name: 'datetime',
141 | type: 'dateTime',
142 | required: true,
143 | default: '',
144 | description: 'Starting datetime from which logs will be deleted (ISO 8601 format)',
145 | displayOptions: {
146 | show: {
147 | resource: ['activity_logs'],
148 | operation: ['delete'],
149 | },
150 | },
151 | },
152 | {
153 | displayName: 'Additional Fields',
154 | name: 'additionalFields',
155 | type: 'collection',
156 | placeholder: 'Add Field',
157 | default: {},
158 | displayOptions: {
159 | show: {
160 | resource: ['activity_logs'],
161 | operation: ['delete'],
162 | },
163 | },
164 | options: [
165 | {
166 | displayName: 'Delete Unassigned Logs Only',
167 | name: 'delete_unassigned_logs',
168 | type: 'boolean',
169 | default: false,
170 | description: 'Whether to only delete logs where user_id is nil',
171 | },
172 | ],
173 | },
174 | ];
175 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/api_info.description.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 |
3 | export const apiInfoOperations: INodeProperties[] = [
4 | {
5 | displayName: 'Operation',
6 | name: 'operation',
7 | type: 'options',
8 | noDataExpression: true,
9 | displayOptions: {
10 | show: {
11 | resource: ['api_info'],
12 | },
13 | },
14 | options: [
15 | {
16 | name: 'Get',
17 | value: 'get',
18 | description: 'Retrieve API information',
19 | action: 'Get API information',
20 | },
21 | ],
22 | default: 'get',
23 | },
24 | ];
25 |
26 | export const apiInfoFields: INodeProperties[] = [];
27 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/cards.description.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 | import { INTEGRATION_SLUGS } from '../utils/constants';
3 | import { formatTitleCase } from '../utils/formatters';
4 |
5 | export const cardsOperations: INodeProperties[] = [
6 | {
7 | displayName: 'Operation',
8 | name: 'operation',
9 | type: 'options',
10 | noDataExpression: true,
11 | displayOptions: {
12 | show: {
13 | resource: ['cards'],
14 | },
15 | },
16 | options: [
17 | {
18 | name: 'Lookup',
19 | value: 'lookup',
20 | description: 'Lookup cards with external integration details',
21 | action: 'Lookup cards with external integration details',
22 | },
23 | {
24 | name: 'Jump',
25 | value: 'jump',
26 | description: 'Jump to a card by integration ID',
27 | action: 'Jump to a card by integration ID',
28 | },
29 | {
30 | name: 'Jump By Identifier',
31 | value: 'jumpByIdentifier',
32 | description: 'Jump to a card by integration identifier',
33 | action: 'Jump to a card by integration identifier',
34 | },
35 | ],
36 | default: 'lookup',
37 | },
38 | ];
39 |
40 | export const cardsFields: INodeProperties[] = [
41 | // Fields for Lookup operation
42 | {
43 | displayName: 'Integration Slug',
44 | name: 'integration_slug',
45 | type: 'options',
46 | required: true,
47 | default: '',
48 | description: 'The integration type to use (e.g. autotask, cw_manage)',
49 | options: INTEGRATION_SLUGS.map((slug) => ({
50 | name: formatTitleCase(slug),
51 | value: slug,
52 | })),
53 | displayOptions: {
54 | show: {
55 | resource: ['cards'],
56 | operation: ['lookup'],
57 | },
58 | },
59 | },
60 | {
61 | displayName: 'Additional Fields',
62 | name: 'additionalFields',
63 | type: 'collection',
64 | placeholder: 'Add Field',
65 | default: {},
66 | displayOptions: {
67 | show: {
68 | resource: ['cards'],
69 | operation: ['lookup'],
70 | },
71 | },
72 | options: [
73 | {
74 | displayName: 'Integration ID',
75 | name: 'integration_id',
76 | type: 'string',
77 | default: '',
78 | description:
79 | 'ID in the external integration. Must be present unless Integration Identifier is set.',
80 | },
81 | {
82 | displayName: 'Integration Identifier',
83 | name: 'integration_identifier',
84 | type: 'string',
85 | default: '',
86 | description: 'Identifier in the external integration (used if Integration ID is not set)',
87 | },
88 | ],
89 | },
90 |
91 | // Fields for Jump operation
92 | {
93 | displayName: 'Integration Slug',
94 | name: 'integration_slug',
95 | type: 'options',
96 | required: true,
97 | default: '',
98 | description: 'The integration type to use (e.g. autotask, cw_manage)',
99 | options: INTEGRATION_SLUGS.map((slug) => ({
100 | name: formatTitleCase(slug),
101 | value: slug,
102 | })),
103 | displayOptions: {
104 | show: {
105 | resource: ['cards'],
106 | operation: ['jump'],
107 | },
108 | },
109 | },
110 | {
111 | displayName: 'Integration ID',
112 | name: 'integration_id',
113 | type: 'string',
114 | required: true,
115 | default: '',
116 | description: 'The integration ID to use',
117 | displayOptions: {
118 | show: {
119 | resource: ['cards'],
120 | operation: ['jump'],
121 | },
122 | },
123 | },
124 | {
125 | displayName: 'Integration Type',
126 | name: 'integration_type',
127 | type: 'string',
128 | required: true,
129 | default: '',
130 | description: 'The integration type to use',
131 | displayOptions: {
132 | show: {
133 | resource: ['cards'],
134 | operation: ['jump'],
135 | },
136 | },
137 | },
138 | {
139 | displayName: 'Integration Slug',
140 | name: 'integration_slug',
141 | type: 'options',
142 | required: true,
143 | default: '',
144 | description: 'The integration type to use (e.g. autotask, cw_manage)',
145 | options: INTEGRATION_SLUGS.map((slug) => ({
146 | name: formatTitleCase(slug),
147 | value: slug,
148 | })),
149 | displayOptions: {
150 | show: {
151 | resource: ['cards'],
152 | operation: ['jumpByIdentifier'],
153 | },
154 | },
155 | },
156 | {
157 | displayName: 'Additional Fields',
158 | name: 'additionalFields',
159 | type: 'collection',
160 | placeholder: 'Add Field',
161 | default: {},
162 | displayOptions: {
163 | show: {
164 | resource: ['cards'],
165 | operation: ['jump'],
166 | },
167 | },
168 | options: [
169 | {
170 | displayName: 'Integration ID',
171 | name: 'integration_id',
172 | type: 'string',
173 | default: '',
174 | description: 'ID of the entity in the external integration',
175 | },
176 | {
177 | displayName: 'Integration Identifier',
178 | name: 'integration_identifier',
179 | type: 'string',
180 | default: '',
181 | description:
182 | 'Identifier of the entity in the external integration (if integration_id is not set)',
183 | },
184 | ],
185 | },
186 | ];
187 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/expirations.description.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 | import { HUDU_API_CONSTANTS, RESOURCE_TYPES } from '../utils/constants';
3 |
4 | export const expirationsOperations: INodeProperties[] = [
5 | {
6 | displayName: 'Operation',
7 | name: 'operation',
8 | type: 'options',
9 | noDataExpression: true,
10 | displayOptions: {
11 | show: {
12 | resource: ['expirations'],
13 | },
14 | },
15 | options: [
16 | {
17 | name: 'Get Many',
18 | value: 'getAll',
19 | description: 'Get many expirations',
20 | action: 'Get many expirations',
21 | },
22 | ],
23 | default: 'getAll',
24 | },
25 | ];
26 |
27 | export const expirationsFields: INodeProperties[] = [
28 | {
29 | displayName: 'Return All',
30 | name: 'returnAll',
31 | type: 'boolean',
32 | displayOptions: {
33 | show: {
34 | resource: ['expirations'],
35 | operation: ['getAll'],
36 | },
37 | },
38 | default: false,
39 | description: 'Whether to return all results or only up to a given limit',
40 | },
41 | {
42 | displayName: 'Limit',
43 | name: 'limit',
44 | type: 'number',
45 | displayOptions: {
46 | show: {
47 | resource: ['expirations'],
48 | operation: ['getAll'],
49 | returnAll: [false],
50 | },
51 | },
52 | typeOptions: {
53 | minValue: 1,
54 | },
55 | default: HUDU_API_CONSTANTS.PAGE_SIZE,
56 | description: 'Max number of results to return',
57 | },
58 | {
59 | displayName: 'Filters',
60 | name: 'filters',
61 | type: 'collection',
62 | placeholder: 'Add Filter',
63 | default: {},
64 | displayOptions: {
65 | show: {
66 | resource: ['expirations'],
67 | operation: ['getAll'],
68 | },
69 | },
70 | options: [
71 | {
72 | displayName: 'Company Name or ID',
73 | name: 'company_id',
74 | type: 'options',
75 | typeOptions: {
76 | loadOptionsMethod: 'getCompanies',
77 | loadOptionsParameters: {
78 | includeBlank: true,
79 | },
80 | },
81 | default: '',
82 | description: 'Choose from the list, or specify an ID using an expression',
83 | },
84 | {
85 | displayName: 'Expiration Type',
86 | name: 'expiration_type',
87 | type: 'options',
88 | options: [
89 | {
90 | name: 'Article Expiration',
91 | value: 'article_expiration',
92 | },
93 | {
94 | name: 'Asset Field',
95 | value: 'asset_field',
96 | },
97 | {
98 | name: 'Domain',
99 | value: 'domain',
100 | },
101 | {
102 | name: 'SSL Certificate',
103 | value: 'ssl_certificate',
104 | },
105 | {
106 | name: 'Undeclared',
107 | value: 'undeclared',
108 | },
109 | {
110 | name: 'Warranty',
111 | value: 'warranty',
112 | },
113 | ],
114 | default: 'undeclared',
115 | description:
116 | 'Filter expirations by expiration type (undeclared, domain, ssl_certificate, warranty, asset_field, article_expiration)',
117 | },
118 | {
119 | displayName: 'Resource ID',
120 | name: 'resource_id',
121 | type: 'number',
122 | default: undefined,
123 | description: 'Filter logs by resource ID; must be coupled with resource type',
124 | },
125 | {
126 | displayName: 'Resource Type',
127 | name: 'resource_type',
128 | type: 'options',
129 | options: RESOURCE_TYPES.map((type) => ({
130 | name: type,
131 | value: type,
132 | })),
133 | default: '',
134 | description: 'Filter logs by resource type; must be coupled with resource ID',
135 | },
136 | ],
137 | },
138 | ];
139 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './activity_logs.description';
2 | export * from './api_info.description';
3 | export * from './articles.description';
4 | export * from './asset_layouts.description';
5 | export * from './asset_layout_fields.description';
6 | export * from './asset_passwords.description';
7 | export * from './assets.description';
8 | export * from './HuduAssetLinkFieldDescription';
9 | export * from './HuduAssetCustomFieldDescription';
10 | export * from './HuduAssetStandardFieldDescription';
11 | export * from './cards.description';
12 | export * from './companies.description';
13 | export * from './expirations.description';
14 | export * from './folders.description';
15 | export * from './ip_addresses.description';
16 | export * from './list_options.description';
17 | export * from './lists.description';
18 | export * from './magic_dash.description';
19 | export * from './matchers.description';
20 | export * from './networks.description';
21 | export * from './passwordFolders.descriptions';
22 | export * from './procedures.description';
23 | export * from './procedure_tasks.description';
24 | export * from './public_photos.description';
25 | export * from './rack_storages.description';
26 | export * from './rack_storage_items.description';
27 | export * from './relations.description';
28 | export * from './uploads.description';
29 | export * from './users.description';
30 | export * from './websites.description';
31 | export * from './vlans.description';
32 | export * from './vlan_zones.description';
33 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/list_options.description.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 |
3 | export const listOptionsOperations: INodeProperties[] = [
4 | {
5 | displayName: 'Operation',
6 | name: 'operation',
7 | type: 'options',
8 | noDataExpression: true,
9 | displayOptions: {
10 | show: {
11 | resource: ['list_options'],
12 | },
13 | },
14 | options: [
15 | {
16 | name: 'Create',
17 | value: 'create',
18 | description: 'Create a new list item',
19 | action: 'Create a list item',
20 | },
21 | {
22 | name: 'Delete',
23 | value: 'delete',
24 | description: 'Delete a list item',
25 | action: 'Delete a list item',
26 | },
27 | {
28 | name: 'Get',
29 | value: 'get',
30 | description: 'Get list items for a specific list',
31 | action: 'Get list items',
32 | },
33 | {
34 | name: 'Update',
35 | value: 'update',
36 | description: 'Update a list item',
37 | action: 'Update a list item',
38 | },
39 | ],
40 | default: 'get',
41 | },
42 | ];
43 |
44 | export const listOptionsFields: INodeProperties[] = [
45 | // List ID for all operations
46 | {
47 | displayName: 'List Name or ID',
48 | name: 'list_id',
49 | type: 'options',
50 | typeOptions: {
51 | loadOptionsMethod: 'getLists',
52 | },
53 | required: true,
54 | displayOptions: {
55 | show: {
56 | resource: ['list_options'],
57 | operation: ['get', 'create', 'update', 'delete'],
58 | },
59 | },
60 | default: '',
61 | description: 'Choose from the list, or specify an ID using an expression',
62 | },
63 |
64 | // Item ID for update and delete
65 | {
66 | displayName: 'List Item ID',
67 | name: 'item_id',
68 | type: 'string',
69 | required: true,
70 | displayOptions: {
71 | show: {
72 | resource: ['list_options'],
73 | operation: ['update', 'delete'],
74 | },
75 | },
76 | default: '',
77 | description: 'The ID of the list item',
78 | },
79 |
80 | // Name for create and update
81 | {
82 | displayName: 'Name',
83 | name: 'name',
84 | type: 'string',
85 | required: true,
86 | displayOptions: {
87 | show: {
88 | resource: ['list_options'],
89 | operation: ['create', 'update'],
90 | },
91 | },
92 | default: '',
93 | description: 'The name of the list item',
94 | },
95 | ];
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/lists.description.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 |
3 | export const listsOperations: INodeProperties[] = [
4 | {
5 | displayName: 'Operation',
6 | name: 'operation',
7 | type: 'options',
8 | noDataExpression: true,
9 | displayOptions: {
10 | show: {
11 | resource: ['lists'],
12 | },
13 | },
14 | options: [
15 | {
16 | name: 'Create',
17 | value: 'create',
18 | description: 'Create a new list',
19 | action: 'Create a list',
20 | },
21 | {
22 | name: 'Delete',
23 | value: 'delete',
24 | description: 'Delete a list',
25 | action: 'Delete a list',
26 | },
27 | {
28 | name: 'Get',
29 | value: 'get',
30 | description: 'Get a specific list',
31 | action: 'Get a list',
32 | },
33 | {
34 | name: 'Get Many',
35 | value: 'getAll',
36 | description: 'Get many lists',
37 | action: 'Get many lists',
38 | },
39 | {
40 | name: 'Update',
41 | value: 'update',
42 | description: 'Update a list',
43 | action: 'Update a list',
44 | },
45 | ],
46 | default: 'getAll',
47 | },
48 | ];
49 |
50 | export const listsFields: INodeProperties[] = [
51 | // List ID for get, update, delete
52 | {
53 | displayName: 'List Name or ID',
54 | name: 'id',
55 | type: 'options',
56 | typeOptions: {
57 | loadOptionsMethod: 'getLists',
58 | },
59 | required: true,
60 | displayOptions: {
61 | show: {
62 | resource: ['lists'],
63 | operation: ['get', 'update', 'delete'],
64 | },
65 | },
66 | default: '',
67 | description: 'Choose from the list, or specify an ID using an expression',
68 | },
69 | // Name for create
70 | {
71 | displayName: 'Name',
72 | name: 'name',
73 | type: 'string',
74 | required: true,
75 | displayOptions: {
76 | show: {
77 | resource: ['lists'],
78 | operation: ['create'],
79 | },
80 | },
81 | default: '',
82 | description: 'The name of the list',
83 | },
84 | // Name for update
85 | {
86 | displayName: 'Name',
87 | name: 'updateFields.name',
88 | type: 'string',
89 | required: true,
90 | displayOptions: {
91 | show: {
92 | resource: ['lists'],
93 | operation: ['update'],
94 | },
95 | },
96 | default: '',
97 | description: 'The new name of the list',
98 | },
99 | // Filters for getAll
100 | {
101 | displayName: 'Filters',
102 | name: 'filters',
103 | type: 'collection',
104 | placeholder: 'Add Filter',
105 | default: {},
106 | displayOptions: {
107 | show: {
108 | resource: ['lists'],
109 | operation: ['getAll'],
110 | },
111 | },
112 | options: [
113 | {
114 | displayName: 'Query',
115 | name: 'query',
116 | type: 'string',
117 | default: '',
118 | description: 'Search lists by name (partial match)',
119 | },
120 | {
121 | displayName: 'Name',
122 | name: 'name',
123 | type: 'string',
124 | default: '',
125 | description: 'Filter by exact list name',
126 | },
127 | ],
128 | description: 'Filters to apply when retrieving lists',
129 | },
130 | // Return all toggle for getAll
131 | {
132 | displayName: 'Return All',
133 | name: 'returnAll',
134 | type: 'boolean',
135 | displayOptions: {
136 | show: {
137 | resource: ['lists'],
138 | operation: ['getAll'],
139 | },
140 | },
141 | default: false,
142 | description: 'Whether to return all results or limit by a set number',
143 | },
144 | // Limit for getAll
145 | {
146 | displayName: 'Limit',
147 | name: 'limit',
148 | type: 'number',
149 | typeOptions: {
150 | minValue: 1,
151 | },
152 | displayOptions: {
153 | show: {
154 | resource: ['lists'],
155 | operation: ['getAll'],
156 | returnAll: [false],
157 | },
158 | },
159 | default: 50,
160 | description: 'Max number of results to return',
161 | },
162 | ];
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/passwordFolders.descriptions.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 |
3 | export const passwordFoldersOperations: INodeProperties[] = [
4 | {
5 | displayName: 'Operation',
6 | name: 'operation',
7 | type: 'options',
8 | noDataExpression: true,
9 | displayOptions: {
10 | show: {
11 | resource: ['password_folders'],
12 | },
13 | },
14 | options: [
15 | {
16 | name: 'Get',
17 | value: 'get',
18 | description: 'Get a password folder by ID',
19 | action: 'Get a password folder',
20 | },
21 | {
22 | name: 'Get Many',
23 | value: 'getAll',
24 | description: 'Get many password folders',
25 | action: 'Get many password folders',
26 | },
27 | ],
28 | default: 'getAll',
29 | },
30 | ];
31 |
32 | export const passwordFoldersFields: INodeProperties[] = [
33 | {
34 | displayName: 'Password Folder ID',
35 | name: 'id',
36 | type: 'number',
37 | required: true,
38 | displayOptions: {
39 | show: {
40 | resource: ['password_folders'],
41 | operation: ['get'],
42 | },
43 | },
44 | default: 0,
45 | description: 'The ID of the password folder to retrieve',
46 | },
47 | {
48 | displayName: 'Return All',
49 | name: 'returnAll',
50 | type: 'boolean',
51 | displayOptions: {
52 | show: {
53 | resource: ['password_folders'],
54 | operation: ['getAll'],
55 | },
56 | },
57 | default: false,
58 | description: 'Whether to return all results or only up to a given limit',
59 | },
60 | {
61 | displayName: 'Limit',
62 | name: 'limit',
63 | type: 'number',
64 | displayOptions: {
65 | show: {
66 | resource: ['password_folders'],
67 | operation: ['getAll'],
68 | returnAll: [false],
69 | },
70 | },
71 | typeOptions: {
72 | minValue: 1,
73 | },
74 | default: 50,
75 | description: 'Max number of results to return',
76 | },
77 | {
78 | displayName: 'Filters',
79 | name: 'filters',
80 | type: 'collection',
81 | placeholder: 'Add Filter',
82 | default: {},
83 | displayOptions: {
84 | show: {
85 | resource: ['password_folders'],
86 | operation: ['getAll'],
87 | },
88 | },
89 | options: [
90 | {
91 | displayName: 'Company Name or ID',
92 | name: 'company_id',
93 | type: 'options',
94 | typeOptions: {
95 | loadOptionsMethod: 'getCompanies',
96 | loadOptionsParameters: {
97 | includeBlank: true,
98 | },
99 | },
100 | default: '',
101 | description: 'The company to associate with the password folder. Choose from the list, or specify an ID using an expression.',
102 | },
103 | {
104 | displayName: 'Name',
105 | name: 'name',
106 | type: 'string',
107 | default: '',
108 | description: 'Filter folders by name',
109 | },
110 | {
111 | displayName: 'Search',
112 | name: 'search',
113 | type: 'string',
114 | default: '',
115 | description: 'Filter by search query',
116 | },
117 | ],
118 | },
119 | ];
120 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/resources.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 |
3 | export const resourceOptions = [
4 | {
5 | name: 'Activity Log',
6 | value: 'activity_logs',
7 | },
8 | {
9 | name: 'API Info',
10 | value: 'api_info',
11 | },
12 | {
13 | name: 'Article',
14 | value: 'articles',
15 | },
16 | {
17 | name: 'Asset',
18 | value: 'assets',
19 | },
20 | {
21 | name: 'Asset Field Custom',
22 | value: 'assetCustomField',
23 | },
24 | {
25 | name: 'Asset Field Link',
26 | value: 'assetLinkField',
27 | },
28 | {
29 | name: 'Asset Field Standard',
30 | value: 'assetStandardField',
31 | },
32 | {
33 | name: 'Asset Layout',
34 | value: 'asset_layouts',
35 | },
36 | {
37 | name: 'Asset Layout Field',
38 | value: 'asset_layout_fields',
39 | },
40 | {
41 | name: 'Asset Password',
42 | value: 'asset_passwords',
43 | },
44 | {
45 | name: 'Card',
46 | value: 'cards',
47 | },
48 | {
49 | name: 'Company',
50 | value: 'companies',
51 | },
52 | {
53 | name: 'Expiration',
54 | value: 'expirations',
55 | },
56 | {
57 | name: 'Folder',
58 | value: 'folders',
59 | },
60 | {
61 | name: 'IP Address',
62 | value: 'ipAddresses',
63 | },
64 | {
65 | name: 'List',
66 | value: 'lists',
67 | },
68 | {
69 | name: 'List Options',
70 | value: 'list_options',
71 | },
72 | {
73 | name: 'Magic Dash',
74 | value: 'magic_dash',
75 | },
76 | {
77 | name: 'Matcher',
78 | value: 'matchers',
79 | },
80 | {
81 | name: 'Network',
82 | value: 'networks',
83 | },
84 | {
85 | name: 'Password Folder',
86 | value: 'password_folders',
87 | },
88 | {
89 | name: 'Procedure',
90 | value: 'procedures',
91 | },
92 | {
93 | name: 'Procedure Task',
94 | value: 'procedure_tasks',
95 | },
96 | {
97 | name: 'Public Photo',
98 | value: 'public_photos',
99 | },
100 | {
101 | name: 'Rack Storage',
102 | value: 'rack_storages',
103 | },
104 | {
105 | name: 'Rack Storage Item',
106 | value: 'rack_storage_items',
107 | },
108 | {
109 | name: 'Relation',
110 | value: 'relations',
111 | },
112 | {
113 | name: 'Upload',
114 | value: 'uploads',
115 | },
116 | {
117 | name: 'User',
118 | value: 'users',
119 | },
120 | {
121 | name: 'Website',
122 | value: 'websites',
123 | },
124 | {
125 | name: 'VLAN',
126 | value: 'vlans',
127 | },
128 | {
129 | name: 'VLAN Zone',
130 | value: 'vlan_zones',
131 | },
132 | ];
133 |
134 | export const resourceProperty: INodeProperties = {
135 | displayName: 'Resource',
136 | name: 'resource',
137 | type: 'options',
138 | noDataExpression: true,
139 | options: resourceOptions,
140 | default: 'companies',
141 | };
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/uploads.description.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 | import { HUDU_API_CONSTANTS } from '../utils/constants';
3 |
4 | export const uploadsOperations: INodeProperties[] = [
5 | {
6 | displayName: 'Operation',
7 | name: 'operation',
8 | type: 'options',
9 | noDataExpression: true,
10 | displayOptions: {
11 | show: {
12 | resource: ['uploads'],
13 | },
14 | },
15 | options: [
16 | {
17 | name: 'Get Many',
18 | value: 'getAll',
19 | description: 'Get many uploads',
20 | action: 'Get many uploads',
21 | },
22 | {
23 | name: 'Get',
24 | value: 'get',
25 | description: 'Get a specific upload',
26 | action: 'Get an upload',
27 | },
28 | {
29 | name: 'Delete',
30 | value: 'delete',
31 | description: 'Delete an upload',
32 | action: 'Delete an upload',
33 | },
34 | ],
35 | default: 'getAll',
36 | },
37 | ];
38 |
39 | export const uploadsFields: INodeProperties[] = [
40 | // ----------------------------------
41 | // getAll
42 | // ----------------------------------
43 | {
44 | displayName: 'Return All',
45 | name: 'returnAll',
46 | type: 'boolean',
47 | displayOptions: {
48 | show: {
49 | resource: ['uploads'],
50 | operation: ['getAll'],
51 | },
52 | },
53 | default: false,
54 | description: 'Whether to return all results or only up to a given limit',
55 | },
56 | {
57 | displayName: 'Limit',
58 | name: 'limit',
59 | type: 'number',
60 | displayOptions: {
61 | show: {
62 | resource: ['uploads'],
63 | operation: ['getAll'],
64 | returnAll: [false],
65 | },
66 | },
67 | typeOptions: {
68 | minValue: 1,
69 | },
70 | default: HUDU_API_CONSTANTS.PAGE_SIZE,
71 | description: 'Max number of results to return',
72 | },
73 |
74 | // ----------------------------------
75 | // get/delete
76 | // ----------------------------------
77 | {
78 | displayName: 'Upload ID',
79 | name: 'id',
80 | type: 'number',
81 | displayOptions: {
82 | show: {
83 | resource: ['uploads'],
84 | operation: ['get', 'delete'],
85 | },
86 | },
87 | default: 0,
88 | required: true,
89 | description: 'ID of the upload',
90 | },
91 | ];
92 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/descriptions/users.description.ts:
--------------------------------------------------------------------------------
1 | import type { INodeProperties } from 'n8n-workflow';
2 | import { HUDU_API_CONSTANTS } from '../utils/constants';
3 |
4 | export const userOperations: INodeProperties[] = [
5 | {
6 | displayName: 'Operation',
7 | name: 'operation',
8 | type: 'options',
9 | noDataExpression: true,
10 | displayOptions: {
11 | show: {
12 | resource: ['users'],
13 | },
14 | },
15 | options: [
16 | {
17 | name: 'Get Many',
18 | value: 'getAll',
19 | description: 'Get many users',
20 | action: 'Get many users',
21 | },
22 | {
23 | name: 'Get',
24 | value: 'get',
25 | description: 'Get a user by ID',
26 | action: 'Get a user',
27 | },
28 | ],
29 | default: 'getAll',
30 | },
31 | ];
32 |
33 | export const userFields: INodeProperties[] = [
34 | // Return All option for GetAll operation
35 | {
36 | displayName: 'Return All',
37 | name: 'returnAll',
38 | type: 'boolean',
39 | displayOptions: {
40 | show: {
41 | resource: ['users'],
42 | operation: ['getAll'],
43 | },
44 | },
45 | default: false,
46 | description: 'Whether to return all results or only up to a given limit',
47 | },
48 | {
49 | displayName: 'Limit',
50 | name: 'limit',
51 | type: 'number',
52 | displayOptions: {
53 | show: {
54 | resource: ['users'],
55 | operation: ['getAll'],
56 | returnAll: [false],
57 | },
58 | },
59 | typeOptions: {
60 | minValue: 1,
61 | },
62 | default: HUDU_API_CONSTANTS.PAGE_SIZE,
63 | description: 'Max number of results to return',
64 | },
65 | // ID field for Get operation
66 | {
67 | displayName: 'User ID',
68 | name: 'id',
69 | type: 'number',
70 | required: true,
71 | displayOptions: {
72 | show: {
73 | resource: ['users'],
74 | operation: ['get'],
75 | },
76 | },
77 | default: 0,
78 | description: 'ID of the user to retrieve',
79 | },
80 | // Filters for GetAll operation
81 | {
82 | displayName: 'Filters',
83 | name: 'filters',
84 | type: 'collection',
85 | placeholder: 'Add Filter',
86 | default: {},
87 | displayOptions: {
88 | show: {
89 | resource: ['users'],
90 | operation: ['getAll'],
91 | },
92 | },
93 | options: [
94 | {
95 | displayName: 'Archived',
96 | name: 'archived',
97 | type: 'boolean',
98 | default: false,
99 | description: 'Whether the user is archived',
100 | },
101 | {
102 | displayName: 'Email',
103 | name: 'email',
104 | type: 'string',
105 | placeholder: 'name@email.com',
106 | default: '',
107 | description: 'Filter users by email address',
108 | },
109 | {
110 | displayName: 'First Name',
111 | name: 'first_name',
112 | type: 'string',
113 | default: '',
114 | description: 'Filter users by first name',
115 | },
116 | {
117 | displayName: 'Last Name',
118 | name: 'last_name',
119 | type: 'string',
120 | default: '',
121 | description: 'Filter users by last name',
122 | },
123 | {
124 | displayName: 'Portal Member Company Name or ID',
125 | name: 'portal_member_company_id',
126 | type: 'options',
127 | typeOptions: {
128 | loadOptionsMethod: 'getCompanies',
129 | loadOptionsParameters: {
130 | includeBlank: true,
131 | },
132 | },
133 | default: '',
134 | description: 'The company to associate with the portal member. Choose from the list, or specify an ID using an expression.',
135 | },
136 | {
137 | displayName: 'Search',
138 | name: 'search',
139 | type: 'string',
140 | default: '',
141 | description: 'Search across first name and last name',
142 | },
143 | {
144 | displayName: 'Security Level',
145 | name: 'security_level',
146 | type: 'options',
147 | options: [
148 | {
149 | name: 'Admin',
150 | value: 'admin',
151 | },
152 | {
153 | name: 'Author',
154 | value: 'author',
155 | },
156 | {
157 | name: 'Editor',
158 | value: 'editor',
159 | },
160 | {
161 | name: 'Portal Admin',
162 | value: 'portal_admin',
163 | },
164 | {
165 | name: 'Portal Member',
166 | value: 'portal_member',
167 | },
168 | {
169 | name: 'Spectator',
170 | value: 'spectator',
171 | },
172 | {
173 | name: 'Super Admin',
174 | value: 'super_admin',
175 | },
176 | ],
177 | default: 'super_admin',
178 | description: 'Filter users by security level',
179 | },
180 | ],
181 | },
182 | ];
183 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/docs/FILTERS.md:
--------------------------------------------------------------------------------
1 | # Post-Processing Filters
2 |
3 | This document describes the post-processing filter system implemented for the Hudu node resources.
4 |
5 | ## Overview
6 |
7 | The filter system allows for flexible filtering of resource results after they are retrieved from the API. These filters are applied client-side after the API response is received.
8 |
9 | ## Resource-Specific Filters
10 |
11 | ### Articles
12 | - `folder_id`: Filter by folder ID (exact match)
13 |
14 | ### Folders
15 | - `parent_folder_id`: Filter by parent folder ID (exact match)
16 | - `childFolder`: Filter by whether the folder is a child ('yes'/'no')
17 |
18 | ### Relations
19 | - `fromable_type`: Filter by the type of the origin entity (case-insensitive match)
20 | - `fromable_id`: Filter by the ID of the origin entity (exact match)
21 | - `toable_type`: Filter by the type of the destination entity (case-insensitive match)
22 | - `toable_id`: Filter by the ID of the destination entity (exact match)
23 | - `is_inverse`: Filter by whether the relation is inverse (boolean match)
24 |
25 | ## Implementation Details
26 |
27 | Post-processing filters are implemented using the `FilterMapping` type and applied using the `applyPostFilters` utility function. These filters are executed client-side after retrieving data from the API.
28 |
29 | ## Best Practices
30 |
31 | 1. Use post-processing filters sparingly as they may impact performance
32 | 2. Consider case sensitivity requirements for string matches
33 | 3. Use appropriate type conversion for numeric comparisons
34 | 4. Document any special filter behavior in the code
35 |
36 | ## Note
37 |
38 | Other filters mentioned in the resource descriptions are applied directly through the API and are not part of the post-processing filter system.
--------------------------------------------------------------------------------
/src/nodes/Hudu/hudu.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
27 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/asset_layouts/getAssetLayoutFieldValues.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
2 | import { handleGetOperation } from '../../utils/operations';
3 | import type { IAssetLayoutFieldEntity } from '../../resources/asset_layout_fields/asset_layout_fields.types';
4 |
5 | export async function getAssetLayoutFieldValues(
6 | this: ILoadOptionsFunctions,
7 | ): Promise {
8 | try {
9 | const layoutId = this.getNodeParameter('asset_layout_id') as string;
10 | const fieldId = this.getNodeParameter('field_id') as string;
11 |
12 | if (!layoutId || !fieldId) {
13 | return [];
14 | }
15 |
16 | // Fetch the layout details
17 | const layout = await handleGetOperation.call(this, '/asset_layouts', layoutId);
18 | const fields = (layout as { asset_layout: { fields: IAssetLayoutFieldEntity[] } }).asset_layout.fields || [];
19 | const field = fields.find((f) => f.id === Number.parseInt(fieldId, 10));
20 |
21 | if (!field) {
22 | return [];
23 | }
24 |
25 | // Return the current values of the field as options
26 | return [
27 | {
28 | name: 'Expiration',
29 | value: 'expiration',
30 | description: field.expiration.toString(),
31 | },
32 | {
33 | name: 'Field Type',
34 | value: 'field_type',
35 | description: field.field_type,
36 | },
37 | {
38 | name: 'Hint',
39 | value: 'hint',
40 | description: field.hint,
41 | },
42 | {
43 | name: 'Label',
44 | value: 'label',
45 | description: field.label,
46 | },
47 | {
48 | name: 'Options',
49 | value: 'options',
50 | description: field.options,
51 | },
52 | {
53 | name: 'Position',
54 | value: 'position',
55 | description: field.position.toString(),
56 | },
57 | {
58 | name: 'Required',
59 | value: 'required',
60 | description: field.required?.toString() || 'false',
61 | },
62 | {
63 | name: 'Show in List',
64 | value: 'show_in_list',
65 | description: field.show_in_list.toString(),
66 | },
67 | ];
68 | } catch (error) {
69 | return [];
70 | }
71 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/asset_layouts/getAssetLayouts.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions, IDataObject, INodePropertyOptions } from 'n8n-workflow';
2 | import { handleListing } from '../../utils';
3 |
4 | interface AssetLayoutOption {
5 | name: string;
6 | value: number;
7 | archived: boolean;
8 | }
9 |
10 | interface HuduAssetLayout extends IDataObject {
11 | id: number;
12 | name: string;
13 | active: boolean;
14 | }
15 |
16 | /**
17 | * Get all asset layouts from Hudu API
18 | */
19 | export async function getAssetLayouts(this: ILoadOptionsFunctions): Promise {
20 | try {
21 | let includeBlank = false;
22 | try {
23 | includeBlank = (this.getNodeParameter('parameters.includeBlank', false) as boolean) || false;
24 | } catch {
25 | // Parameter doesn't exist, ignore
26 | }
27 |
28 | const assetLayouts = (await handleListing.call(
29 | this,
30 | 'GET',
31 | '/asset_layouts',
32 | 'asset_layouts',
33 | {},
34 | {},
35 | true,
36 | 0,
37 | )) as HuduAssetLayout[];
38 |
39 | const mappedLayouts = assetLayouts
40 | .map((layout) => ({
41 | name: `${layout.name} (${layout.id})${!layout.active ? ' - Archived' : ''}`,
42 | value: layout.id,
43 | archived: !layout.active,
44 | }))
45 | .sort((a: AssetLayoutOption, b: AssetLayoutOption) => {
46 | if (a.archived !== b.archived) {
47 | return a.archived ? 1 : -1;
48 | }
49 | return a.name.localeCompare(b.name);
50 | });
51 |
52 | if (includeBlank) {
53 | return [
54 | {
55 | name: '- No Asset Layout -',
56 | value: '',
57 | },
58 | ...mappedLayouts.map(({ name, value }) => ({ name, value })),
59 | ];
60 | }
61 |
62 | return mappedLayouts.map(({ name, value }) => ({ name, value }));
63 | } catch (error) {
64 | return [];
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/asset_layouts/getAssetLinkFields.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions } from 'n8n-workflow';
2 | import { huduApiRequest } from '../../utils/requestUtils';
3 | import { debugLog } from '../../utils/debugConfig';
4 | import { handleGetOperation } from '../../utils/operations';
5 | import type { IAssetLayoutFieldEntity } from '../../resources/asset_layout_fields/asset_layout_fields.types';
6 | import { ASSET_LAYOUT_FIELD_TYPES } from '../../utils/constants';
7 | import type { IAssetResponse } from '../../resources/assets/assets.types';
8 |
9 | /**
10 | * Loads asset link fields (fields of type AssetTag) for a given asset.
11 | * Used as a loadOptionsMethod for the assetLinkField resource dropdown in n8n.
12 | *
13 | * @param this - n8n ILoadOptionsFunctions context
14 | * @returns Array of options for asset link fields
15 | */
16 | export async function getAssetLinkFields(
17 | this: ILoadOptionsFunctions,
18 | ): Promise<{ name: string; value: string; description?: string }[]> {
19 | const assetId = this.getCurrentNodeParameter('assetId') as string | number;
20 | if (!assetId || isNaN(Number(assetId)) || Number(assetId) <= 0) {
21 | return [{ name: 'Please Enter a Valid Asset ID First', value: '' }];
22 | }
23 |
24 | try {
25 | debugLog('[OPTION_LOADING] Fetching asset link fields for asset ID:', assetId);
26 |
27 | // Fetch the asset to get its layout ID
28 | const assetResponse = await huduApiRequest.call(this, 'GET', '/assets', {}, { id: assetId }) as IAssetResponse;
29 | if (!assetResponse || !assetResponse.assets || !assetResponse.assets.length) {
30 | debugLog('[OPTION_LOADING] No asset found with ID:', assetId);
31 | return [{ name: `Asset with ID ${assetId} Not Found`, value: '' }];
32 | }
33 |
34 | const asset = assetResponse.assets[0];
35 | const layoutId = asset.asset_layout_id;
36 | if (!layoutId) {
37 | debugLog('[OPTION_LOADING] Asset has no layout ID');
38 | return [{ name: 'Asset Has No Associated Layout', value: '' }];
39 | }
40 |
41 | // Fetch the layout
42 | debugLog('[OPTION_LOADING] Fetching layout details for ID:', layoutId);
43 | const layoutResponse = await handleGetOperation.call(this, '/asset_layouts', layoutId);
44 | const layout = (layoutResponse as { asset_layout: { fields: IAssetLayoutFieldEntity[] } }).asset_layout;
45 | if (!layout || !Array.isArray(layout.fields)) {
46 | debugLog('[OPTION_LOADING] Layout not found or has no fields');
47 | return [{ name: 'Layout Not Found or Has No Fields', value: '' }];
48 | }
49 |
50 | // Filter for ASSET_TAG fields
51 | const assetTagFields = layout.fields.filter((field: IAssetLayoutFieldEntity) => field.field_type === ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG && !field.is_destroyed);
52 | debugLog('[OPTION_LOADING] Found asset link fields:', { count: assetTagFields.length });
53 |
54 | // Return message if no asset link fields are found
55 | if (assetTagFields.length === 0) {
56 | debugLog('[OPTION_LOADING] No asset link fields found in layout');
57 | return [{ name: 'No Asset Link Fields Found for This Asset', value: 'NO_LINK_FIELDS_FOUND' }];
58 | }
59 |
60 | // Return as options
61 | return assetTagFields.map(field => ({
62 | name: field.label,
63 | value: field.label,
64 | description: field.hint || undefined,
65 | }));
66 | } catch (error) {
67 | debugLog('[OPTION_LOADING] Error in getAssetLinkFields:', error);
68 | return [{ name: `Error: ${(error as Error).message}`, value: '' }];
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/asset_layouts/getCustomFieldsLayoutFields.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
2 | import { NodeOperationError } from 'n8n-workflow';
3 | import { handleGetOperation } from '../../utils/operations';
4 | import type { IAssetLayoutFieldEntity } from '../../resources/asset_layout_fields/asset_layout_fields.types';
5 | import { debugLog } from '../../utils/debugConfig';
6 | import { ASSET_LAYOUT_FIELD_TYPES } from '../../utils/constants';
7 |
8 | interface IAssetLayout {
9 | active: boolean;
10 | name: string;
11 | fields: IAssetLayoutFieldEntity[];
12 | }
13 |
14 | interface IAssetLayoutResponse {
15 | asset_layout: IAssetLayout;
16 | }
17 |
18 | export async function getCustomFieldsLayoutFields(
19 | this: ILoadOptionsFunctions,
20 | ): Promise {
21 | try {
22 | debugLog('[ASSET_OPTIONS] Starting getCustomFieldsLayoutFields');
23 |
24 | const layoutId = this.getCurrentNodeParameter('getall_asset_layout_id') as string;
25 |
26 | debugLog('[ASSET_OPTIONS] Context:', {
27 | node: this.getNode().name,
28 | layoutId,
29 | });
30 |
31 | if (!layoutId) {
32 | debugLog('[ASSET_OPTIONS] No layout ID provided, returning empty options');
33 | return [];
34 | }
35 |
36 | // Fetch the layout details
37 | debugLog('[ASSET_OPTIONS] Fetching layout details for ID:', layoutId);
38 | const response = await handleGetOperation.call(this, '/asset_layouts', layoutId);
39 | const layout = response as unknown as IAssetLayoutResponse;
40 | debugLog('[ASSET_OPTIONS] Layout response:', layout);
41 |
42 | // Check if layout exists
43 | if (!layout || !layout.asset_layout) {
44 | debugLog('[ASSET_OPTIONS] Layout not found or inaccessible');
45 | throw new NodeOperationError(this.getNode(), 'Asset layout not found or inaccessible');
46 | }
47 |
48 | const layoutData = layout.asset_layout;
49 | const fields = layoutData.fields || [];
50 | debugLog('[ASSET_OPTIONS] Found fields:', fields);
51 |
52 | // If no fields are found
53 | if (fields.length === 0) {
54 | debugLog('[ASSET_OPTIONS] No fields found in layout, returning empty options');
55 | return [];
56 | }
57 |
58 | // Map fields to options format
59 | debugLog('[ASSET_OPTIONS] Starting field mapping process');
60 | const options = fields
61 | .filter((field: IAssetLayoutFieldEntity) => !field.is_destroyed && field.field_type === ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG)
62 | .map((field: IAssetLayoutFieldEntity) => ({
63 | name: `${field.label} (${field.field_type})${!layoutData.active ? ' [Archived Layout]' : ''}`,
64 | value: field.id.toString(),
65 | description: field.hint || undefined,
66 | }))
67 | .sort((a, b) => a.name.localeCompare(b.name));
68 |
69 | debugLog('[ASSET_OPTIONS] Final mapped options:', options);
70 | return options;
71 | } catch (error) {
72 | debugLog('[ASSET_OPTIONS] Error in getCustomFieldsLayoutFields:', error);
73 | if (error instanceof NodeOperationError) {
74 | throw error;
75 | }
76 | throw new NodeOperationError(this.getNode(), `Failed to load custom fields layout fields: ${(error as Error).message}`);
77 | }
78 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/asset_layouts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getAssetLayouts';
2 | export * from './getAssetLayoutFields';
3 | export * from './getCustomFieldsLayoutFields';
4 | export * from './getAssetLayoutFieldValues';
5 | export * from './getAssetLinkFields';
6 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/assets/getAssetCustomFields.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions, INodePropertyOptions, IDataObject } from 'n8n-workflow';
2 | import { NodeOperationError } from 'n8n-workflow';
3 | import { huduApiRequest } from '../../utils/requestUtils';
4 | import { debugLog } from '../../utils/debugConfig';
5 |
6 | export async function getAssetCustomFields(
7 | this: ILoadOptionsFunctions,
8 | ): Promise {
9 | const assetId = this.getCurrentNodeParameter('assetId') as string | number;
10 | if (!assetId || isNaN(Number(assetId)) || Number(assetId) <= 0) {
11 | return [{ name: 'Please Enter a Valid Asset ID First', value: '' }];
12 | }
13 |
14 | try {
15 | debugLog('[OPTION_LOADING] Fetching custom fields for asset ID:', assetId);
16 | const response = await huduApiRequest.call(this, 'GET', '/assets', {}, { id: assetId }) as IDataObject;
17 | const assets = Array.isArray(response.assets) ? response.assets : [];
18 |
19 | if (!assets.length) {
20 | debugLog('[OPTION_LOADING] No asset found with ID:', assetId);
21 | return [{ name: `Asset with ID ${assetId} not found`, value: '' }];
22 | }
23 |
24 | const fields = Array.isArray(assets[0].fields) ? assets[0].fields : [];
25 | debugLog('[OPTION_LOADING] Found fields for asset:', { count: fields.length });
26 | debugLog('[OPTION_LOADING] Field labels and types (raw):', fields.map((f: any) => ({ label: f.label, type: f.field_type })));
27 |
28 | // Fetch asset layout to get field types
29 | const assetLayoutId = assets[0].asset_layout_id;
30 | let layoutFields: any[] = [];
31 | if (assetLayoutId) {
32 | try {
33 | const layoutResponse = await huduApiRequest.call(this, 'GET', `/asset_layouts/${assetLayoutId}`) as IDataObject;
34 | layoutFields = Array.isArray((layoutResponse.asset_layout as IDataObject)?.fields)
35 | ? (layoutResponse.asset_layout as IDataObject).fields as IDataObject[]
36 | : [];
37 | debugLog('[OPTION_LOADING] Asset layout fields retrieved', { count: layoutFields.length });
38 | } catch (layoutError) {
39 | debugLog('[OPTION_LOADING] Error fetching asset layout:', layoutError);
40 | }
41 | }
42 |
43 | // Build picklist from layout fields, not asset fields
44 | if (!layoutFields.length) {
45 | debugLog('[OPTION_LOADING] No layout fields found, cannot build picklist');
46 | return [{ name: 'No Custom Fields Found for This Asset', value: 'NO_CUSTOM_FIELDS_FOUND' }];
47 | }
48 |
49 | // Map asset field values by label for quick lookup
50 | const assetFieldValueMap = new Map();
51 | for (const field of fields) {
52 | if (field.label) {
53 | assetFieldValueMap.set(field.label, field.value);
54 | }
55 | }
56 |
57 | // Build picklist from layout fields
58 | const customFields = layoutFields
59 | .filter((layoutField: any) => layoutField.field_type !== 'AssetTag')
60 | .map((layoutField: any) => {
61 | const value = assetFieldValueMap.get(layoutField.label);
62 | return {
63 | name: layoutField.label,
64 | value: layoutField.label,
65 | description: layoutField.field_type + (value !== undefined && value !== null ? ` (Current value: ${value})` : ''),
66 | };
67 | });
68 | debugLog('[OPTION_LOADING] Custom fields from layout:', customFields);
69 |
70 | if (customFields.length === 0) {
71 | debugLog('[OPTION_LOADING] No custom fields found for asset/layout');
72 | return [{ name: 'No Custom Fields Found for This Asset', value: 'NO_CUSTOM_FIELDS_FOUND' }];
73 | }
74 |
75 | return customFields;
76 | } catch (error) {
77 | debugLog('[OPTION_LOADING] Error in getAssetCustomFields:', error);
78 | throw new NodeOperationError(this.getNode(), `Failed to load custom fields for asset: ${(error as Error).message}`);
79 | }
80 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/assets/getAssets.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions, IDataObject } from 'n8n-workflow';
2 | import { handleListing } from '../../utils';
3 |
4 | interface AssetOption {
5 | name: string;
6 | value: string | number;
7 | }
8 |
9 | interface HuduAsset extends IDataObject {
10 | id: number;
11 | name: string;
12 | asset_type: string;
13 | company_name: string;
14 | slug: string;
15 | archived: boolean;
16 | company_id: number;
17 | asset_layout_id: number;
18 | }
19 |
20 | /**
21 | * Format asset display name based on filtering context
22 | */
23 | function formatAssetName(
24 | asset: HuduAsset,
25 | showAssetType: boolean,
26 | showCompanyName: boolean,
27 | ): string {
28 | const parts = [asset.name];
29 |
30 | if (showAssetType) {
31 | parts.push(`(${asset.asset_type})`);
32 | }
33 |
34 | if (showCompanyName) {
35 | parts.push(`(${asset.company_name})`);
36 | }
37 |
38 | return parts.join(' ');
39 | }
40 |
41 | /**
42 | * Get assets from Hudu API with optional filtering and grouping
43 | */
44 | export async function getAssets(this: ILoadOptionsFunctions) {
45 | try {
46 | const companyId = this.getNodeParameter('company_id', 0) as string;
47 | const assetLayoutId = this.getNodeParameter('asset_layout_id', 0) as string;
48 |
49 | // Build query parameters
50 | const qs: IDataObject = {
51 | archived: false,
52 | };
53 |
54 | if (companyId) {
55 | qs.company_id = Number.parseInt(companyId, 10);
56 | }
57 |
58 | if (assetLayoutId) {
59 | qs.asset_layout_id = Number.parseInt(assetLayoutId, 10);
60 | }
61 |
62 | const assets = (await handleListing.call(
63 | this,
64 | 'GET',
65 | '/assets',
66 | 'assets',
67 | qs,
68 | {},
69 | true,
70 | 0,
71 | )) as HuduAsset[];
72 |
73 | if (!Array.isArray(assets)) {
74 | return [];
75 | }
76 |
77 | // Determine display options based on filters
78 | const showAssetType = !assetLayoutId;
79 | const showCompanyName = !companyId;
80 |
81 | // Map assets to options with appropriate grouping
82 | if (showAssetType) {
83 | // Group by asset_type (primary grouping)
84 | const groupedByType = assets.reduce((acc, asset) => {
85 | const group = asset.asset_type;
86 | if (!acc[group]) {
87 | acc[group] = [];
88 | }
89 | acc[group].push({
90 | name: formatAssetName(asset, false, showCompanyName),
91 | value: JSON.stringify({
92 | id: asset.id,
93 | url: `/a/${asset.slug}`,
94 | name: asset.name
95 | }),
96 | });
97 | return acc;
98 | }, {} as { [key: string]: AssetOption[] });
99 |
100 | // Sort groups and items within groups
101 | return Object.entries(groupedByType)
102 | .sort(([a], [b]) => a.localeCompare(b))
103 | .flatMap(([group, options]) => [
104 | { name: group, value: group },
105 | ...options.sort((a, b) => a.name.localeCompare(b.name)),
106 | ]);
107 | }
108 |
109 | // Group by company if not grouping by asset_type
110 | const groupedByCompany = assets.reduce((acc, asset) => {
111 | const group = asset.company_name;
112 | if (!acc[group]) {
113 | acc[group] = [];
114 | }
115 | acc[group].push({
116 | name: formatAssetName(asset, showAssetType, false),
117 | value: JSON.stringify({
118 | id: asset.id,
119 | url: `/a/${asset.slug}`,
120 | name: asset.name
121 | }),
122 | });
123 | return acc;
124 | }, {} as { [key: string]: AssetOption[] });
125 |
126 | // Sort groups and items within groups
127 | return Object.entries(groupedByCompany)
128 | .sort(([a], [b]) => a.localeCompare(b))
129 | .flatMap(([group, options]) => [
130 | { name: group, value: group },
131 | ...options.sort((a, b) => a.name.localeCompare(b.name)),
132 | ]);
133 | } catch (error) {
134 | return [];
135 | }
136 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/assets/getAssetsForCompany.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
2 | import { debugLog } from '../../utils/debugConfig';
3 | import { handleListing } from '../../utils';
4 |
5 | export async function getAssetsForCompany(this: ILoadOptionsFunctions): Promise {
6 | const companyId = this.getCurrentNodeParameter('companyIdForAssetLookup') as string | number;
7 |
8 | debugLog('[OPTION_LOADING] getAssetsForCompany called', { companyId });
9 |
10 | if (!companyId || companyId === '') {
11 | debugLog('[OPTION_LOADING] No companyId provided for getAssetsForCompany');
12 | return [{ name: 'Select a Company first...', value: '' }];
13 | }
14 |
15 | try {
16 | const endpoint = `/companies/${companyId}/assets`;
17 | debugLog('[OPTION_LOADING] Fetching assets for company using handleListing', { endpoint });
18 |
19 | // Use handleListing to properly handle pagination
20 | const assets = await handleListing.call(
21 | this,
22 | 'GET',
23 | endpoint,
24 | 'assets', // resourceName
25 | {}, // body
26 | {}, // query parameters
27 | true, // returnAll
28 | 0, // limit
29 | );
30 |
31 | debugLog('[OPTION_LOADING] Received assets for company', { count: assets.length });
32 |
33 | if (Array.isArray(assets) && assets.length > 0) {
34 | const mappedAssets = assets.map((asset: any) => ({
35 | name: asset.name || `ID: ${asset.id}`,
36 | value: asset.id,
37 | }));
38 |
39 | debugLog('[OPTION_LOADING] Mapped assets for options', { count: mappedAssets.length });
40 |
41 | if (mappedAssets.length === 0) {
42 | return [{ name: 'No Assets Found for This Company', value: '' }];
43 | }
44 |
45 | return mappedAssets;
46 | } else {
47 | debugLog('[OPTION_LOADING] No assets found or invalid format', { assets });
48 | return [{ name: 'No Assets Found for This Company', value: '' }];
49 | }
50 | } catch (error) {
51 | debugLog('[ERROR] Error loading assets for company', { error });
52 | return [
53 | {
54 | name: `Error: ${error instanceof Error ? error.message : 'Failed to load assets'}`,
55 | value: 'ERROR_LOADING_ASSETS',
56 | },
57 | ];
58 | }
59 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/assets/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getAssets';
2 | export * from './getAssetCustomFields';
3 | export * from './getLinkableAssets';
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/companies/getCompanies.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions, IDataObject } from 'n8n-workflow';
2 | import { handleListing } from '../../utils';
3 |
4 | interface CompanyOption {
5 | name: string;
6 | value: number;
7 | }
8 |
9 | interface HuduCompany extends IDataObject {
10 | id: number;
11 | name: string;
12 | }
13 |
14 | /**
15 | * Get all companies from Hudu API
16 | */
17 | export async function getCompanies(this: ILoadOptionsFunctions) {
18 | try {
19 | const includeBlank = this.getNodeParameter('includeBlank', true) as boolean;
20 |
21 | const companies = (await handleListing.call(
22 | this,
23 | 'GET',
24 | '/companies',
25 | 'companies',
26 | {},
27 | {},
28 | true,
29 | 0,
30 | )) as HuduCompany[];
31 |
32 | if (!Array.isArray(companies)) {
33 | return [];
34 | }
35 |
36 | const mappedCompanies = companies
37 | .map((company) => ({
38 | name: `${company.name} (${company.id})`,
39 | value: company.id,
40 | }))
41 | .sort((a: CompanyOption, b: CompanyOption) => a.name.localeCompare(b.name));
42 |
43 | if (includeBlank) {
44 | return [
45 | {
46 | name: '- No Company -',
47 | value: '',
48 | },
49 | ...mappedCompanies,
50 | ];
51 | }
52 |
53 | return mappedCompanies;
54 | } catch (error) {
55 | return [];
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/companies/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getCompanies';
2 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/index.ts:
--------------------------------------------------------------------------------
1 | export * from './users';
2 | export * from './companies';
3 | export * from './asset_layouts';
4 | export * from './lists';
5 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/lists/getLists.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions, IDataObject, INodePropertyOptions } from 'n8n-workflow';
2 | import { handleListing } from '../../utils';
3 |
4 | interface IList extends IDataObject {
5 | id: number;
6 | name: string;
7 | list_items: {
8 | id: number;
9 | name: string;
10 | }[];
11 | }
12 |
13 | /**
14 | * Get all lists from Hudu API
15 | */
16 | export async function getLists(this: ILoadOptionsFunctions): Promise {
17 | try {
18 | // Use debug to see what's happening
19 | const lists = (await handleListing.call(
20 | this,
21 | 'GET',
22 | '/lists',
23 | 'lists',
24 | {},
25 | {},
26 | true,
27 | 0,
28 | )) as IList[];
29 |
30 | if (!Array.isArray(lists)) {
31 | console.log('getLists returned non-array:', lists);
32 | return [];
33 | }
34 |
35 | console.log(`getLists found ${lists.length} lists`);
36 |
37 | const mappedLists = lists
38 | .map((list) => {
39 | // Ensure ID is a number and convert to string for the value
40 | const id = typeof list.id === 'number' ? list.id : parseInt(String(list.id), 10);
41 | return {
42 | name: `${list.name as string} (${id})`,
43 | value: id,
44 | };
45 | })
46 | .sort((a, b) => a.name.localeCompare(b.name));
47 |
48 | // Log the first few mapped lists
49 | console.log('First few mapped lists:', mappedLists.slice(0, 3));
50 |
51 | return mappedLists as INodePropertyOptions[];
52 | } catch (error) {
53 | console.error('Error in getLists:', error);
54 | return [];
55 | }
56 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/lists/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getLists';
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/users/getUsers.ts:
--------------------------------------------------------------------------------
1 | import type { ILoadOptionsFunctions, IDataObject } from 'n8n-workflow';
2 | import { handleListing } from '../../utils';
3 |
4 | interface UserOption {
5 | name: string;
6 | value: number;
7 | archived: boolean;
8 | }
9 |
10 | interface HuduUser extends IDataObject {
11 | id: number;
12 | first_name: string;
13 | last_name: string;
14 | archived: boolean;
15 | }
16 |
17 | /**
18 | * Get all users from Hudu API
19 | */
20 | export async function getUsers(this: ILoadOptionsFunctions) {
21 | try {
22 | const includeBlank = this.getNodeParameter('includeBlank', true) as boolean;
23 | const users = (await handleListing.call(
24 | this,
25 | 'GET',
26 | '/users',
27 | 'users',
28 | {},
29 | {},
30 | true, // returnAll
31 | 0, // no limit
32 | )) as HuduUser[];
33 |
34 | if (!Array.isArray(users)) {
35 | return [];
36 | }
37 |
38 | const mappedUsers = users
39 | .map((user) => ({
40 | name: `${user.first_name} ${user.last_name} (${user.id})${
41 | user.archived ? ' - Archived' : ''
42 | }`,
43 | value: user.id,
44 | archived: user.archived,
45 | }))
46 | .sort((a: UserOption, b: UserOption) => {
47 | if (a.archived !== b.archived) {
48 | return a.archived ? 1 : -1;
49 | }
50 | return a.name.localeCompare(b.name);
51 | });
52 |
53 | if (includeBlank) {
54 | return [
55 | {
56 | name: '- No User -',
57 | value: '',
58 | },
59 | ...mappedUsers.map(({ name, value }) => ({ name, value })),
60 | ];
61 | }
62 |
63 | return mappedUsers.map(({ name, value }) => ({ name, value }));
64 | } catch (error) {
65 | return [];
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/optionLoaders/users/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getUsers';
2 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/activity_logs/activity_logs.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
2 | import { handleGetAllOperation } from '../../utils/operations';
3 | import type { ActivityLogsOperation } from './activity_logs.types';
4 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
5 |
6 | export async function handleActivityLogsOperation(
7 | this: IExecuteFunctions,
8 | operation: ActivityLogsOperation,
9 | i: number,
10 | ): Promise {
11 | const resourceEndpoint = '/activity_logs';
12 |
13 | switch (operation) {
14 | case 'getAll': {
15 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
16 | const filters = this.getNodeParameter('additionalFields', i) as IDataObject;
17 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
18 |
19 | return await handleGetAllOperation.call(
20 | this,
21 | resourceEndpoint,
22 | 'activity_logs',
23 | filters,
24 | returnAll,
25 | limit,
26 | );
27 | }
28 | }
29 |
30 | throw new Error(`Unsupported operation ${operation}`);
31 | }
32 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/activity_logs/activity_logs.types.ts:
--------------------------------------------------------------------------------
1 | export type ActivityLogsOperation = 'getAll' | 'delete';
2 |
3 | export interface IActivityLogsGetAllParams {
4 | /** n8n UI parameter for maximum number of records to return */
5 | limit?: number;
6 | /** Specify the current page of results to retrieve */
7 | page?: number;
8 | /** Specify the number of results to return per page */
9 | page_size?: number;
10 | /** Filter logs by a specific user ID */
11 | user_id?: number;
12 | /** Filter logs by a user's email address */
13 | user_email?: string;
14 | /** Filter logs by resource ID (matches response's record_id); must be used in conjunction with resource_type */
15 | resource_id?: number;
16 | /** Filter logs by resource type (matches response's record_type - Asset, AssetPassword, Company, Article, etc.); must be used in conjunction with resource_id */
17 | resource_type?: string;
18 | /** Filter logs by the action performed (matches response's action field) */
19 | action_message?: string;
20 | /** Filter logs starting from a specific date; must be in ISO 8601 format */
21 | start_date?: string;
22 | }
23 |
24 | export interface IActivityLogsDeleteParams {
25 | /** Starting datetime from which logs will be deleted (ISO 8601 format) */
26 | datetime: string;
27 | /** Whether to only delete logs where user_id is nil */
28 | delete_unassigned_logs?: boolean;
29 | }
30 |
31 | /**
32 | * Activity Log response from the API
33 | * Note: When filtering, resource_id maps to record_id and resource_type maps to record_type in the response
34 | */
35 | export interface IActivityLog {
36 | /** Unique identifier for the activity log */
37 | id: number;
38 | /** Additional details about the activity, may contain JSON data */
39 | details: Record;
40 | /** The action performed (maps to action_message in query) */
41 | action: string;
42 | /** IP address from which the action was performed */
43 | ip_address: string;
44 | /** Unique token for the activity */
45 | token: string;
46 | /** ID of the user who performed the action */
47 | user_id: number | null;
48 | /** Email of the user who performed the action */
49 | user_email: string | null;
50 | /** Original name of the record if it was changed */
51 | original_record_name: string | null;
52 | /** Full name of the user who performed the action */
53 | user_name: string;
54 | /** Short name of the user who performed the action */
55 | user_short_name: string;
56 | /** Type of record affected (maps to resource_type in query) */
57 | record_type: string | null;
58 | /** Name of the company associated with the record */
59 | company_name: string | null;
60 | /** URL to the company in Hudu */
61 | record_company_url: string | null;
62 | /** URL to the user's profile in Hudu */
63 | record_user_url: string;
64 | /** Type of application where the action occurred */
65 | app_type: string;
66 | /** ID of the record affected (maps to resource_id in query) */
67 | record_id: number | null;
68 | /** Name of the record affected */
69 | record_name: string | null;
70 | /** URL to the record in Hudu */
71 | record_url: string | null;
72 | /** ISO 8601 timestamp of when the activity occurred */
73 | created_at: string;
74 | /** Human-readable formatted datetime */
75 | formatted_datetime: string;
76 | /** User's initials */
77 | user_initials: string;
78 | /** URL to view the activity in Hudu */
79 | url: string | null;
80 | /** User agent string of the browser/client */
81 | agent_string: string | null;
82 | /** Device type used */
83 | device: string;
84 | /** Operating system used */
85 | os: string;
86 | }
87 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/api_info/api_info.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { handleSystemInfoOperation } from '../../utils/operations';
3 | import type { ApiInfoOperation } from './api_info.types';
4 |
5 | export async function handleApiInfoOperation(
6 | this: IExecuteFunctions,
7 | operation: ApiInfoOperation,
8 | ): Promise {
9 | let responseData: IDataObject | IDataObject[];
10 |
11 | switch (operation) {
12 | case 'get': {
13 | responseData = await handleSystemInfoOperation.call(
14 | this,
15 | '/api_info',
16 | );
17 | break;
18 | }
19 | }
20 |
21 | return responseData;
22 | }
23 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/api_info/api_info.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IApiInfo extends IDataObject {
4 | version: string;
5 | date: string;
6 | }
7 |
8 | export type ApiInfoOperation = 'get';
9 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/articles/articles.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 | import type { FilterMapping } from '../../utils/index';
3 |
4 | export interface IArticles extends IDataObject {
5 | id: number; // The unique ID of the article
6 | name: string; // The name of the article
7 | content: string; // The HTML content of the article
8 | slug: string; // The url slug of the article
9 | draft: boolean; // A flag that signifies if the article is a draft
10 | url: string; // The url of the article
11 | object_type: string; // The object type is Article
12 | folder_id?: number; // The unique folder ID where the article lives
13 | enable_sharing: boolean; // A flag that signifies if the article is shareable
14 | share_url?: string; // A url for shareable articles
15 | company_id?: number; // The unique company ID for non-global articles
16 | created_at?: string; // The date and time when the article was created
17 | updated_at?: string; // The date and time when the article was last updated
18 | public_photos?: string[]; // A list of public photos
19 | }
20 |
21 | export interface IArticlesResponse extends IDataObject {
22 | article: IArticles;
23 | }
24 |
25 | export type ArticlesOperation =
26 | | 'archive'
27 | | 'create'
28 | | 'delete'
29 | | 'get'
30 | | 'getAll'
31 | | 'getVersionHistory'
32 | | 'unarchive'
33 | | 'update';
34 |
35 | export interface IArticlePostProcessFilters extends IDataObject {
36 | folder_id?: number;
37 | }
38 |
39 | // Define how each filter should be applied
40 | export const articleFilterMapping: FilterMapping = {
41 | folder_id: (item: IDataObject, value: unknown) => {
42 | return Number(item.folder_id) === Number(value);
43 | },
44 | };
45 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/assetCustomField/assetCustomField.types.ts:
--------------------------------------------------------------------------------
1 | export type AssetCustomFieldOperation = 'get' | 'update';
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/assetLinkField/assetLinkField.types.ts:
--------------------------------------------------------------------------------
1 | export type AssetLinkFieldOperation = 'get' | 'update';
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/assetStandardField/assetStandardField.types.ts:
--------------------------------------------------------------------------------
1 | export type AssetStandardFieldOperation = 'get' | 'update';
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/asset_layout_fields/asset_layout_fields.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IAssetLayoutFieldEntity extends IDataObject {
4 | id: number;
5 | label: string;
6 | show_in_list: boolean;
7 | field_type: string;
8 | required?: boolean;
9 | hint: string;
10 | min?: number;
11 | max?: number;
12 | linkable_id?: number;
13 | expiration: boolean;
14 | options: string;
15 | position: number;
16 | is_destroyed: boolean;
17 | }
18 |
19 | export interface IAssetLayoutFieldResponse extends IDataObject {
20 | asset_layout_field: IAssetLayoutFieldEntity;
21 | }
22 |
23 | export type AssetLayoutFieldOperation = 'get' | 'getAll' | 'update' | 'delete' | 'create' | 'reorder';
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/asset_layouts/asset_layouts.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { processDateRange } from '../../utils/index';
3 | import {
4 | handleGetAllOperation,
5 | handleGetOperation,
6 | handleCreateOperation,
7 | handleUpdateOperation,
8 | } from '../../utils/operations';
9 | import type { AssetLayoutOperation } from './asset_layouts.types';
10 | import type { DateRangePreset } from '../../utils/dateUtils';
11 | import { debugLog } from '../../utils/debugConfig';
12 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
13 |
14 | export async function handleAssetLayoutOperation(
15 | this: IExecuteFunctions,
16 | operation: AssetLayoutOperation,
17 | i: number,
18 | ): Promise {
19 | debugLog(`[OPERATION_${operation.toUpperCase()}] Starting asset layout operation`, { operation, index: i });
20 | const resourceEndpoint = '/asset_layouts';
21 | let responseData: IDataObject | IDataObject[] = {};
22 |
23 | switch (operation) {
24 | case 'getAll': {
25 | debugLog('[OPERATION_GET_ALL] Processing get all asset layouts operation');
26 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
27 | const filters = this.getNodeParameter('filters', i) as IDataObject;
28 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
29 | const qs: IDataObject = {
30 | ...filters,
31 | };
32 |
33 | debugLog('[RESOURCE_PARAMS] Get all asset layouts parameters', { returnAll, filters, limit });
34 |
35 | // Process date range if present
36 | if (filters.updated_at) {
37 | const updatedAtFilter = filters.updated_at as IDataObject;
38 |
39 | if (updatedAtFilter.range) {
40 | const rangeObj = updatedAtFilter.range as IDataObject;
41 |
42 | filters.updated_at = processDateRange({
43 | range: {
44 | mode: rangeObj.mode as 'exact' | 'range' | 'preset',
45 | exact: rangeObj.exact as string,
46 | start: rangeObj.start as string,
47 | end: rangeObj.end as string,
48 | preset: rangeObj.preset as DateRangePreset,
49 | },
50 | });
51 | qs.updated_at = filters.updated_at;
52 | debugLog('[UTIL_DATE_PROCESSING] Processed date range', qs.updated_at);
53 | }
54 | }
55 |
56 | debugLog('[API_REQUEST] Getting all asset layouts', { qs, returnAll, limit });
57 |
58 | responseData = await handleGetAllOperation.call(
59 | this,
60 | resourceEndpoint,
61 | 'asset_layouts',
62 | qs,
63 | returnAll,
64 | limit,
65 | );
66 |
67 | debugLog('[API_RESPONSE] Get all asset layouts response', responseData);
68 | break;
69 | }
70 |
71 | case 'get': {
72 | debugLog('[OPERATION_GET] Processing get asset layout operation');
73 | const id = this.getNodeParameter('id', i) as string;
74 |
75 | debugLog('[API_REQUEST] Getting asset layout', { id });
76 |
77 | responseData = await handleGetOperation.call(this, resourceEndpoint, id);
78 |
79 | debugLog('[API_RESPONSE] Get asset layout response', responseData);
80 | break;
81 | }
82 |
83 | case 'create': {
84 | debugLog('[OPERATION_CREATE] Processing create asset layout operation');
85 | const name = this.getNodeParameter('name', i) as string;
86 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
87 | const fields = this.getNodeParameter('fields', i) as IDataObject;
88 |
89 | debugLog('[RESOURCE_PARAMS] Create asset layout parameters', { name, additionalFields, fields });
90 |
91 | const body: IDataObject = {
92 | name,
93 | ...additionalFields,
94 | };
95 |
96 | if (fields && (fields as IDataObject).field) {
97 | body.fields = (fields as IDataObject).field;
98 | }
99 |
100 | debugLog('[API_REQUEST] Creating asset layout with body', body);
101 |
102 | responseData = await handleCreateOperation.call(this, resourceEndpoint, { asset_layout: body });
103 |
104 | debugLog('[API_RESPONSE] Create asset layout response', responseData);
105 | break;
106 | }
107 |
108 | case 'update': {
109 | debugLog('[OPERATION_UPDATE] Processing update asset layout operation');
110 | const id = this.getNodeParameter('id', i) as string;
111 | const name = this.getNodeParameter('name', i) as string;
112 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
113 | const fields = this.getNodeParameter('fields', i) as IDataObject;
114 |
115 | debugLog('[RESOURCE_PARAMS] Update asset layout parameters', { id, name, additionalFields, fields });
116 |
117 | const body: IDataObject = {
118 | asset_layout: {
119 | name,
120 | ...additionalFields,
121 | },
122 | };
123 |
124 | if (fields && (fields as IDataObject).field) {
125 | (body.asset_layout as IDataObject).fields = (fields as IDataObject).field;
126 | }
127 |
128 | debugLog('[API_REQUEST] Updating asset layout with body', body);
129 |
130 | responseData = await handleUpdateOperation.call(this, resourceEndpoint, id, body);
131 |
132 | debugLog('[API_RESPONSE] Update asset layout response', responseData);
133 | break;
134 | }
135 | }
136 |
137 | debugLog(`[OPERATION_${operation.toUpperCase()}] Operation completed`, responseData);
138 | return responseData;
139 | }
140 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/asset_layouts/asset_layouts.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IAssetLayoutField extends IDataObject {
4 | id: number; // The unique identifier for the field
5 | label: string; // The label of the field
6 | show_in_list: boolean; // Whether to show this field in the list view
7 | field_type: string; // The type of the field
8 | required?: boolean; // Whether the field is required (Can be null)
9 | hint: string; // Help text for the field
10 | min?: number; // Minimum value for the field (Can be null)
11 | max?: number; // Maximum value for the field (Can be null)
12 | linkable_id: number; // The ID of the linked item
13 | expiration: boolean; // Whether this field has expiration tracking
14 | options: string; // Field options
15 | position: number; // The position of the field in the layout
16 | is_destroyed: boolean; // Whether this field has been destroyed
17 | }
18 |
19 | export interface IAssetLayout extends IDataObject {
20 | id: number; // The unique identifier for the layout
21 | slug: string; // The URL slug for the layout
22 | name: string; // The name of the layout
23 | icon: string; // The icon for the layout
24 | color: string; // The color for the layout
25 | icon_color: string; // The icon color for the layout
26 | sidebar_folder_id?: number; // The folder ID for the sidebar (Can be null)
27 | active: boolean; // Whether the layout is active
28 | include_passwords: boolean; // Whether to include passwords section
29 | include_photos: boolean; // Whether to include photos section
30 | include_comments: boolean; // Whether to include comments section
31 | include_files: boolean; // Whether to include files section
32 | created_at: string; // When the layout was created (format: date-time)
33 | updated_at: string; // When the layout was last updated (format: date-time)
34 | fields: IAssetLayoutField[]; // The fields defined for this layout
35 | }
36 |
37 | export interface IAssetLayoutResponse extends IDataObject {
38 | asset_layout: IAssetLayout;
39 | }
40 |
41 | export type AssetLayoutOperation = 'create' | 'get' | 'getAll' | 'update';
42 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/asset_passwords/asset_passwords.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IAssetPassword extends IDataObject {
4 | id: number; // Unique identifier of the asset password
5 | passwordable_type: string; // Type of the related object for the password (e.g., 'Asset', 'Website')
6 | company_id: number; // Identifier of the company to which the password belongs
7 | name: string; // Name of the password
8 | username: string; // Username associated with the password
9 | slug: string; // URL-friendly identifier for the password
10 | description: string; // Description or notes related to the password
11 | password: string; // The actual password string
12 | otp_secret: string; // Secret key for one-time passwords (OTP), if used
13 | url: string; // URL related to the password, if applicable
14 | passwordable_id?: number; // ID of the related object (e.g., 'Website') for the password. Can be null.
15 | password_type?: string; // Type or category of the password. Can be null.
16 | created_at?: string; // Timestamp when the password was created (format: date-time)
17 | updated_at?: string; // Timestamp when the password was last updated (format: date-time)
18 | password_folder_id?: number; // ID of the folder in which the password is stored, if any. Can be null.
19 | password_folder_name?: string; // Name of the folder in which the password is stored, if any. Can be null.
20 | login_url?: string; // URL for the login page associated with the password. Can be null.
21 | in_portal?: boolean; // Whether the password is accessible in the portal
22 | }
23 |
24 | export interface IAssetPasswordResponse extends IDataObject {
25 | asset_password: IAssetPassword;
26 | }
27 |
28 | export type AssetPasswordOperation =
29 | | 'create'
30 | | 'get'
31 | | 'getAll'
32 | | 'update'
33 | | 'delete'
34 | | 'archive'
35 | | 'unarchive';
36 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/assets/assets.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | interface IAssetField extends IDataObject {
4 | id: number; // The identifier of the field
5 | value: string; // The value stored in the field
6 | label: string; // The label of the field
7 | position: number; // The position of the field in the asset's layout
8 | }
9 |
10 | export interface IAsset extends IDataObject {
11 | id: number; // The unique identifier of the asset
12 | company_id: number; // The identifier of the company to which the asset belongs
13 | asset_layout_id: number; // The identifier of the asset layout associated with the asset
14 | slug: string; // The URL slug used to identify the asset
15 | name: string; // The name of the asset
16 | primary_serial?: string; // The primary serial number of the asset (if available)
17 | primary_mail?: string; // The primary email associated with the asset (if available)
18 | primary_model?: string; // The primary model of the asset (if available)
19 | primary_manufacturer?: string; // The primary manufacturer of the asset (if available)
20 | company_name: string; // The name of the company to which the asset belongs
21 | object_type: string; // The type of object the asset represents
22 | asset_type: string; // The category of the asset
23 | archived: boolean; // Indicates whether the asset is archived or not
24 | url: string; // The URL of the asset page
25 | created_at?: string; // The date and time when the asset was created (format: date-time)
26 | updated_at?: string; // The date and time when the asset was last updated (format: date-time)
27 | fields: IAssetField[]; // A list of fields associated with the asset
28 | cards?: IDataObject[]; // A list of cards associated with the asset (if available)
29 | }
30 |
31 | export interface IAssetResponse extends IDataObject {
32 | assets: IAsset[];
33 | }
34 |
35 | export type AssetsOperations =
36 | | 'create'
37 | | 'get'
38 | | 'getAll'
39 | | 'update'
40 | | 'delete'
41 | | 'archive'
42 | | 'unarchive'
43 | | 'moveLayout';
44 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/cards/cards.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { handleCardLookupOperation, handleCardJumpOperation } from '../../utils/operations/cards';
3 | import type { CardsOperation } from './cards.types';
4 |
5 | export async function handleCardOperation(
6 | this: IExecuteFunctions,
7 | operation: CardsOperation,
8 | i: number,
9 | ): Promise {
10 | switch (operation) {
11 | case 'lookup': {
12 | const integrationSlug = this.getNodeParameter('integration_slug', i) as string;
13 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
14 | return handleCardLookupOperation.call(this, integrationSlug, additionalFields);
15 | }
16 |
17 | case 'jump': {
18 | const integrationType = this.getNodeParameter('integration_type', i) as string;
19 | const integrationSlug = this.getNodeParameter('integration_slug', i) as string;
20 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
21 | return handleCardJumpOperation.call(this, integrationType, integrationSlug, additionalFields);
22 | }
23 |
24 | default:
25 | return {};
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/cards/cards.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 | import type { INTEGRATION_SLUGS } from '../../utils/constants';
3 |
4 | export interface ICard extends IDataObject {
5 | integration_slug: string;
6 | integration_id?: string;
7 | integration_identifier?: string;
8 | integration_type?: string;
9 | }
10 |
11 | export interface ICardResponse extends IDataObject {
12 | integrator_cards: ICard[];
13 | }
14 |
15 | export type CardsOperation =
16 | | 'lookup' // List first as it's the default operation
17 | | 'jump';
18 |
19 | export interface ICardsJumpParams {
20 | integration_id?: string;
21 | integration_identifier?: string;
22 | integration_slug: (typeof INTEGRATION_SLUGS)[number];
23 | integration_type: string;
24 | }
25 |
26 | export interface ICardsLookupParams {
27 | integration_id?: string;
28 | integration_identifier?: string;
29 | integration_slug: (typeof INTEGRATION_SLUGS)[number];
30 | integration_type: string;
31 | }
32 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/companies/companies.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface ICompanyIntegration extends IDataObject {
4 | id: number; // The unique identifier of the integration
5 | integrator_id: number; // The unique identifier of the integrator
6 | integrator_name: string; // The name of the integrator
7 | sync_id: number; // The unique identifier for the synchronization
8 | identifier?: string; // The identifier of the integration (can be null)
9 | name: string; // The name of the integration
10 | potential_company_id?: number; // The unique identifier of the potential company associated with the integration (can be null)
11 | company_id: number; // The unique identifier of the company associated with the integration
12 | company_name: string; // The name of the company associated with the integration
13 | }
14 |
15 | export interface ICompany extends IDataObject {
16 | id: number; // The unique identifier of the company
17 | slug: string; // The URL-friendly identifier of the company
18 | name: string; // The full name of the company
19 | nickname?: string; // The nickname or short name of the company (can be null)
20 | address_line_1: string; // The first line of the company's address
21 | address_line_2?: string; // The second line of the company's address (can be null)
22 | city: string; // The city where the company is located
23 | state: string; // The state or province where the company is located
24 | zip: string; // The zip or postal code of the company's location
25 | country_name?: string; // The name of the country where the company is located (can be null)
26 | phone_number: string; // The company's phone number
27 | company_type?: string; // The type of the company (can be null)
28 | parent_company_id?: number; // The unique identifier of the parent company (can be null)
29 | parent_company_name?: string; // The name of the parent company (can be null)
30 | fax_number: string; // The company's fax number
31 | website: string; // The company's website URL
32 | notes?: string; // Additional notes or information about the company (can be null)
33 | archived: boolean; // Indicates if the company has been archived
34 | object_type: string; // The type of the object, in this case, "Company"
35 | id_number: string; // A custom set identification number
36 | url: string; // The URL path of the company within the application
37 | full_url: string; // The full URL of the company within the application
38 | passwords_url: string; // The URL for the company's passwords within the application
39 | knowledge_base_url: string; // The URL for the company's knowledge base within the application
40 | created_at?: string; // The date and time when the company was created
41 | updated_at?: string; // The date and time when the company was last updated
42 | integrations?: ICompanyIntegration[]; // A list of integrations associated with the company
43 | }
44 |
45 | export interface ICompanyResponse extends IDataObject {
46 | companies: ICompany[];
47 | }
48 |
49 | export type CompaniesOperations =
50 | | 'create'
51 | | 'get'
52 | | 'getAll'
53 | | 'update'
54 | | 'delete'
55 | | 'archive'
56 | | 'unarchive'
57 | | 'jump';
58 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/expirations/expirations.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { handleGetAllOperation } from '../../utils/operations';
3 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
4 | import type { ExpirationsOperations } from './expirations.types';
5 |
6 | export async function handleExpirationOperation(
7 | this: IExecuteFunctions,
8 | operation: ExpirationsOperations,
9 | i: number,
10 | ): Promise {
11 | const resourceEndpoint = '/expirations';
12 | let responseData: IDataObject | IDataObject[];
13 |
14 | switch (operation) {
15 | case 'getAll': {
16 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
17 | const filters = this.getNodeParameter('filters', i) as IDataObject;
18 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
19 |
20 | responseData = await handleGetAllOperation.call(
21 | this,
22 | resourceEndpoint,
23 | 'expirations',
24 | filters,
25 | returnAll,
26 | limit,
27 | );
28 | break;
29 | }
30 | }
31 |
32 | return responseData;
33 | }
34 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/expirations/expirations.types.ts:
--------------------------------------------------------------------------------
1 | import { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IExpiration extends IDataObject {
4 | // Required fields
5 | id: number; // The unique identifier for the expiration
6 | date: string; // The expiration date (format: date)
7 | expirationable_type: string; // The type of object associated with the expiration (e.g., Website)
8 | expirationable_id: number; // The ID of the object associated with the expiration
9 | account_id: number; // The account ID associated with the expiration
10 | company_id: number; // The company ID associated with the expiration
11 | expiration_type: string; // The type of expiration (e.g., domain)
12 |
13 | // Optional fields
14 | asset_layout_field_id?: number; // The asset layout field ID associated with the expiration (if any)
15 | sync_id?: number; // The sync ID associated with the expiration (if any)
16 | discarded_at?: string; // The timestamp when the expiration was discarded (if any) (format: date-time)
17 | created_at?: string; // The timestamp when the expiration was created (format: date-time)
18 | updated_at?: string; // The timestamp when the expiration was last updated (format: date-time)
19 | asset_field_id?: number; // The asset field ID associated with the expiration (if any)
20 | }
21 |
22 | export interface IExpirationResponse extends IDataObject {
23 | expirations: IExpiration[];
24 | }
25 |
26 | export type ExpirationsOperations = 'getAll';
27 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/folders/folders.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import {
3 | handleGetAllOperation,
4 | handleGetOperation,
5 | handleCreateOperation,
6 | handleUpdateOperation,
7 | handleDeleteOperation,
8 | } from '../../utils/operations';
9 | import type { FilterMapping } from '../../utils/types';
10 | import type { FolderOperation, IFolderPostProcessFilters, IFolder, IFolderPathResponse } from './folders.types';
11 | import { folderFilterMapping } from './folders.types';
12 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
13 |
14 | export async function handleFolderOperation(
15 | this: IExecuteFunctions,
16 | operation: FolderOperation,
17 | i: number,
18 | ): Promise {
19 | const resourceEndpoint = '/folders';
20 | let responseData: IDataObject | IDataObject[];
21 |
22 | switch (operation) {
23 | case 'getAll': {
24 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
25 | const filters = this.getNodeParameter('filters', i) as IDataObject;
26 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
27 |
28 | // Extract post-processing filters and API filters separately
29 | const postProcessFilters: IFolderPostProcessFilters = {};
30 | const apiFilters: IDataObject = {};
31 |
32 | // Copy only API filters
33 | for (const [key, value] of Object.entries(filters)) {
34 | if (key === 'parent_folder_id') {
35 | postProcessFilters.parent_folder_id = value as number;
36 | } else if (key === 'childFolder') {
37 | postProcessFilters.childFolder = value as 'yes' | 'no' | '';
38 | } else {
39 | apiFilters[key] = value;
40 | }
41 | }
42 |
43 | responseData = await handleGetAllOperation.call(
44 | this,
45 | resourceEndpoint,
46 | 'folders',
47 | apiFilters,
48 | returnAll,
49 | limit,
50 | postProcessFilters as IDataObject,
51 | folderFilterMapping as FilterMapping,
52 | );
53 | break;
54 | }
55 |
56 | case 'get': {
57 | const folderId = this.getNodeParameter('folderId', i) as string;
58 | responseData = await handleGetOperation.call(this, resourceEndpoint, folderId);
59 | break;
60 | }
61 |
62 | case 'create': {
63 | const name = this.getNodeParameter('name', i) as string;
64 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
65 |
66 | const body: IDataObject = {
67 | name,
68 | ...additionalFields,
69 | };
70 |
71 | // Ensure proper field names
72 | if (additionalFields.parent_folder_id === '') {
73 | body.parent_folder_id = null;
74 | }
75 |
76 | if (additionalFields.company_id === '') {
77 | body.company_id = null;
78 | }
79 |
80 | responseData = await handleCreateOperation.call(this, resourceEndpoint, { folder: body });
81 | break;
82 | }
83 |
84 | case 'update': {
85 | const folderId = this.getNodeParameter('folderId', i) as string;
86 | const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
87 |
88 | // Ensure proper field handling
89 | if (updateFields.parent_folder_id === '') {
90 | updateFields.parent_folder_id = null;
91 | }
92 |
93 | if (updateFields.company_id === '') {
94 | updateFields.company_id = null;
95 | }
96 |
97 | const body: IDataObject = {
98 | folder: updateFields,
99 | };
100 |
101 | responseData = await handleUpdateOperation.call(this, resourceEndpoint, folderId, body);
102 | break;
103 | }
104 |
105 | case 'getPath': {
106 | const folderId = this.getNodeParameter('folderId', i) as string;
107 | const folders: IFolder[] = [];
108 | let currentFolderId: string | null = folderId;
109 |
110 | // Recursively get all folders in the path
111 | while (currentFolderId) {
112 | const folderData = await handleGetOperation.call(this, resourceEndpoint, currentFolderId) as IDataObject;
113 | const folder = folderData.folder as IFolder;
114 | folders.unshift(folder); // Add to start of array to maintain correct order
115 | currentFolderId = folder.parent_folder_id ? folder.parent_folder_id.toString() : null;
116 | }
117 |
118 | // Build the path string
119 | const path = folders.map(folder => folder.name).join(' / ');
120 |
121 | responseData = {
122 | path,
123 | folders,
124 | } as IFolderPathResponse;
125 | break;
126 | }
127 |
128 | case 'delete': {
129 | const folderId = this.getNodeParameter('folderId', i) as string;
130 | responseData = await handleDeleteOperation.call(this, resourceEndpoint, folderId);
131 | break;
132 | }
133 | }
134 |
135 | return responseData;
136 | }
137 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/folders/folders.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 | import type { FilterMapping } from '../../utils';
3 |
4 | export interface IFolder extends IDataObject {
5 | id: number;
6 | company_id?: number;
7 | icon?: string;
8 | description?: string;
9 | name: string;
10 | parent_folder_id?: number;
11 | created_at?: string;
12 | updated_at?: string;
13 | }
14 |
15 | export interface IFolderResponse extends IDataObject {
16 | folder: IFolder;
17 | }
18 |
19 | export interface IFolderPostProcessFilters {
20 | parent_folder_id?: number;
21 | childFolder?: 'yes' | 'no' | '';
22 | [key: string]: unknown;
23 | }
24 |
25 | // Define how each filter should be applied
26 | export const folderFilterMapping: FilterMapping = {
27 | parent_folder_id: (item: IDataObject, value: unknown) => item.parent_folder_id === value,
28 | childFolder: (item: IDataObject, value: unknown) => {
29 | if (value === 'yes') return item.parent_folder_id !== null;
30 | if (value === 'no') return item.parent_folder_id === null;
31 | return true;
32 | },
33 | };
34 |
35 | export interface IFolderPathResponse extends IDataObject {
36 | path: string;
37 | folders: IFolder[];
38 | }
39 |
40 | export type FolderOperation = 'create' | 'get' | 'getAll' | 'update' | 'delete' | 'getPath';
41 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/index.ts:
--------------------------------------------------------------------------------
1 | // Export all resource handlers and types
2 | export * from './activity_logs/activity_logs.handler';
3 | export * from './activity_logs/activity_logs.types';
4 | export * from './api_info/api_info.handler';
5 | export * from './api_info/api_info.types';
6 | export * from './articles/articles.handler';
7 | export * from './articles/articles.types';
8 | export * from './asset_layouts/asset_layouts.handler';
9 | export * from './asset_layouts/asset_layouts.types';
10 | export * from './asset_layout_fields/asset_layout_fields.handler';
11 | export * from './asset_layout_fields/asset_layout_fields.types';
12 | export * from './asset_passwords/asset_passwords.handler';
13 | export * from './asset_passwords/asset_passwords.types';
14 | export * from './assets/assets.handler';
15 | export * from './assets/assets.types';
16 | export * from './cards/cards.handler';
17 | export * from './cards/cards.types';
18 | export * from './companies/companies.handler';
19 | export * from './companies/companies.types';
20 | export * from './expirations/expirations.handler';
21 | export * from './expirations/expirations.types';
22 | export * from './folders/folders.handler';
23 | export * from './folders/folders.types';
24 | export * from './ip_addresses/ip_addresses.handler';
25 | export * from './ip_addresses/ip_addresses.types';
26 | export * from './list_options/list_options.handler';
27 | export * from './list_options/list_options.types';
28 | export * from './magic_dash/magic_dash.handler';
29 | export * from './magic_dash/magic_dash.types';
30 | export * from './matchers/matchers.handler';
31 | export * from './matchers/matchers.types';
32 | export * from './networks/networks.handler';
33 | export * from './networks/networks.types';
34 | export * from './password_folders/password_folders.handler';
35 | export * from './password_folders/password_folders.types';
36 | export * from './procedures/procedures.handler';
37 | export * from './procedures/procedures.types';
38 | export * from './procedure_tasks/procedure_tasks.handler';
39 | export * from './procedure_tasks/procedure_tasks.types';
40 | export * from './public_photos/public_photos.handler';
41 | export * from './public_photos/public_photos.types';
42 | export * from './rack_storages/rack_storages.handler';
43 | export * from './rack_storages/rack_storages.types';
44 | export * from './rack_storage_items/rack_storage_items.handler';
45 | export * from './rack_storage_items/rack_storage_items.types';
46 | export * from './relations/relations.handler';
47 | export * from './relations/relations.types';
48 | export * from './uploads/uploads.handler';
49 | export * from './uploads/uploads.types';
50 | export * from './users/users.handler';
51 | export * from './users/users.types';
52 | export * from './websites/websites.handler';
53 | export * from './websites/websites.types';
54 | export * from './lists/lists.handler';
55 | export * from './lists/lists.types';
56 | export * from './vlans/vlans.handler';
57 | export * from './vlans/vlans.types';
58 | export * from './vlan_zones/vlan_zones.handler';
59 | export * from './vlan_zones/vlan_zones.types';
60 |
61 | // Asset Field Custom
62 | export * from './assetCustomField/assetCustomField.types';
63 | export * from './assetCustomField/assetCustomField.handler';
64 |
65 | // Asset Link Field
66 | export * from './assetLinkField/assetLinkField.types';
67 | export * from './assetLinkField/assetLinkField.handler';
68 |
69 | // Asset Standard Field
70 | export * from './assetStandardField/assetStandardField.types';
71 | export * from './assetStandardField/assetStandardField.handler';
72 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/ip_addresses/ip_addresses.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { processDateRange, validateCompanyId } from '../../utils/index';
3 | import {
4 | handleGetAllOperation,
5 | handleGetOperation,
6 | handleCreateOperation,
7 | handleUpdateOperation,
8 | handleDeleteOperation,
9 | } from '../../utils/operations';
10 | import type { IpAddressOperations } from './ip_addresses.types';
11 | import type { DateRangePreset } from '../../utils/dateUtils';
12 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
13 | import { debugLog } from '../../utils/debugConfig';
14 |
15 | export async function handleIpAddressesOperation(
16 | this: IExecuteFunctions,
17 | operation: IpAddressOperations,
18 | i: number,
19 | ): Promise {
20 | const resourceEndpoint = '/ip_addresses';
21 | let responseData: IDataObject | IDataObject[] = {};
22 |
23 | switch (operation) {
24 | case 'getAll': {
25 | const filters = this.getNodeParameter('filters', i) as IDataObject;
26 | if (filters.company_id) {
27 | filters.company_id = validateCompanyId(filters.company_id, this.getNode(), 'Company ID');
28 | }
29 | const qs: IDataObject = {
30 | ...filters,
31 | };
32 |
33 | if (filters.created_at) {
34 | const createdAtFilter = filters.created_at as IDataObject;
35 | if (createdAtFilter.range) {
36 | const rangeObj = createdAtFilter.range as IDataObject;
37 | filters.created_at = processDateRange({
38 | range: {
39 | mode: rangeObj.mode as 'exact' | 'range' | 'preset',
40 | exact: rangeObj.exact as string,
41 | start: rangeObj.start as string,
42 | end: rangeObj.end as string,
43 | preset: rangeObj.preset as DateRangePreset,
44 | },
45 | });
46 | qs.created_at = filters.created_at;
47 | }
48 | }
49 |
50 | if (filters.updated_at) {
51 | const updatedAtFilter = filters.updated_at as IDataObject;
52 | if (updatedAtFilter.range) {
53 | const rangeObj = updatedAtFilter.range as IDataObject;
54 | filters.updated_at = processDateRange({
55 | range: {
56 | mode: rangeObj.mode as 'exact' | 'range' | 'preset',
57 | exact: rangeObj.exact as string,
58 | start: rangeObj.start as string,
59 | end: rangeObj.end as string,
60 | preset: rangeObj.preset as DateRangePreset,
61 | },
62 | });
63 | qs.updated_at = filters.updated_at;
64 | }
65 | }
66 |
67 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
68 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
69 |
70 | responseData = await handleGetAllOperation.call(
71 | this,
72 | resourceEndpoint,
73 | 'ip_addresses',
74 | qs,
75 | returnAll,
76 | limit,
77 | );
78 | return responseData;
79 | }
80 |
81 | case 'get': {
82 | const id = this.getNodeParameter('id', i) as string;
83 | return await handleGetOperation.call(this, resourceEndpoint, id);
84 | }
85 |
86 | case 'create': {
87 | const address = this.getNodeParameter('address', i) as string;
88 | const status = this.getNodeParameter('status', i) as string;
89 | const companyId = Number.parseInt(this.getNodeParameter('company_id', i) as string, 10);
90 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
91 |
92 | // Map 'comments' to 'notes' for API compatibility
93 | const body: IDataObject = {
94 | address,
95 | status,
96 | company_id: companyId,
97 | ...additionalFields,
98 | };
99 |
100 | // Map comments to notes if it exists
101 | if (body.comments !== undefined) {
102 | debugLog('[RESOURCE_TRANSFORM] Mapping comments to notes field for IP address', { comments: body.comments });
103 | body.notes = body.comments;
104 | delete body.comments;
105 | }
106 |
107 | return await handleCreateOperation.call(this, resourceEndpoint, { ip_address: body });
108 | }
109 |
110 | case 'update': {
111 | const id = this.getNodeParameter('id', i) as string;
112 | const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
113 |
114 | if (updateFields.company_id) {
115 | updateFields.company_id = Number.parseInt(updateFields.company_id as string, 10);
116 | }
117 |
118 | // Map comments to notes if it exists
119 | if (updateFields.comments !== undefined) {
120 | debugLog('[RESOURCE_TRANSFORM] Mapping comments to notes field for IP address update', { comments: updateFields.comments });
121 | updateFields.notes = updateFields.comments;
122 | delete updateFields.comments;
123 | }
124 |
125 | return await handleUpdateOperation.call(this, resourceEndpoint, id, { ip_address: updateFields });
126 | }
127 |
128 | case 'delete': {
129 | const id = this.getNodeParameter('id', i) as string;
130 | return await handleDeleteOperation.call(this, resourceEndpoint, id);
131 | }
132 |
133 | default:
134 | throw new Error(`Operation ${operation} is not supported`);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/ip_addresses/ip_addresses.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export type IpAddressOperations = 'create' | 'delete' | 'get' | 'getAll' | 'update';
4 |
5 | export interface IpAddress {
6 | id?: number; // The unique identifier for the IP address
7 | address: string; // The IP address
8 | status: 'unassigned' | 'assigned' | 'reserved' | 'deprecated' | 'dhcp' | 'slaac'; // The status of the IP address
9 | fqdn?: string; // The Fully Qualified Domain Name associated with the IP address
10 | description?: string; // A brief description of the IP address
11 | comments?: string; // Additional comments about the IP address (maps to 'notes' in the API)
12 | asset_id?: number; // The identifier of the asset associated with this IP address
13 | network_id?: number; // The identifier of the network to which this IP address belongs
14 | company_id?: number; // The identifier of the company that owns this IP address
15 | skip_dns_validation?: boolean; // If true, the server will not attempt to verify that the FQDN resolves to the address
16 | created_at?: string; // The date and time when the IP address was created
17 | updated_at?: string; // The date and time when the IP address was last updated
18 | }
19 |
20 | export interface IpAddressFilters extends IDataObject {
21 | network_id?: number;
22 | address?: string;
23 | status?: string;
24 | fqdn?: string;
25 | asset_id?: number;
26 | company_id?: number;
27 | created_at?: string;
28 | updated_at?: string;
29 | }
30 |
31 | export interface IpAddressCreateParams {
32 | address: string;
33 | status: string;
34 | fqdn?: string;
35 | description?: string;
36 | comments?: string;
37 | asset_id?: number;
38 | network_id?: number;
39 | company_id?: number;
40 | skip_dns_validation?: boolean;
41 | }
42 |
43 | export interface IpAddressUpdateParams extends IpAddressCreateParams {
44 | id: number;
45 | }
46 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/list_options/list_options.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 | import type { IListItem } from '../lists/lists.types';
3 |
4 | export interface IListOptionsResponse extends IDataObject {
5 | list_options: IListItem[]; // The list items to display as options
6 | }
7 |
8 | export type ListOptionsOperation = 'get' | 'create' | 'update' | 'delete';
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/lists/lists.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import {
3 | handleCreateOperation,
4 | handleGetOperation,
5 | handleGetAllOperation,
6 | handleUpdateOperation,
7 | handleDeleteOperation,
8 | } from '../../utils/operations';
9 | import type { ListsOperation } from './lists.types';
10 | import { DEBUG_CONFIG, debugLog } from '../../utils/debugConfig';
11 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
12 |
13 | export async function handleListsOperation(
14 | this: IExecuteFunctions,
15 | operation: ListsOperation,
16 | i: number,
17 | ): Promise {
18 | const resourceEndpoint = '/lists';
19 | let responseData: IDataObject | IDataObject[] = {};
20 |
21 | if (DEBUG_CONFIG.RESOURCE_PROCESSING) {
22 | debugLog('Lists Handler - Input', {
23 | operation,
24 | index: i,
25 | });
26 | }
27 |
28 | switch (operation) {
29 | case 'create': {
30 | // Name is required
31 | const name = this.getNodeParameter('name', i) as string;
32 | if (!name || name.trim() === '') {
33 | throw new Error('List name cannot be blank');
34 | }
35 |
36 | const body: IDataObject = {
37 | name,
38 | };
39 |
40 | responseData = await handleCreateOperation.call(
41 | this,
42 | resourceEndpoint,
43 | { list: body },
44 | );
45 | break;
46 | }
47 | case 'get': {
48 | const listId = this.getNodeParameter('id', i) as string;
49 | responseData = await handleGetOperation.call(
50 | this,
51 | resourceEndpoint,
52 | listId,
53 | );
54 | break;
55 | }
56 | case 'getAll': {
57 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
58 | const filters = this.getNodeParameter('filters', i, {}) as IDataObject;
59 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
60 | const qs: IDataObject = { ...filters };
61 | responseData = await handleGetAllOperation.call(
62 | this,
63 | resourceEndpoint,
64 | 'lists',
65 | qs,
66 | returnAll,
67 | limit,
68 | );
69 | break;
70 | }
71 | case 'update': {
72 | const listId = this.getNodeParameter('id', i) as string;
73 | const name = this.getNodeParameter('updateFields.name', i) as string;
74 | if (!name || name.trim() === '') {
75 | throw new Error('List name cannot be blank');
76 | }
77 |
78 | const updateData: IDataObject = {
79 | name,
80 | };
81 |
82 | responseData = await handleUpdateOperation.call(
83 | this,
84 | resourceEndpoint,
85 | listId,
86 | { list: updateData },
87 | );
88 | break;
89 | }
90 | case 'delete': {
91 | const listId = this.getNodeParameter('id', i) as string;
92 | responseData = await handleDeleteOperation.call(
93 | this,
94 | resourceEndpoint,
95 | listId,
96 | );
97 | break;
98 | }
99 | default:
100 | throw new Error(`The operation "${operation}" is not supported!`);
101 | }
102 |
103 | if (DEBUG_CONFIG.RESOURCE_PROCESSING) {
104 | debugLog('Lists Handler - Response', responseData);
105 | }
106 |
107 | return responseData;
108 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/lists/lists.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IListItem extends IDataObject {
4 | id?: number; // The unique ID of the list item
5 | name: string; // The name of the list item
6 | _destroy?: boolean; // Used for marking item for deletion in update
7 | }
8 |
9 | export interface IList extends IDataObject {
10 | id: number; // The unique ID of the list
11 | name: string; // The name of the list
12 | list_items: IListItem[]; // The items in the list
13 | created_at?: string; // The date and time when the list was created
14 | updated_at?: string; // The date and time when the list was last updated
15 | }
16 |
17 | export interface IListResponse extends IDataObject {
18 | list: IList;
19 | }
20 |
21 | export type ListsOperation =
22 | | 'create'
23 | | 'get'
24 | | 'getAll'
25 | | 'update'
26 | | 'delete';
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/magic_dash/magic_dash.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
2 | import { handleCreateOperation, handleDeleteOperation, handleUpdateOperation } from '../../utils/operations';
3 | import {
4 | handleMagicDashGetAllOperation,
5 | handleMagicDashGetByIdOperation,
6 | handleMagicDashDeleteByTitleOperation,
7 | } from '../../utils/operations/magic_dash';
8 | import type { MagicDashOperation } from './magic_dash.types';
9 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
10 |
11 | export async function handleMagicDashOperation(
12 | this: IExecuteFunctions,
13 | operation: MagicDashOperation,
14 | i: number,
15 | ): Promise {
16 | const resourceEndpoint = '/magic_dash';
17 | let responseData: IDataObject | IDataObject[] = {};
18 |
19 | switch (operation) {
20 | case 'getAll': {
21 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
22 | const filters = this.getNodeParameter('filters', i) as IDataObject;
23 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
24 |
25 | return await handleMagicDashGetAllOperation.call(this, filters, returnAll, limit as 25);
26 | }
27 |
28 | case 'get': {
29 | const id = this.getNodeParameter('id', i) as number;
30 | return await handleMagicDashGetByIdOperation.call(this, id);
31 | }
32 |
33 | case 'create':
34 | case 'update': {
35 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
36 | const message = this.getNodeParameter('message', i) as string;
37 | const companyName = this.getNodeParameter('companyName', i) as string;
38 | const title = this.getNodeParameter('title', i) as string;
39 | const content = this.getNodeParameter('content', i) as string;
40 |
41 | // Build the request body
42 | const magicDashBody: IDataObject = {
43 | message,
44 | company_name: companyName,
45 | title,
46 | };
47 |
48 | // Add content if it's not empty
49 | if (content) {
50 | magicDashBody.content = content;
51 | }
52 |
53 | // Add any additional fields
54 | for (const key of Object.keys(additionalFields)) {
55 | if (additionalFields[key] !== undefined && additionalFields[key] !== '') {
56 | magicDashBody[key] = additionalFields[key];
57 | }
58 | }
59 |
60 | const body = {
61 | magic_dash: magicDashBody,
62 | };
63 |
64 | responseData = operation === 'create'
65 | ? await handleCreateOperation.call(this, resourceEndpoint, body)
66 | : await handleUpdateOperation.call(this, resourceEndpoint, title, body);
67 | break;
68 | }
69 |
70 | case 'delete': {
71 | const title = this.getNodeParameter('title', i) as string;
72 | const companyName = this.getNodeParameter('companyName', i) as string;
73 | responseData = await handleMagicDashDeleteByTitleOperation.call(this, title, companyName);
74 | break;
75 | }
76 |
77 | case 'deleteById': {
78 | const id = this.getNodeParameter('id', i) as number;
79 | responseData = await handleDeleteOperation.call(this, resourceEndpoint, id);
80 | break;
81 | }
82 | }
83 |
84 | return responseData;
85 | }
86 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/magic_dash/magic_dash.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IMagicDash extends IDataObject {
4 | id: number; // The unique identifier for the MagicDash item (required)
5 | title: string; // The title of the MagicDash item (required)
6 | message: string; // The primary content to be displayed on the MagicDash item (required)
7 | company_id: number; // The unique identifier of the associated company (required)
8 | company_name: string; // The name of the associated company (required)
9 | shade?: string; // An optional color for the MagicDash item to represent different contextual states (can be null)
10 | content_link?: string; // A link to an external website associated with the MagicDash item's content (can be null)
11 | content?: string; // HTML content (tables, images, videos, etc.) to be displayed in the MagicDash item (can be null)
12 | icon?: string; // A FontAwesome icon for the header of the MagicDash item (can be null)
13 | image_url?: string; // A URL for an image to be used in the header of the MagicDash item (can be null)
14 | }
15 |
16 | export type IMagicDashResponse = IMagicDash[];
17 |
18 | export type MagicDashOperation = 'getAll' | 'get' | 'create' | 'update' | 'delete' | 'deleteById';
19 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/matchers/matchers.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { handleUpdateOperation, handleDeleteOperation } from '../../utils/operations';
3 | import { handleMatcherGetAllOperation } from '../../utils/operations/matchers';
4 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
5 | import type { MatcherOperation } from './matchers.types';
6 |
7 | export async function handleMatcherOperation(
8 | this: IExecuteFunctions,
9 | operation: MatcherOperation,
10 | i: number,
11 | ): Promise {
12 | const resourceEndpoint = '/matchers';
13 | let responseData: IDataObject | IDataObject[] = {};
14 |
15 | switch (operation) {
16 | case 'getAll': {
17 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
18 | const filters = this.getNodeParameter('filters', i) as IDataObject;
19 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
20 | const integrationId = this.getNodeParameter('integrationId', i) as number;
21 |
22 | responseData = await handleMatcherGetAllOperation.call(
23 | this,
24 | integrationId,
25 | filters,
26 | returnAll,
27 | limit,
28 | );
29 | break;
30 | }
31 |
32 | case 'update': {
33 | const id = this.getNodeParameter('id', i) as number;
34 | const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
35 |
36 | // Build the request body with only defined fields
37 | const matcherUpdate: IDataObject = {};
38 |
39 | if (updateFields.company_id !== undefined) {
40 | matcherUpdate.company_id = Number.parseInt(updateFields.company_id as string, 10);
41 | }
42 | if (updateFields.potential_company_id !== undefined) {
43 | matcherUpdate.potential_company_id = Number.parseInt(
44 | updateFields.potential_company_id as string,
45 | 10,
46 | );
47 | }
48 | if (updateFields.sync_id !== undefined) {
49 | matcherUpdate.sync_id = updateFields.sync_id;
50 | }
51 | if (updateFields.identifier !== undefined) {
52 | matcherUpdate.identifier = updateFields.identifier;
53 | }
54 |
55 | responseData = await handleUpdateOperation.call(this, resourceEndpoint, id, { matcher: matcherUpdate });
56 | break;
57 | }
58 |
59 | case 'delete': {
60 | const id = this.getNodeParameter('id', i) as number;
61 | responseData = await handleDeleteOperation.call(this, resourceEndpoint, id);
62 | break;
63 | }
64 | }
65 |
66 | return responseData;
67 | }
68 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/matchers/matchers.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IMatcher extends IDataObject {
4 | id: number; // The unique identifier for the matcher (required)
5 | integrator_id: number; // The unique identifier for the integrator (required)
6 | integrator_name: string; // The name of the integrator (required)
7 | sync_id: number; // The unique identifier for the synchronization (required)
8 | name: string; // The name of the matcher (required)
9 | identifier?: string; // The identifier in the integration (can be null)
10 | potential_company_id?: number; // The potential company ID (can be null)
11 | company_id?: number; // The company ID (can be null)
12 | company_name?: string; // The name of the company (can be null)
13 | }
14 |
15 | export interface IMatcherResponse extends IDataObject {
16 | matchers: IMatcher[];
17 | }
18 |
19 | export type MatcherOperation = 'getAll' | 'update' | 'delete';
20 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/networks/networks.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { processDateRange, type DateRangePreset, validateCompanyId } from '../../utils';
3 | import {
4 | handleGetAllOperation,
5 | handleGetOperation,
6 | handleCreateOperation,
7 | handleUpdateOperation,
8 | handleDeleteOperation,
9 | } from '../../utils/operations';
10 | import type { NetworksOperations } from './networks.types';
11 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
12 |
13 | export async function handleNetworksOperation(
14 | this: IExecuteFunctions,
15 | operation: NetworksOperations,
16 | i: number,
17 | ): Promise {
18 | const resourceEndpoint = '/networks';
19 | let responseData: IDataObject | IDataObject[] = {};
20 |
21 | switch (operation) {
22 | case 'create': {
23 | const body: IDataObject = {
24 | name: this.getNodeParameter('name', i) as string,
25 | address: this.getNodeParameter('address', i) as string,
26 | network_type: this.getNodeParameter('network_type', i) as number,
27 | };
28 |
29 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
30 |
31 | // Only add non-empty additional fields
32 | for (const [key, value] of Object.entries(additionalFields)) {
33 | if (value !== undefined && value !== null && value !== '') {
34 | // Convert company_id to number if present
35 | if (key === 'company_id') {
36 | body[key] = Number.parseInt(value as string, 10);
37 | } else {
38 | body[key] = value;
39 | }
40 | }
41 | }
42 |
43 | responseData = await handleCreateOperation.call(this, resourceEndpoint, { network: body });
44 | return responseData;
45 | }
46 |
47 | case 'delete': {
48 | const networkId = this.getNodeParameter('networkId', i) as string;
49 | return await handleDeleteOperation.call(this, resourceEndpoint, networkId);
50 | }
51 |
52 | case 'get': {
53 | const networkId = this.getNodeParameter('networkId', i) as string;
54 | return await handleGetOperation.call(this, resourceEndpoint, networkId);
55 | }
56 |
57 | case 'getAll': {
58 | const filters = this.getNodeParameter('filters', i) as IDataObject;
59 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
60 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
61 | const qs: IDataObject = {
62 | ...filters,
63 | };
64 |
65 | // Validate company_id if provided
66 | if (filters.company_id !== undefined && filters.company_id !== null && filters.company_id !== '') {
67 | qs.company_id = validateCompanyId(filters.company_id, this.getNode(), 'Company ID');
68 | }
69 |
70 | // Pass archived filter if present
71 | if (filters.archived !== undefined) {
72 | qs.archived = filters.archived;
73 | }
74 |
75 | if (filters.created_at) {
76 | const createdAtFilter = filters.created_at as IDataObject;
77 | if (createdAtFilter.range) {
78 | const rangeObj = createdAtFilter.range as IDataObject;
79 | filters.created_at = processDateRange({
80 | range: {
81 | mode: rangeObj.mode as 'exact' | 'range' | 'preset',
82 | exact: rangeObj.exact as string,
83 | start: rangeObj.start as string,
84 | end: rangeObj.end as string,
85 | preset: rangeObj.preset as DateRangePreset,
86 | },
87 | });
88 | }
89 | }
90 |
91 | if (filters.updated_at) {
92 | const updatedAtFilter = filters.updated_at as IDataObject;
93 | if (updatedAtFilter.range) {
94 | const rangeObj = updatedAtFilter.range as IDataObject;
95 | filters.updated_at = processDateRange({
96 | range: {
97 | mode: rangeObj.mode as 'exact' | 'range' | 'preset',
98 | exact: rangeObj.exact as string,
99 | start: rangeObj.start as string,
100 | end: rangeObj.end as string,
101 | preset: rangeObj.preset as DateRangePreset,
102 | },
103 | });
104 | }
105 | }
106 |
107 | return await handleGetAllOperation.call(
108 | this,
109 | resourceEndpoint,
110 | 'networks',
111 | qs,
112 | returnAll,
113 | limit,
114 | );
115 | }
116 |
117 | case 'update': {
118 | const networkId = this.getNodeParameter('networkId', i) as string;
119 | const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
120 |
121 | // Only include non-empty update fields
122 | const networkBody: IDataObject = {};
123 | for (const [key, value] of Object.entries(updateFields)) {
124 | if (value !== undefined && value !== null && value !== '') {
125 | // Validate company_id if present
126 | if (key === 'company_id') {
127 | networkBody[key] = validateCompanyId(value, this.getNode(), 'Company ID');
128 | } else {
129 | networkBody[key] = value;
130 | }
131 | }
132 | }
133 |
134 | const body: IDataObject = {
135 | network: networkBody,
136 | };
137 |
138 | return await handleUpdateOperation.call(this, resourceEndpoint, networkId, body);
139 | }
140 | }
141 |
142 | return responseData;
143 | }
144 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/networks/networks.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface INetwork extends IDataObject {
4 | id?: number; // The unique identifier for the network
5 | name: string; // The name of the network
6 | address: string; // The network address, typically in CIDR notation
7 | network_type: number; // The type of network, represented as an integer
8 | slug?: string; // A slug representing the network
9 | company_id?: number; // The identifier of the company that owns this network
10 | location_id?: number; // The identifier of the location associated with this network
11 | description?: string; // A brief description of the network
12 | created_at?: string; // The date and time when the network was created
13 | updated_at?: string; // The date and time when the network was last updated
14 | archived?: boolean; // Whether the network is archived
15 | notes?: string; // Additional comments about the network
16 | ancestry?: string; // Ancestry path for hierarchical network structure
17 | settings?: IDataObject; // Settings for the network
18 | sync_identifier?: string; // External identifier for synchronisation purposes
19 | is_discovery?: boolean; // Indicates if the network was discovered automatically
20 | status_list_item_id?: number; // The status list item ID for this network
21 | role_list_item_id?: number; // The role list item ID for this network
22 | vlan_id?: number; // The VLAN ID associated with this network
23 | url?: string; // The URL to access this network in the web interface (read-only)
24 | archived_at?: string; // The date and time when the network was archived. Null if not archived (read-only)
25 | }
26 |
27 | export type NetworksOperations = 'create' | 'delete' | 'get' | 'getAll' | 'update';
28 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/password_folders/password_folders.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { handleGetOperation, handleGetAllOperation } from '../../utils/operations';
3 | import type { PasswordFoldersOperations } from './password_folders.types';
4 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
5 |
6 | export async function handlePasswordFoldersOperation(
7 | this: IExecuteFunctions,
8 | operation: PasswordFoldersOperations,
9 | i: number,
10 | ): Promise {
11 | const resourceEndpoint = '/password_folders';
12 | let responseData: IDataObject | IDataObject[] = {};
13 |
14 | switch (operation) {
15 | case 'get': {
16 | const folderId = this.getNodeParameter('id', i) as string;
17 | responseData = await handleGetOperation.call(this, resourceEndpoint, folderId);
18 | break;
19 | }
20 |
21 | case 'getAll': {
22 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
23 | const filters = this.getNodeParameter('filters', i) as IDataObject;
24 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
25 |
26 | const qs: IDataObject = {
27 | ...filters,
28 | };
29 |
30 | responseData = await handleGetAllOperation.call(
31 | this,
32 | resourceEndpoint,
33 | 'password_folders',
34 | qs,
35 | returnAll,
36 | limit,
37 | );
38 | break;
39 | }
40 | }
41 |
42 | return responseData;
43 | }
44 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/password_folders/password_folders.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IPasswordFolder extends IDataObject {
4 | id: number; // The unique identifier of the password folder
5 | company_id: number | null; // The ID of the associated company, if any. Can Be null.
6 | description: string; // A brief description of the password folder
7 | name: string; // The name of the password folder
8 | slug: string; // A slug representing the password folder
9 | created_at: string; // The timestamp of password folder creation
10 | updated_at: string; // The timestamp of the last password folder update
11 | }
12 |
13 | export interface IPasswordFolderResponse extends IDataObject {
14 | password_folders: IPasswordFolder[];
15 | }
16 |
17 | export type PasswordFoldersOperations = 'get' | 'getAll';
18 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/procedure_tasks/procedure_tasks.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import {
3 | handleGetAllOperation,
4 | handleUpdateOperation,
5 | handleCreateOperation,
6 | handleDeleteOperation,
7 | handleGetOperation,
8 | } from '../../utils/operations';
9 | import type { ProcedureTasksOperations } from './procedure_tasks.types';
10 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
11 |
12 | export async function handleProcedureTasksOperation(
13 | this: IExecuteFunctions,
14 | operation: ProcedureTasksOperations,
15 | i: number,
16 | ): Promise {
17 | const resourceEndpoint = '/procedure_tasks';
18 |
19 | switch (operation) {
20 | case 'create': {
21 | const body: IDataObject = {
22 | name: this.getNodeParameter('name', i) as string,
23 | procedure_id: this.getNodeParameter('procedure_id', i) as number,
24 | };
25 |
26 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
27 |
28 | // Only add non-empty additional fields
29 | for (const [key, value] of Object.entries(additionalFields)) {
30 | if (value !== undefined && value !== null && value !== '') {
31 | body[key] = value;
32 | }
33 | }
34 |
35 | return await handleCreateOperation.call(this, resourceEndpoint, { procedure_task: body });
36 | }
37 |
38 | case 'delete': {
39 | const taskId = this.getNodeParameter('taskId', i) as string;
40 | return await handleDeleteOperation.call(this, resourceEndpoint, taskId);
41 | }
42 |
43 | case 'get': {
44 | const taskId = this.getNodeParameter('taskId', i) as string;
45 | return await handleGetOperation.call(this, resourceEndpoint, taskId);
46 | }
47 |
48 | case 'getAll': {
49 | const filters = this.getNodeParameter('filters', i) as IDataObject;
50 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
51 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
52 |
53 | const qs: IDataObject = {
54 | ...filters,
55 | };
56 |
57 | return await handleGetAllOperation.call(
58 | this,
59 | resourceEndpoint,
60 | 'procedure_tasks',
61 | qs,
62 | returnAll,
63 | limit,
64 | );
65 | }
66 |
67 | case 'update': {
68 | const taskId = this.getNodeParameter('taskId', i) as string;
69 | const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
70 |
71 | // Only include non-empty update fields
72 | const body: IDataObject = {};
73 | for (const [key, value] of Object.entries(updateFields)) {
74 | if (value !== undefined && value !== null && value !== '') {
75 | body[key] = value;
76 | }
77 | }
78 |
79 | return await handleUpdateOperation.call(this, resourceEndpoint, taskId, { procedure_task: body });
80 | }
81 | }
82 |
83 | // This should never be reached due to TypeScript's exhaustive check
84 | throw new Error(`Unsupported operation ${operation}`);
85 | }
86 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/procedure_tasks/procedure_tasks.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IProcedureTask extends IDataObject {
4 | id?: number; // The unique ID of the procedure task
5 | name: string; // The name of the task
6 | description?: string; // A detailed description of the task
7 | procedure_id: number; // The ID of the procedure this task belongs to
8 | position?: number; // The position of the task in the procedure
9 | user_id?: number; // The ID of the user assigned to the task
10 | due_date?: string; // The due date for the task
11 | priority?: 'unsure' | 'low' | 'normal' | 'high' | 'urgent'; // The priority level of the task
12 | assigned_users?: number[]; // An array of user IDs assigned to the task
13 | completed?: boolean; // Indicates whether the task is completed
14 | completed_at?: string; // The date and time when the task was completed
15 | created_at?: string; // The date and time when the task was created
16 | updated_at?: string; // The date and time when the task was last updated
17 | }
18 |
19 | export type ProcedureTasksOperations = 'create' | 'delete' | 'get' | 'getAll' | 'update';
20 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/procedures/procedures.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IProcedure extends IDataObject {
4 | id: number; // The unique identifier of the procedure
5 | slug: string; // The URL-friendly unique identifier of the procedure
6 | name: string; // The name of the procedure
7 | description?: string; // A brief description of the procedure (can be null)
8 | total: number; // The total number of tasks in the procedure
9 | completed: number; // The number of completed tasks in the procedure
10 | url: string; // The URL for accessing the procedure
11 | object_type: string; // The type of object the procedure represents
12 | company_id?: number; // The unique identifier of the company this procedure belongs to (can be null)
13 | company_name?: string; // The name of the associated company (can be null)
14 | completion_percentage: string; // The completion percentage of the procedure
15 | created_at: string; // The date and time when the procedure was created
16 | updated_at: string; // The date and time when the procedure was last updated
17 | parent_procedure?: string; // The parent procedure, if any (can be null)
18 | asset?: string; // The associated asset, if any (can be null)
19 | share_url: string; // The URL for sharing the procedure
20 | procedure_tasks_attributes?: IDataObject[]; // A list of attributes for the tasks associated with the procedure
21 | }
22 |
23 | export interface IProcedureResponse extends IDataObject {
24 | procedures: IProcedure[];
25 | }
26 |
27 | export type ProceduresOperations =
28 | | 'create'
29 | | 'delete'
30 | | 'get'
31 | | 'getAll'
32 | | 'update'
33 | | 'archive'
34 | | 'unarchive'
35 | | 'createFromTemplate'
36 | | 'duplicate'
37 | | 'kickoff';
38 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/public_photos/public_photos.handler.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | IExecuteFunctions,
3 | IDataObject,
4 | IHttpRequestMethods,
5 | } from 'n8n-workflow';
6 | import { huduApiRequest, handleListing } from '../../utils';
7 | import type { PublicPhotoOperation, IPublicPhoto } from './public_photos.types';
8 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
9 | import { NodeOperationError } from 'n8n-workflow';
10 |
11 | export async function handlePublicPhotoOperation(
12 | this: IExecuteFunctions,
13 | operation: PublicPhotoOperation,
14 | i: number,
15 | ): Promise {
16 | let responseData: IDataObject | IDataObject[];
17 |
18 | switch (operation) {
19 | case 'create': {
20 | // Get the binary property name from the parameter (default: 'data')
21 | const binaryPropertyName = this.getNodeParameter('photo', i) as string;
22 | const recordType = this.getNodeParameter('record_type', i) as string;
23 | const recordId = this.getNodeParameter('record_id', i) as number;
24 |
25 | // Extract binary data from the item
26 | const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
27 | const fileBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
28 |
29 | // Prepare form data for multipart upload
30 | const formData = {
31 | photo: {
32 | value: fileBuffer,
33 | options: {
34 | filename: binaryData.fileName || 'photo',
35 | contentType: binaryData.mimeType || 'application/octet-stream',
36 | },
37 | },
38 | record_type: recordType,
39 | record_id: recordId,
40 | };
41 |
42 | // Signal multipart upload to the request utility
43 | (formData as any)._isMultipart = true;
44 |
45 | responseData = await huduApiRequest.call(
46 | this,
47 | 'POST' as IHttpRequestMethods,
48 | '/public_photos',
49 | formData,
50 | );
51 | break;
52 | }
53 |
54 | case 'getAll': {
55 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
56 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
57 | const filter = this.getNodeParameter('filter', i, {}) as IDataObject;
58 | let recordTypeFilter = '';
59 | let recordIdFilter: number | null = null;
60 | if (filter && Array.isArray(filter.criteria) && filter.criteria.length > 0) {
61 | const criteria = filter.criteria[0] as IDataObject;
62 | recordTypeFilter = (criteria.record_type_filter as string) || '';
63 | recordIdFilter = criteria.record_id_filter !== undefined ? (criteria.record_id_filter as number) : null;
64 | }
65 |
66 | let photos = await handleListing.call(
67 | this,
68 | 'GET' as IHttpRequestMethods,
69 | '/public_photos',
70 | 'public_photos',
71 | {},
72 | {},
73 | returnAll,
74 | limit,
75 | ) as IPublicPhoto[];
76 |
77 | if (recordTypeFilter) {
78 | photos = photos.filter(photo => photo.record_type === recordTypeFilter);
79 | }
80 |
81 | if (recordIdFilter !== null) {
82 | photos = photos.filter(photo => photo.record_id === recordIdFilter);
83 | }
84 |
85 | responseData = photos;
86 | break;
87 | }
88 |
89 | case 'get': {
90 | const photoId = this.getNodeParameter('id', i) as number;
91 | if (photoId === undefined || photoId === null) {
92 | throw new NodeOperationError(this.getNode(), 'Photo ID is required for the Get operation.');
93 | }
94 |
95 | // Efficiently fetch pages of public photos and search for the requested ID
96 | const pageSize = HUDU_API_CONSTANTS.PAGE_SIZE;
97 | let page = 1;
98 | let foundPhoto: IPublicPhoto | undefined;
99 | let hasMore = true;
100 |
101 | while (hasMore && !foundPhoto) {
102 | // Fetch the current page
103 | const queryParams = { page, page_size: pageSize };
104 | const batch = await huduApiRequest.call(
105 | this,
106 | 'GET' as IHttpRequestMethods,
107 | '/public_photos',
108 | {},
109 | queryParams,
110 | 'public_photos',
111 | ) as IPublicPhoto[];
112 |
113 | if (Array.isArray(batch) && batch.length > 0) {
114 | foundPhoto = batch.find(photo => photo.id === photoId);
115 | // If found, break early
116 | if (foundPhoto) {
117 | break;
118 | }
119 | // If batch is less than pageSize, this is the last page
120 | hasMore = batch.length === pageSize;
121 | page++;
122 | } else {
123 | // No more results
124 | hasMore = false;
125 | }
126 | }
127 |
128 | if (!foundPhoto) {
129 | throw new NodeOperationError(this.getNode(), `Public photo with ID "${photoId}" not found.`);
130 | }
131 | responseData = foundPhoto;
132 | break;
133 | }
134 |
135 | case 'update': {
136 | const id = this.getNodeParameter('id', i) as number;
137 | const formData = {
138 | record_type: this.getNodeParameter('record_type', i) as string,
139 | record_id: this.getNodeParameter('record_id', i) as number,
140 | };
141 |
142 | responseData = await huduApiRequest.call(
143 | this,
144 | 'PUT' as IHttpRequestMethods,
145 | `/public_photos/${id}`,
146 | { public_photo: formData },
147 | );
148 | break;
149 | }
150 | }
151 |
152 | return responseData;
153 | }
154 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/public_photos/public_photos.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IPublicPhoto extends IDataObject {
4 | id: number; // The ID of the public photo
5 | url: string; // The URL of the public photo
6 | record_type: string; // The type of record the public photo is associated with (e.g., Article)
7 | record_id: number; // The ID of the record the public photo is associated with
8 | }
9 |
10 | export interface IPublicPhotoResponse extends IDataObject {
11 | public_photo: IPublicPhoto;
12 | }
13 |
14 | export type PublicPhotoOperation = 'getAll' | 'create' | 'update' | 'get';
15 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/rack_storage_items/rack_storage_items.handler.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | IExecuteFunctions,
3 | IDataObject,
4 | } from 'n8n-workflow';
5 | import {
6 | handleCreateOperation,
7 | handleDeleteOperation,
8 | handleGetOperation,
9 | handleGetAllOperation,
10 | handleUpdateOperation,
11 | } from '../../utils/operations';
12 | import type { RackStorageItemOperation } from './rack_storage_items.types';
13 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
14 |
15 | export async function handleRackStorageItemOperation(
16 | this: IExecuteFunctions,
17 | operation: RackStorageItemOperation,
18 | i: number,
19 | ): Promise {
20 | const resourceEndpoint = '/rack_storage_items';
21 |
22 | switch (operation) {
23 | case 'getAll': {
24 | const filters = this.getNodeParameter('filters', i) as IDataObject;
25 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
26 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
27 |
28 | return await handleGetAllOperation.call(
29 | this,
30 | resourceEndpoint,
31 | 'rack_storage_items',
32 | filters,
33 | returnAll,
34 | limit,
35 | );
36 | }
37 |
38 | case 'get': {
39 | const id = this.getNodeParameter('id', i) as number;
40 | return await handleGetOperation.call(this, resourceEndpoint, id);
41 | }
42 |
43 | case 'create': {
44 | const body = {
45 | rack_storage_item: {
46 | rack_storage_role_id: this.getNodeParameter('rack_storage_role_id', i) as number,
47 | asset_id: this.getNodeParameter('asset_id', i) as number,
48 | start_unit: this.getNodeParameter('start_unit', i) as number,
49 | end_unit: this.getNodeParameter('end_unit', i) as number,
50 | status: this.getNodeParameter('status', i) as number,
51 | side: this.getNodeParameter('side', i) as number,
52 | ...(this.getNodeParameter('additionalFields', i) as IDataObject),
53 | },
54 | };
55 | return await handleCreateOperation.call(this, resourceEndpoint, body);
56 | }
57 |
58 | case 'update': {
59 | const id = this.getNodeParameter('id', i) as number;
60 | const body = {
61 | rack_storage_item: {
62 | ...(this.getNodeParameter('updateFields', i) as IDataObject),
63 | },
64 | };
65 | return await handleUpdateOperation.call(this, resourceEndpoint, id, body);
66 | }
67 |
68 | case 'delete': {
69 | const id = this.getNodeParameter('id', i) as number;
70 | return await handleDeleteOperation.call(this, resourceEndpoint, id);
71 | }
72 | }
73 |
74 | // This should never be reached due to TypeScript's exhaustive check
75 | throw new Error(`Unsupported operation ${operation}`);
76 | }
77 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/rack_storage_items/rack_storage_items.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IRackStorageItem extends IDataObject {
4 | id?: number; // The unique ID of the rack storage item
5 | rack_storage_role_id: number; // The unique ID of the rack storage role
6 | asset_id: number; // The unique ID of the asset
7 | start_unit: number; // The start unit of the rack storage item
8 | end_unit: number; // The end unit of the rack storage item
9 | status: number; // The status of the rack storage item
10 | side: number; // The side of the rack storage item
11 | max_wattage?: number; // The maximum wattage of the rack storage item
12 | power_draw?: number; // The power draw of the rack storage item
13 | rack_storage_role_name?: string; // The name of the rack storage role
14 | reserved_message?: string; // The reserved message for the rack storage item
15 | rack_storage_role_description?: string; // The description of the rack storage role
16 | rack_storage_role_hex_color?: string; // The hex color of the rack storage role
17 | asset_name?: string; // The name of the asset
18 | asset_url?: string; // The URL of the asset
19 | url?: string; // The URL of the rack storage item
20 | company_id?: number; // The unique ID of the company
21 | }
22 |
23 | export interface IRackStorageItemResponse extends IDataObject {
24 | rack_storage_item: IRackStorageItem;
25 | }
26 |
27 | export type RackStorageItemOperation = 'getAll' | 'get' | 'create' | 'update' | 'delete';
28 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/rack_storages/rack_storages.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { processDateRange, validateCompanyId } from '../../utils/index';
3 | import {
4 | handleCreateOperation,
5 | handleDeleteOperation,
6 | handleGetOperation,
7 | handleGetAllOperation,
8 | handleUpdateOperation,
9 | } from '../../utils/operations';
10 | import type { RackStorageOperation } from './rack_storages.types';
11 | import type { DateRangePreset } from '../../utils/dateUtils';
12 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
13 |
14 | export async function handleRackStorageOperation(
15 | this: IExecuteFunctions,
16 | operation: RackStorageOperation,
17 | i: number,
18 | ): Promise {
19 | const resourceEndpoint = '/rack_storages';
20 |
21 | switch (operation) {
22 | case 'getAll': {
23 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
24 | const filters = this.getNodeParameter('filters', i) as IDataObject;
25 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
26 |
27 | if (filters.company_id) {
28 | filters.company_id = validateCompanyId(filters.company_id, this.getNode(), 'Company ID');
29 | }
30 | const qs: IDataObject = {
31 | ...filters,
32 | };
33 |
34 | // Process date range filters
35 | if (filters.created_at) {
36 | const createdAtFilter = filters.created_at as IDataObject;
37 | if (createdAtFilter.range) {
38 | const rangeObj = createdAtFilter.range as IDataObject;
39 | filters.created_at = processDateRange({
40 | range: {
41 | mode: rangeObj.mode as 'exact' | 'range' | 'preset',
42 | exact: rangeObj.exact as string,
43 | start: rangeObj.start as string,
44 | end: rangeObj.end as string,
45 | preset: rangeObj.preset as DateRangePreset,
46 | },
47 | });
48 | qs.created_at = filters.created_at;
49 | }
50 | }
51 |
52 | if (filters.updated_at) {
53 | const updatedAtFilter = filters.updated_at as IDataObject;
54 | if (updatedAtFilter.range) {
55 | const rangeObj = updatedAtFilter.range as IDataObject;
56 | filters.updated_at = processDateRange({
57 | range: {
58 | mode: rangeObj.mode as 'exact' | 'range' | 'preset',
59 | exact: rangeObj.exact as string,
60 | start: rangeObj.start as string,
61 | end: rangeObj.end as string,
62 | preset: rangeObj.preset as DateRangePreset,
63 | },
64 | });
65 | qs.updated_at = filters.updated_at;
66 | }
67 | }
68 |
69 | return await handleGetAllOperation.call(
70 | this,
71 | resourceEndpoint,
72 | '',
73 | qs,
74 | returnAll,
75 | limit,
76 | );
77 | }
78 |
79 | case 'get': {
80 | const id = this.getNodeParameter('id', i) as number;
81 | return await handleGetOperation.call(this, resourceEndpoint, id);
82 | }
83 |
84 | case 'create': {
85 | const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
86 | if (additionalFields.company_id) {
87 | additionalFields.company_id = Number.parseInt(additionalFields.company_id as string, 10);
88 | }
89 |
90 | const body = {
91 | rack_storage: {
92 | name: this.getNodeParameter('name', i) as string,
93 | location_id: this.getNodeParameter('locationId', i) as number,
94 | ...additionalFields,
95 | },
96 | };
97 | return await handleCreateOperation.call(this, resourceEndpoint, body);
98 | }
99 |
100 | case 'update': {
101 | const id = this.getNodeParameter('id', i) as number;
102 | const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
103 |
104 | if (updateFields.company_id) {
105 | updateFields.company_id = Number.parseInt(updateFields.company_id as string, 10);
106 | }
107 |
108 | const body = {
109 | rack_storage: {
110 | ...updateFields,
111 | },
112 | };
113 | return await handleUpdateOperation.call(this, resourceEndpoint, id, body);
114 | }
115 |
116 | case 'delete': {
117 | const id = this.getNodeParameter('id', i) as number;
118 | return await handleDeleteOperation.call(this, resourceEndpoint, id);
119 | }
120 | }
121 |
122 | // This should never be reached due to TypeScript's exhaustive check
123 | throw new Error(`Unsupported operation ${operation}`);
124 | }
125 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/rack_storages/rack_storages.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | interface IRackStorageItem {
4 | has_items: boolean;
5 | items: IDataObject[] | null;
6 | number: number;
7 | }
8 |
9 | export interface IRackStorageFilters extends IDataObject {
10 | company_id?: number; // Filter by company id
11 | location_id?: number; // Filter by location id
12 | height?: number; // Filter by rack height
13 | min_width?: number; // Filter by minimum rack width
14 | max_width?: number; // Filter by maximum rack width
15 | created_at?: string; // Filter by creation date range or exact time
16 | updated_at?: string; // Filter by update date range or exact time
17 | }
18 |
19 | export interface IRackStorage extends IDataObject {
20 | id?: number; // The unique ID of the rack storage
21 | location_id: number; // The unique ID of the location of the rack storage
22 | name: string; // The name of the rack storage
23 | description?: string; // The description of the rack storage
24 | descending_units?: boolean; // Whether the units are numbered in descending order
25 | max_wattage?: number | null; // The maximum wattage the rack storage can handle
26 | starting_unit?: number; // The starting unit of the rack storage
27 | height?: number; // The height of the rack storage
28 | width?: number | null; // The width of the rack storage
29 | serial_number?: string; // The serial number of the rack storage
30 | asset_tag?: string; // The asset tag of the rack storage
31 | front_items?: IRackStorageItem[]; // Array of items on the front of the rack
32 | created_at?: string; // The date and time when the rack storage was created
33 | updated_at?: string; // The date and time when the rack storage was last updated
34 | discarded_at?: string; // The date and time when the rack storage was discarded (can be null)
35 | company_id?: number; // The unique ID of the company
36 | }
37 |
38 | export interface IRackStorageResponse extends IDataObject {
39 | rack_storage: IRackStorage;
40 | }
41 |
42 | export type RackStorageOperation = 'getAll' | 'get' | 'create' | 'update' | 'delete';
43 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/relations/relations.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
3 | import {
4 | handleCreateOperation,
5 | handleDeleteOperation,
6 | handleGetAllOperation,
7 | } from '../../utils/operations';
8 | import type { RelationOperation } from './relations.types';
9 | import { relationFilterMapping } from './relations.types';
10 |
11 | export async function handleRelationsOperation(
12 | this: IExecuteFunctions,
13 | operation: RelationOperation,
14 | i: number,
15 | ): Promise {
16 | const resourceEndpoint = '/relations';
17 |
18 | switch (operation) {
19 | case 'getAll': {
20 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
21 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
22 | const filters = this.getNodeParameter('filters', i, {}) as IDataObject;
23 |
24 | return await handleGetAllOperation.call(
25 | this,
26 | resourceEndpoint,
27 | 'relations',
28 | {},
29 | returnAll,
30 | limit,
31 | filters,
32 | relationFilterMapping,
33 | );
34 | }
35 |
36 | case 'create': {
37 | const body = {
38 | relation: {
39 | toable_id: this.getNodeParameter('toable_id', i) as number,
40 | toable_type: this.getNodeParameter('toable_type', i) as string,
41 | fromable_id: this.getNodeParameter('fromable_id', i) as number,
42 | fromable_type: this.getNodeParameter('fromable_type', i) as string,
43 | description: this.getNodeParameter('description', i) as string,
44 | is_inverse: this.getNodeParameter('is_inverse', i) as boolean,
45 | },
46 | };
47 |
48 | return await handleCreateOperation.call(this, resourceEndpoint, body);
49 | }
50 |
51 | case 'delete': {
52 | const id = this.getNodeParameter('id', i) as number;
53 | return await handleDeleteOperation.call(this, resourceEndpoint, id);
54 | }
55 | }
56 |
57 | // This should never be reached due to TypeScript's exhaustive check
58 | throw new Error(`Unsupported operation ${operation}`);
59 | }
60 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/relations/relations.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 | import type { FilterMapping } from '../../utils';
3 |
4 | export interface IRelation extends IDataObject {
5 | id: number; // The unique identifier of the relation
6 | description?: string; // The description of the relation (optional, can be null)
7 | is_inverse: boolean; // Indicates whether the relation is inverse or not
8 | name: string; // The name of the relation
9 | fromable_id: number; // The ID of the origin entity involved in the relation
10 | fromable_type: string; // The type of the origin entity involved in the relation
11 | fromable_url: string; // The URL of the origin entity involved in the relation
12 | toable_id: number; // The ID of the destination entity involved in the relation
13 | toable_type: string; // The type of the destination entity involved in the relation
14 | toable_url: string; // The URL of the destination entity involved in the relation
15 | }
16 |
17 | export interface IRelationResponse extends IDataObject {
18 | relation: IRelation;
19 | }
20 |
21 | export type RelationOperation = 'getAll' | 'create' | 'delete';
22 |
23 | export type RelationType =
24 | | 'Asset'
25 | | 'Website'
26 | | 'Procedure'
27 | | 'AssetPassword'
28 | | 'Company'
29 | | 'Article';
30 |
31 | export interface IRelationPostProcessFilters extends IDataObject {
32 | fromable_type?: string;
33 | fromable_id?: number;
34 | toable_type?: string;
35 | toable_id?: number;
36 | is_inverse?: boolean;
37 | }
38 |
39 | // Define how each filter should be applied
40 | export const relationFilterMapping: FilterMapping> = {
41 | fromable_type: (item: IDataObject, value: unknown) => {
42 | return (
43 | typeof value === 'string' &&
44 | typeof item.fromable_type === 'string' &&
45 | item.fromable_type.toLowerCase() === value.toLowerCase()
46 | );
47 | },
48 | fromable_id: (item: IDataObject, value: unknown) => {
49 | return Number(item.fromable_id) === Number(value);
50 | },
51 | toable_type: (item: IDataObject, value: unknown) => {
52 | return (
53 | typeof value === 'string' &&
54 | typeof item.toable_type === 'string' &&
55 | item.toable_type.toLowerCase() === value.toLowerCase()
56 | );
57 | },
58 | toable_id: (item: IDataObject, value: unknown) => {
59 | return Number(item.toable_id) === Number(value);
60 | },
61 | is_inverse: (item: IDataObject, value: unknown) => {
62 | return Boolean(item.is_inverse) === Boolean(value);
63 | },
64 | };
65 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/uploads/uploads.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject, IHttpRequestMethods } from 'n8n-workflow';
2 | import { huduApiRequest } from '../../utils';
3 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
4 | import type { UploadOperation } from './uploads.types';
5 |
6 | export async function handleUploadOperation(
7 | this: IExecuteFunctions,
8 | operation: UploadOperation,
9 | i: number,
10 | ): Promise {
11 | let responseData: IDataObject | IDataObject[];
12 |
13 | switch (operation) {
14 | case 'getAll': {
15 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
16 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
17 |
18 | const response = await huduApiRequest.call(this, 'GET' as IHttpRequestMethods, '/uploads');
19 | responseData = Array.isArray(response) ? response : [response];
20 |
21 | if (!returnAll && responseData.length > limit) {
22 | responseData = responseData.slice(0, limit);
23 | }
24 | break;
25 | }
26 |
27 | case 'get': {
28 | const id = this.getNodeParameter('id', i) as number;
29 | responseData = await huduApiRequest.call(
30 | this,
31 | 'GET' as IHttpRequestMethods,
32 | `/uploads/${id}`,
33 | );
34 | break;
35 | }
36 |
37 | case 'delete': {
38 | const id = this.getNodeParameter('id', i) as number;
39 | responseData = await huduApiRequest.call(
40 | this,
41 | 'DELETE' as IHttpRequestMethods,
42 | `/uploads/${id}`,
43 | );
44 | break;
45 | }
46 | }
47 |
48 | return responseData;
49 | }
50 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/uploads/uploads.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IUpload extends IDataObject {
4 | id: number; // Unique identifier of the upload
5 | url: string; // URL where the file can be accessed
6 | name: string; // Name of the file
7 | ext: string; // File extension
8 | mime: string; // MIME type of the file
9 | size: string; // Size of the file
10 | created_date: string; // Date when the file was uploaded
11 | archived_at?: string; // Date when the file was archived (can be null)
12 | uploadable_id?: number; // ID of the object the file is associated with (can be null)
13 | uploadable_type?: string; // Type of the object the file is associated with (can be null)
14 | }
15 |
16 | export interface IUploadResponse extends IDataObject {
17 | upload: IUpload;
18 | }
19 |
20 | export type UploadOperation = 'getAll' | 'get' | 'delete';
21 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/users/users.handler.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { HUDU_API_CONSTANTS } from '../../utils/constants';
3 | import { handleGetOperation, handleGetAllOperation } from '../../utils/operations';
4 | import type { UserOperation } from './users.types';
5 |
6 | export async function handleUserOperation(
7 | this: IExecuteFunctions,
8 | operation: UserOperation,
9 | i: number,
10 | ): Promise {
11 | const resourceEndpoint = '/users';
12 |
13 | switch (operation) {
14 | case 'getAll': {
15 | const returnAll = this.getNodeParameter('returnAll', i) as boolean;
16 | const filters = this.getNodeParameter('filters', i) as IDataObject;
17 | const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
18 |
19 | return await handleGetAllOperation.call(
20 | this,
21 | resourceEndpoint,
22 | 'users',
23 | filters,
24 | returnAll,
25 | limit,
26 | );
27 | }
28 |
29 | case 'get': {
30 | const id = this.getNodeParameter('id', i) as number;
31 | return await handleGetOperation.call(this, resourceEndpoint, id);
32 | }
33 | }
34 |
35 | // This should never be reached due to TypeScript's exhaustive check
36 | throw new Error(`Unsupported operation ${operation}`);
37 | }
38 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/users/users.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IUser extends IDataObject {
4 | id: number; // The unique identifier of the user
5 | email: string; // The email address of the user
6 | otp_required_for_login: boolean; // Indicates if OTP is required for logging in
7 | security_level: string; // Security level assigned to the user
8 | first_name: string; // The first name of the user
9 | last_name: string; // The last name of the user
10 | phone_number?: string; // The phone number of the user (can be null)
11 | slug: string; // A slug representing the user
12 | time_zone?: string; // The time zone of the user (can be null)
13 | accepted_invite: boolean; // Indicates if the user has accepted an invite
14 | sign_in_count: number; // The number of times the user has signed in
15 | currently_signed_in: boolean; // Indicates if the user is currently signed in
16 | last_sign_in_at?: string; // Timestamp of the last sign-in (can be null)
17 | last_sign_in_ip?: string; // IP address from the last sign-in (can be null)
18 | created_at: string; // The timestamp when the user was created
19 | updated_at?: string; // The timestamp of the last user update (can be null)
20 | archived: boolean; // Indicates if the user is archived (discarded)
21 | portal_member_company_id?: number; // The ID of the associated company for portal members (can be null)
22 | score_30_days?: number; // The user's score over the past 30 days (can be null)
23 | score_all_time?: number; // The user's all-time score (can be null)
24 | score_90_days?: number; // The user's score over the past 90 days (can be null)
25 | }
26 |
27 | export interface IUserResponse extends IDataObject {
28 | user: IUser;
29 | }
30 |
31 | export type UserOperation =
32 | | 'getAll' // List first as it's the default operation
33 | | 'get'; // Get a single user by ID
34 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/vlan_zones/vlan_zones.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IVlanZone extends IDataObject {
4 | id?: number; // The unique identifier for the VLAN Zone
5 | name: string; // Human-readable zone name
6 | slug?: string; // URL-friendly identifier
7 | description?: string; // Optional description
8 | vlan_id_ranges: string; // Comma-separated list of numeric ranges (e.g. "100-500,1000-1500")
9 | company_id: number; // The identifier of the company that owns this VLAN Zone
10 | archived?: boolean; // Whether the VLAN Zone is archived (for create/update)
11 | archived_at?: string; // The date and time when the VLAN Zone was archived. Null if not archived.
12 | created_at?: string; // The date and time when the VLAN Zone was created.
13 | updated_at?: string; // The date and time when the VLAN Zone was last updated.
14 | vlans_count?: number; // Number of VLANs currently assigned to this zone
15 | url?: string; // Link to zone in the web UI
16 | }
17 |
18 | export interface IVlanZoneResponse extends IDataObject {
19 | vlan_zone: IVlanZone;
20 | }
21 |
22 | export type VlanZoneOperation = 'getAll' | 'get' | 'create' | 'update' | 'delete';
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/vlans/vlans.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IVlan extends IDataObject {
4 | id?: number; // The unique identifier for the VLAN
5 | name: string; // Human-readable VLAN name
6 | slug?: string; // URL-friendly identifier
7 | vlan_id: number; // Numeric VLAN (1-4094)
8 | description?: string; // Optional description
9 | notes?: string; // Rich-text notes
10 | company_id: number; // The identifier of the company that owns this VLAN
11 | vlan_zone_id?: number; // Zone (nullable)
12 | status_list_item_id?: number; // The status list item ID for this VLAN
13 | role_list_item_id?: number; // The role list item ID for this VLAN
14 | archived_at?: string; // The date and time when the VLAN was archived. Null if not archived.
15 | created_at?: string; // The date and time when the VLAN was created.
16 | updated_at?: string; // The date and time when the VLAN was last updated.
17 | networks_count?: number; // Number of networks currently assigned to this VLAN.
18 | url?: string; // Link to VLAN in the web UI
19 | }
20 |
21 | export interface IVlanResponse extends IDataObject {
22 | vlan: IVlan;
23 | }
24 |
25 | export type VlanOperation = 'getAll' | 'get' | 'create' | 'update' | 'delete';
--------------------------------------------------------------------------------
/src/nodes/Hudu/resources/websites/websites.types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export interface IWebsite extends IDataObject {
4 | id?: number; // The unique identifier of the website
5 | name: string; // The URL of the website
6 | code?: number; // The HTTP response code of the website
7 | message?: string; // A message related to the website's status
8 | slug?: string; // The URL slug for the website
9 | keyword?: string; // A keyword associated with the website (optional)
10 | monitor_type?: number; // The type of monitoring performed on the website
11 | status?: string; // The status of the website (e.g., 'ready', 'processing')
12 | monitoring_status?: string; // The monitoring status of the website (e.g., 'up', 'down')
13 | refreshed_at?: string; // The timestamp when the website was last refreshed
14 | monitored_at?: string; // The timestamp when the website was last monitored
15 | headers?: object; // HTTP headers associated with the website (optional)
16 | paused?: boolean; // Indicates whether the monitoring of the website is paused
17 | sent_notifications?: boolean; // Indicates whether notifications related to the website have been sent
18 | account_id?: number; // The ID of the associated account
19 | asset_field_id?: number; // The ID of the related asset field (optional)
20 | company_id?: number; // The ID of the associated company
21 | discarded_at?: string; // The timestamp when the website was discarded (optional)
22 | disable_ssl?: boolean; // Indicates whether SSL checks are disabled for the website
23 | disable_whois?: boolean; // Indicates whether WHOIS checks are disabled for the website
24 | disable_dns?: boolean; // Indicates whether DNS checks are disabled for the website
25 | notes?: string; // Additional notes related to the website
26 | object_type?: string; // The type of the object, in this case, 'Website'
27 | icon?: string; // The FontAwesome icon related to the website
28 | asset_type?: string; // The type of the asset, in this case, 'Website'
29 | company_name?: string; // The name of the associated company
30 | url?: string; // The URL path of the website within the application
31 | enable_dmarc_tracking?: boolean; // Indicates whether DMARC checks are enabled for the website
32 | enable_dkim_tracking?: boolean; // Indicates whether DKIM checks are enabled for the website
33 | enable_spf_tracking?: boolean; // Indicates whether SPF checks are enabled for the website
34 | }
35 |
36 | export interface IWebsiteResponse extends IDataObject {
37 | website: IWebsite;
38 | }
39 |
40 | export type WebsiteOperation = 'getAll' | 'get' | 'create' | 'update' | 'delete';
41 |
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/filterUtils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Filter utilities for Hudu API responses
3 | *
4 | * Provides functionality for:
5 | * - Post-processing API response data
6 | * - Type-safe filter function mapping
7 | * - Custom filter application to result sets
8 | * - Flexible filter chaining and composition
9 | */
10 |
11 | import type { IDataObject } from 'n8n-workflow';
12 | import { DEBUG_CONFIG, debugLog } from './debugConfig';
13 |
14 | /**
15 | * Type for filter mapping functions
16 | */
17 | export type FilterFunction = (item: IDataObject, value: T) => boolean;
18 |
19 | /**
20 | * Type for filter mappings object
21 | */
22 | export type FilterMapping = {
23 | [P in keyof T]: FilterFunction;
24 | };
25 |
26 | /**
27 | * Apply post-processing filters to results
28 | */
29 | export function applyPostFilters(
30 | results: IDataObject[],
31 | filters: T,
32 | filterMapping: Record boolean>,
33 | ): IDataObject[] {
34 | if (DEBUG_CONFIG.UTIL_FILTERS) {
35 | debugLog('Filter Processing - Input', {
36 | resultCount: results.length,
37 | filters,
38 | availableFilters: Object.keys(filterMapping),
39 | });
40 | }
41 |
42 | try {
43 | // Apply each filter in sequence
44 | let filteredResults = [...results];
45 |
46 | for (const [key, value] of Object.entries(filters)) {
47 | const filterFn = filterMapping[key];
48 | if (filterFn && value !== undefined && value !== '') {
49 | if (DEBUG_CONFIG.UTIL_FILTERS) {
50 | debugLog('Filter Processing - Applying Filter', {
51 | filter: key,
52 | value,
53 | resultCountBefore: filteredResults.length,
54 | });
55 | }
56 |
57 | filteredResults = filteredResults.filter(item => filterFn(item, value));
58 |
59 | if (DEBUG_CONFIG.UTIL_FILTERS) {
60 | debugLog('Filter Processing - Filter Applied', {
61 | filter: key,
62 | resultCountAfter: filteredResults.length,
63 | });
64 | }
65 | }
66 | }
67 |
68 | if (DEBUG_CONFIG.UTIL_FILTERS) {
69 | debugLog('Filter Processing - Final Results', {
70 | initialCount: results.length,
71 | finalCount: filteredResults.length,
72 | });
73 | }
74 |
75 | return filteredResults;
76 | } catch (error) {
77 | if (DEBUG_CONFIG.UTIL_FILTERS) {
78 | debugLog('Filter Processing - Error', {
79 | error,
80 | message: error instanceof Error ? error.message : String(error),
81 | level: 'error',
82 | });
83 | }
84 | return results;
85 | }
86 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/formatters.ts:
--------------------------------------------------------------------------------
1 | import { IDataObject } from 'n8n-workflow';
2 |
3 | /**
4 | * String formatting utilities
5 | */
6 |
7 | /**
8 | * Formats a string to title case and replaces underscores with spaces
9 | * Example: "hello_world_test" -> "Hello World Test"
10 | */
11 | export function formatTitleCase(str: string): string {
12 | // First split by underscores
13 | return str
14 | .split('_')
15 | // Then split any camelCase words
16 | .map(word => {
17 | return word
18 | .replace(/([A-Z])/g, ' $1') // Add space before capital letters
19 | .trim() // Remove leading space from first word
20 | .split(' ') // Split into array of words
21 | .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()) // Capitalize each word
22 | .join(' '); // Join back with spaces
23 | })
24 | .join(' ');
25 | }
26 |
27 | export function toSnakeCase(str: string): string {
28 | if (!str) return '';
29 | return str
30 | .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g) // Handle camelCase, PascalCase, and spaces
31 | ?.map(x => x.toLowerCase())
32 | .join('_') || '';
33 | }
34 |
35 | /**
36 | * Extract field value from an asset's fields array by field label
37 | * @param fields Array of fields from asset response
38 | * @param fieldLabel The label of the field to find
39 | * @returns The field value or null if not found
40 | */
41 | export function extractFieldValue(fields: IDataObject[], fieldLabel: string): any {
42 | if (!Array.isArray(fields)) return null;
43 |
44 | const field = fields.find(f => f.label === fieldLabel);
45 | return field ? field.value : null;
46 | }
47 |
48 | /**
49 | * Format custom fields for API payload according to Hudu API requirements
50 | * @param fields Object with field name/value pairs
51 | * @returns Array of single-key objects in the format required by Hudu API
52 | */
53 | export function formatCustomFields(fields: Record): IDataObject[] {
54 | return Object.entries(fields)
55 | .filter(([_, value]) => value !== null && value !== undefined)
56 | .map(([key, value]) => ({ [toSnakeCase(key)]: value }));
57 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Central export point for Hudu integration utilities
3 | *
4 | * Exports:
5 | * - Date utilities (dateUtils.ts)
6 | * - HTTP request utilities (requestUtils.ts)
7 | * - Filter utilities (filterUtils.ts)
8 | * - Constants and configuration (constants.ts)
9 | * - Validation utilities (validation.ts)
10 | *
11 | * Import all utilities from this file using:
12 | * import { functionName } from '../../utils';
13 | */
14 |
15 | export * from './dateUtils';
16 | export * from './requestUtils';
17 | export * from './filterUtils';
18 | export * from './constants';
19 | export * from './validation';
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/archive.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 | import { DEBUG_CONFIG, debugLog } from '../debugConfig';
4 |
5 | export async function handleArchiveOperation(
6 | this: IExecuteFunctions,
7 | resourceEndpoint: string,
8 | id: string | number,
9 | archive = true,
10 | companyId?: string | number,
11 | ): Promise {
12 | if (DEBUG_CONFIG.OPERATION_ARCHIVE) {
13 | debugLog('Archive Operation - Input', {
14 | endpoint: resourceEndpoint,
15 | id,
16 | companyId,
17 | action: archive ? 'archive' : 'unarchive',
18 | });
19 | }
20 |
21 | const endpoint = companyId
22 | ? `/companies/${companyId}${resourceEndpoint}/${id}/${archive ? 'archive' : 'unarchive'}`
23 | : `${resourceEndpoint}/${id}/${archive ? 'archive' : 'unarchive'}`;
24 |
25 | const response = await huduApiRequest.call(
26 | this,
27 | 'PUT',
28 | endpoint,
29 | );
30 |
31 | if (DEBUG_CONFIG.OPERATION_ARCHIVE) {
32 | debugLog('Archive Operation - Response', response);
33 | }
34 |
35 | return response;
36 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/cards.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 |
4 | export async function handleCardLookupOperation(
5 | this: IExecuteFunctions,
6 | integrationSlug: string,
7 | additionalParams: IDataObject = {},
8 | ): Promise {
9 | const queryParams: IDataObject = {
10 | integration_slug: integrationSlug,
11 | ...additionalParams,
12 | };
13 |
14 | const response = await huduApiRequest.call(
15 | this,
16 | 'GET',
17 | '/cards/lookup',
18 | undefined,
19 | queryParams,
20 | );
21 |
22 | return (response as { integrator_cards: IDataObject[] }).integrator_cards || [];
23 | }
24 |
25 | export async function handleCardJumpOperation(
26 | this: IExecuteFunctions,
27 | integrationType: string,
28 | integrationSlug: string,
29 | additionalParams: IDataObject = {},
30 | ): Promise {
31 | const queryParams: IDataObject = {
32 | integration_type: integrationType,
33 | integration_slug: integrationSlug,
34 | ...additionalParams,
35 | };
36 |
37 | return await huduApiRequest.call(
38 | this,
39 | 'GET',
40 | '/cards/jump',
41 | undefined,
42 | queryParams,
43 | );
44 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/companies.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 |
4 | export async function handleCompanyJumpOperation(
5 | this: IExecuteFunctions,
6 | integrationSlug: string,
7 | additionalParams: IDataObject = {},
8 | ): Promise {
9 | const queryParams: IDataObject = {
10 | integration_slug: integrationSlug,
11 | };
12 |
13 | if (additionalParams.integrationId) {
14 | queryParams.integration_id = additionalParams.integrationId;
15 | }
16 |
17 | if (additionalParams.integrationIdentifier) {
18 | queryParams.integration_identifier = additionalParams.integrationIdentifier;
19 | }
20 |
21 | return await huduApiRequest.call(
22 | this,
23 | 'GET',
24 | '/companies/jump',
25 | {},
26 | queryParams,
27 | );
28 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/create.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 | import { DEBUG_CONFIG, debugLog } from '../debugConfig';
4 |
5 | export async function handleCreateOperation(
6 | this: IExecuteFunctions,
7 | resourceEndpoint: string,
8 | body: IDataObject,
9 | ): Promise {
10 | if (DEBUG_CONFIG.OPERATION_CREATE) {
11 | debugLog('Create Operation - Input', {
12 | endpoint: resourceEndpoint,
13 | body,
14 | });
15 | }
16 |
17 | const response = await huduApiRequest.call(
18 | this,
19 | 'POST',
20 | resourceEndpoint,
21 | body,
22 | );
23 |
24 | if (DEBUG_CONFIG.OPERATION_CREATE) {
25 | debugLog('Create Operation - Response', response);
26 | }
27 |
28 | return response;
29 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/delete.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 | import { DEBUG_CONFIG, debugLog } from '../debugConfig';
4 |
5 | export async function handleDeleteOperation(
6 | this: IExecuteFunctions,
7 | resourceEndpoint: string,
8 | id: string | number,
9 | companyId?: string | number,
10 | ): Promise {
11 | if (DEBUG_CONFIG.OPERATION_DELETE) {
12 | debugLog('Delete Operation - Input', {
13 | endpoint: resourceEndpoint,
14 | id,
15 | companyId,
16 | });
17 | }
18 |
19 | const endpoint = companyId
20 | ? `/companies/${companyId}${resourceEndpoint}/${id}`
21 | : `${resourceEndpoint}/${id}`;
22 |
23 | const response = await huduApiRequest.call(
24 | this,
25 | 'DELETE',
26 | endpoint,
27 | );
28 |
29 | if (DEBUG_CONFIG.OPERATION_DELETE) {
30 | debugLog('Delete Operation - Response', response);
31 | }
32 |
33 | return response;
34 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/get.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, ILoadOptionsFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 | import { DEBUG_CONFIG, debugLog } from '../debugConfig';
4 |
5 | export async function handleGetOperation(
6 | this: IExecuteFunctions | ILoadOptionsFunctions,
7 | resourceEndpoint: string,
8 | id: string | number,
9 | ): Promise {
10 | if (DEBUG_CONFIG.OPERATION_GET) {
11 | debugLog('Get Operation - Input', {
12 | endpoint: resourceEndpoint,
13 | id,
14 | });
15 | }
16 |
17 | const response = await huduApiRequest.call(
18 | this,
19 | 'GET',
20 | `${resourceEndpoint}/${id}`,
21 | );
22 |
23 | if (DEBUG_CONFIG.OPERATION_GET) {
24 | debugLog('Get Operation - Response', response);
25 | }
26 |
27 | return response;
28 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/getAll.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { handleListing } from '../requestUtils';
3 | import type { FilterMapping } from '../types';
4 | import { DEBUG_CONFIG, debugLog } from '../debugConfig';
5 |
6 | export async function handleGetAllOperation(
7 | this: IExecuteFunctions,
8 | resourceEndpoint: string,
9 | resourceName: string | undefined,
10 | filters: IDataObject = {},
11 | returnAll = false,
12 | limit = 25,
13 | postProcessFilters?: IDataObject,
14 | filterMapping?: FilterMapping,
15 | ): Promise {
16 | if (DEBUG_CONFIG.OPERATION_GET_ALL) {
17 | debugLog('GetAll Operation - Input', {
18 | endpoint: resourceEndpoint,
19 | resourceName,
20 | filters,
21 | returnAll,
22 | limit,
23 | hasPostProcessFilters: !!postProcessFilters,
24 | hasFilterMapping: !!filterMapping,
25 | });
26 | }
27 |
28 | const results = await handleListing.call(
29 | this,
30 | 'GET',
31 | resourceEndpoint,
32 | resourceName,
33 | {},
34 | filters,
35 | returnAll,
36 | limit,
37 | postProcessFilters,
38 | filterMapping,
39 | );
40 |
41 | if (DEBUG_CONFIG.OPERATION_GET_ALL) {
42 | debugLog('GetAll Operation - Response', {
43 | totalResults: Array.isArray(results) ? results.length : 1,
44 | results,
45 | });
46 | }
47 |
48 | return Array.isArray(results) ? results : [results];
49 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/getCompanyIdForAsset.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { NodeOperationError } from 'n8n-workflow';
3 | import { huduApiRequest } from '../requestUtils';
4 | import { debugLog } from '../debugConfig';
5 |
6 | /**
7 | * Fetches an asset by ID and returns its company ID and the asset object.
8 | * Throws a NodeOperationError if the asset is not found or has no company_id.
9 | *
10 | * @param context n8n execution context (this)
11 | * @param assetId The asset ID to look up
12 | * @param itemIndex Optional item index for error context
13 | * @returns { companyId: string | number, assetObject: IDataObject }
14 | */
15 | export async function getCompanyIdForAsset(
16 | context: IExecuteFunctions,
17 | assetId: string | number,
18 | itemIndex?: number,
19 | ): Promise<{ companyId: string | number, assetObject: IDataObject }> {
20 | debugLog('[RESOURCE_PROCESSING] Looking up company_id for asset', { assetId });
21 | const assetLookup = await huduApiRequest.call(context, 'GET', '/assets', {}, { id: String(assetId) });
22 | const assetsArray = (assetLookup as IDataObject).assets as IDataObject[];
23 | if (!assetsArray?.length) {
24 | throw new NodeOperationError(context.getNode(), `No asset found with ID '${assetId}'`, { itemIndex });
25 | }
26 | const assetObject = assetsArray[0];
27 | const companyId = assetObject.company_id;
28 | if (typeof companyId !== 'string' && typeof companyId !== 'number') {
29 | throw new NodeOperationError(context.getNode(), `Asset with ID '${assetId}' has an invalid company_id type.`, { itemIndex });
30 | }
31 | debugLog('[RESOURCE_PROCESSING] Found company_id for asset', { assetId, companyId });
32 | return { companyId, assetObject };
33 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getAll';
2 | export * from './get';
3 | export * from './create';
4 | export * from './update';
5 | export * from './delete';
6 | export * from './archive';
7 | export * from './systemInfo';
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/magic_dash.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 | import { HUDU_API_CONSTANTS } from '../constants';
4 |
5 | export async function handleMagicDashGetAllOperation(
6 | this: IExecuteFunctions,
7 | filters: IDataObject = {},
8 | returnAll = false,
9 | limit = HUDU_API_CONSTANTS.PAGE_SIZE,
10 | ): Promise {
11 | let allItems: IDataObject[] = [];
12 | let page = 1;
13 | const pageSize = HUDU_API_CONSTANTS.PAGE_SIZE;
14 |
15 | let hasMorePages = true;
16 | do {
17 | const qs: IDataObject = {
18 | page,
19 | page_size: pageSize,
20 | };
21 |
22 | // Handle filters
23 | if (filters.company_id) {
24 | qs.company_id = Number.parseInt(filters.company_id as string, 10);
25 | }
26 | if (filters.title) {
27 | qs.title = filters.title;
28 | }
29 |
30 | const response = await huduApiRequest.call(
31 | this,
32 | 'GET',
33 | '/magic_dash',
34 | {},
35 | qs,
36 | );
37 |
38 | const items = Array.isArray(response) ? response : [];
39 | allItems.push(...items);
40 |
41 | if (items.length < pageSize || (!returnAll && allItems.length >= limit)) {
42 | hasMorePages = false;
43 | } else {
44 | page++;
45 | }
46 | } while (hasMorePages);
47 |
48 | if (!returnAll) {
49 | allItems = allItems.slice(0, limit);
50 | }
51 |
52 | return allItems;
53 | }
54 |
55 | export async function handleMagicDashGetByIdOperation(
56 | this: IExecuteFunctions,
57 | id: number,
58 | ): Promise {
59 | const response = await huduApiRequest.call(this, 'GET', '/magic_dash');
60 |
61 | const items = Array.isArray(response) ? response : [];
62 | const item = items.find((item) => item.id === id);
63 |
64 | if (!item) {
65 | throw new Error(`Magic Dash item with ID ${id} not found`);
66 | }
67 | return item;
68 | }
69 |
70 | export async function handleMagicDashDeleteByTitleOperation(
71 | this: IExecuteFunctions,
72 | title: string,
73 | companyName: string,
74 | ): Promise {
75 | const body = {
76 | title,
77 | company_name: companyName,
78 | };
79 |
80 | return await huduApiRequest.call(
81 | this,
82 | 'DELETE',
83 | '/magic_dash',
84 | body,
85 | );
86 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/matchers.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 |
4 | export async function handleMatcherGetAllOperation(
5 | this: IExecuteFunctions,
6 | integrationId: number,
7 | filters: IDataObject = {},
8 | returnAll = false,
9 | limit = 25,
10 | ): Promise {
11 | const queryParams: IDataObject = {
12 | integration_id: integrationId,
13 | };
14 |
15 | // Add optional filters
16 | if (filters.matched !== undefined) {
17 | queryParams.matched = filters.matched;
18 | }
19 | if (filters.sync_id !== undefined) {
20 | queryParams.sync_id = filters.sync_id;
21 | }
22 | if (filters.identifier !== undefined) {
23 | queryParams.identifier = filters.identifier;
24 | }
25 | if (filters.company_id !== undefined) {
26 | queryParams.company_id = Number.parseInt(filters.company_id as string, 10);
27 | }
28 | if (!returnAll) {
29 | queryParams.page_size = limit;
30 | }
31 |
32 | const response = await huduApiRequest.call(
33 | this,
34 | 'GET',
35 | '/matchers',
36 | undefined,
37 | queryParams,
38 | );
39 |
40 | // Extract the matchers array from the response
41 | let matchers = Array.isArray(response)
42 | ? response
43 | : ((response as IDataObject).matchers as IDataObject[]) || [];
44 |
45 | if (!returnAll && matchers.length > limit) {
46 | matchers = matchers.slice(0, limit);
47 | }
48 |
49 | return matchers;
50 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/procedures.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 |
4 | export async function handleProcedureCreateFromTemplateOperation(
5 | this: IExecuteFunctions,
6 | templateId: string,
7 | additionalFields: IDataObject = {},
8 | ): Promise {
9 | return await huduApiRequest.call(
10 | this,
11 | 'POST',
12 | `/procedures/${templateId}/create_from_template`,
13 | additionalFields,
14 | );
15 | }
16 |
17 | export async function handleProcedureDuplicateOperation(
18 | this: IExecuteFunctions,
19 | procedureId: string,
20 | companyId: number,
21 | additionalFields: IDataObject = {},
22 | ): Promise {
23 | const body: IDataObject = {
24 | company_id: companyId,
25 | ...additionalFields,
26 | };
27 |
28 | return await huduApiRequest.call(
29 | this,
30 | 'POST',
31 | `/procedures/${procedureId}/duplicate`,
32 | body,
33 | );
34 | }
35 |
36 | export async function handleProcedureKickoffOperation(
37 | this: IExecuteFunctions,
38 | procedureId: string,
39 | additionalFields: IDataObject,
40 | ): Promise {
41 | const qs: IDataObject = {};
42 |
43 | if (additionalFields.asset_id) {
44 | qs.asset_id = additionalFields.asset_id;
45 | }
46 |
47 | if (additionalFields.name) {
48 | qs.name = additionalFields.name;
49 | }
50 |
51 | return await huduApiRequest.call(
52 | this,
53 | 'POST',
54 | `/procedures/${procedureId}/kickoff`,
55 | {},
56 | qs,
57 | );
58 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/systemInfo.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 |
4 | export async function handleSystemInfoOperation(
5 | this: IExecuteFunctions,
6 | endpoint: string,
7 | ): Promise {
8 | return await huduApiRequest.call(
9 | this,
10 | 'GET',
11 | endpoint,
12 | );
13 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/operations/update.ts:
--------------------------------------------------------------------------------
1 | import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2 | import { huduApiRequest } from '../requestUtils';
3 | import { DEBUG_CONFIG, debugLog } from '../debugConfig';
4 |
5 | export async function handleUpdateOperation(
6 | this: IExecuteFunctions,
7 | resourceEndpoint: string,
8 | id: string | number,
9 | body: IDataObject,
10 | ): Promise {
11 | if (DEBUG_CONFIG.OPERATION_UPDATE) {
12 | debugLog('Update Operation - Input', {
13 | endpoint: resourceEndpoint,
14 | id,
15 | body,
16 | });
17 | }
18 |
19 | const response = await huduApiRequest.call(
20 | this,
21 | 'PUT',
22 | `${resourceEndpoint}/${id}`,
23 | body,
24 | );
25 |
26 | if (DEBUG_CONFIG.OPERATION_UPDATE) {
27 | debugLog('Update Operation - Response', response);
28 | }
29 |
30 | return response;
31 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/types.ts:
--------------------------------------------------------------------------------
1 | import type { IDataObject } from 'n8n-workflow';
2 |
3 | export type FilterFunction = (item: IDataObject, value: T[keyof T]) => boolean;
4 |
5 | export interface FilterMapping {
6 | [key: string]: FilterFunction;
7 | }
--------------------------------------------------------------------------------
/src/nodes/Hudu/utils/validation.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Validation utilities for Hudu integration
3 | *
4 | * Provides functionality for:
5 | * - Input validation for common fields
6 | * - Type checking and conversion
7 | * - Standardised error messages
8 | */
9 |
10 | import type { INode } from 'n8n-workflow';
11 | import { NodeOperationError } from 'n8n-workflow';
12 |
13 | /**
14 | * Validates and converts a company ID value to a number
15 | * Throws an error if validation fails
16 | */
17 | export function validateCompanyId(value: unknown, node: INode, fieldName: string = 'Company ID'): number {
18 | // Handle empty values
19 | if (value === undefined || value === null || (typeof value === 'string' && value.trim() === '')) {
20 | throw new NodeOperationError(node, `${fieldName} cannot be empty`);
21 | }
22 |
23 | // Convert to number and validate
24 | const numValue = Number(value);
25 | if (isNaN(numValue) || !Number.isInteger(numValue) || numValue < 1) {
26 | throw new NodeOperationError(
27 | node,
28 | `Invalid ${fieldName}: "${value}". Expected a positive integer. If you are using expressions, make sure to reference the ID field.`
29 | );
30 | }
31 |
32 | return numValue;
33 | }
34 |
35 | /**
36 | * Type guard to check if a value is a valid company ID
37 | */
38 | export function isValidCompanyId(value: unknown): value is number {
39 | if (value === undefined || value === null || (typeof value === 'string' && value.trim() === '')) {
40 | return false;
41 | }
42 |
43 | const numValue = Number(value);
44 | return !isNaN(numValue) && Number.isInteger(numValue) && numValue > 0;
45 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "target": "es2019",
7 | "lib": ["es2015", "es2019", "es2020", "es2022.error"],
8 | "removeComments": true,
9 | "useUnknownInCatchVariables": false,
10 | "forceConsistentCasingInFileNames": true,
11 | "noImplicitAny": true,
12 | "noImplicitReturns": true,
13 | "noUnusedLocals": true,
14 | "noErrorTruncation": true,
15 | "esModuleInterop": true,
16 | "skipLibCheck": true,
17 | "sourceMap": true,
18 | "outDir": "./dist",
19 | "rootDir": ".",
20 | "types": ["node"],
21 | "typeRoots": ["node_modules/@types"],
22 | "resolveJsonModule": true,
23 | "baseUrl": ".",
24 | "paths": {
25 | "@/*": ["src/*"]
26 | },
27 | "preserveSymlinks": true
28 | },
29 | "include": ["credentials/**/*", "src/**/*", "index.ts", "tests/**/*"],
30 | "exclude": ["node_modules/**/*", "dist/**/*"]
31 | }
32 |
--------------------------------------------------------------------------------