n8n-nodes-dominusnode 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/nodes/DominusNodeAccount/DominusNodeAccount.node.d.ts +23 -0
- package/dist/nodes/DominusNodeAccount/DominusNodeAccount.node.js +223 -0
- package/dist/nodes/DominusNodeAccount/DominusNodeAccount.node.js.map +1 -0
- package/dist/nodes/DominusNodeKeys/DominusNodeKeys.node.d.ts +20 -0
- package/dist/nodes/DominusNodeKeys/DominusNodeKeys.node.js +145 -0
- package/dist/nodes/DominusNodeKeys/DominusNodeKeys.node.js.map +1 -0
- package/dist/nodes/DominusNodePlans/DominusNodePlans.node.d.ts +19 -0
- package/dist/nodes/DominusNodePlans/DominusNodePlans.node.js +123 -0
- package/dist/nodes/DominusNodePlans/DominusNodePlans.node.js.map +1 -0
- package/dist/nodes/DominusNodeProxy/DominusNodeProxy.node.d.ts +2 -1
- package/dist/nodes/DominusNodeProxy/DominusNodeProxy.node.js +12 -1
- package/dist/nodes/DominusNodeProxy/DominusNodeProxy.node.js.map +1 -1
- package/dist/nodes/DominusNodeTeams/DominusNodeTeams.node.d.ts +27 -0
- package/dist/nodes/DominusNodeTeams/DominusNodeTeams.node.js +258 -0
- package/dist/nodes/DominusNodeTeams/DominusNodeTeams.node.js.map +1 -0
- package/dist/nodes/DominusNodeUsage/DominusNodeUsage.node.d.ts +3 -1
- package/dist/nodes/DominusNodeUsage/DominusNodeUsage.node.js +47 -1
- package/dist/nodes/DominusNodeUsage/DominusNodeUsage.node.js.map +1 -1
- package/dist/nodes/DominusNodeWallet/DominusNodeWallet.node.d.ts +5 -1
- package/dist/nodes/DominusNodeWallet/DominusNodeWallet.node.js +44 -1
- package/dist/nodes/DominusNodeWallet/DominusNodeWallet.node.js.map +1 -1
- package/package.json +7 -3
- package/src/index.ts +4 -0
- package/src/nodes/DominusNodeAccount/DominusNodeAccount.node.ts +283 -0
- package/src/nodes/DominusNodeKeys/DominusNodeKeys.node.ts +192 -0
- package/src/nodes/DominusNodePlans/DominusNodePlans.node.ts +154 -0
- package/src/nodes/DominusNodeProxy/DominusNodeProxy.node.ts +11 -1
- package/src/nodes/DominusNodeTeams/DominusNodeTeams.node.ts +351 -0
- package/src/nodes/DominusNodeUsage/DominusNodeUsage.node.ts +53 -1
- package/src/nodes/DominusNodeWallet/DominusNodeWallet.node.ts +52 -1
- package/tests/DominusNodeProxy.test.ts +2 -2
- package/tests/DominusNodeUsage.test.ts +2 -2
- package/tests/DominusNodeWallet.test.ts +2 -2
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dominus Node Plans n8n community node.
|
|
3
|
+
*
|
|
4
|
+
* Operations (3 tools):
|
|
5
|
+
* - Get Plan: Get current user plan
|
|
6
|
+
* - List Plans: List all available plans
|
|
7
|
+
* - Change Plan: Switch to a different plan
|
|
8
|
+
*
|
|
9
|
+
* Security:
|
|
10
|
+
* - UUID validation for plan IDs
|
|
11
|
+
* - Credential sanitization in error messages
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
IDataObject,
|
|
18
|
+
IExecuteFunctions,
|
|
19
|
+
INodeExecutionData,
|
|
20
|
+
INodeType,
|
|
21
|
+
INodeTypeDescription,
|
|
22
|
+
NodeOperationError,
|
|
23
|
+
} from "n8n-workflow";
|
|
24
|
+
|
|
25
|
+
import { DominusNodeAuth, sanitizeError } from "../../shared/auth";
|
|
26
|
+
|
|
27
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
28
|
+
|
|
29
|
+
export class DominusNodePlans implements INodeType {
|
|
30
|
+
description: INodeTypeDescription = {
|
|
31
|
+
displayName: "Dominus Node Plans",
|
|
32
|
+
name: "dominusNodePlans",
|
|
33
|
+
icon: "file:dominusnode.svg",
|
|
34
|
+
group: ["transform"],
|
|
35
|
+
version: 1,
|
|
36
|
+
subtitle: '={{$parameter["operation"]}}',
|
|
37
|
+
description: "Manage Dominus Node subscription plans",
|
|
38
|
+
defaults: { name: "Dominus Node Plans" },
|
|
39
|
+
inputs: ["main"],
|
|
40
|
+
outputs: ["main"],
|
|
41
|
+
credentials: [{ name: "dominusNodeApi", required: true }],
|
|
42
|
+
properties: [
|
|
43
|
+
{
|
|
44
|
+
displayName: "Operation",
|
|
45
|
+
name: "operation",
|
|
46
|
+
type: "options",
|
|
47
|
+
noDataExpression: true,
|
|
48
|
+
options: [
|
|
49
|
+
{ name: "Get Plan", value: "getPlan", description: "Get current user plan", action: "Get plan" },
|
|
50
|
+
{ name: "List Plans", value: "listPlans", description: "List all available plans", action: "List plans" },
|
|
51
|
+
{ name: "Change Plan", value: "changePlan", description: "Switch to a different plan", action: "Change plan" },
|
|
52
|
+
],
|
|
53
|
+
default: "getPlan",
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// --- Change Plan ---
|
|
57
|
+
{
|
|
58
|
+
displayName: "Plan ID",
|
|
59
|
+
name: "planId",
|
|
60
|
+
type: "string",
|
|
61
|
+
default: "",
|
|
62
|
+
required: true,
|
|
63
|
+
description: "UUID of the plan to switch to",
|
|
64
|
+
displayOptions: { show: { operation: ["changePlan"] } },
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
70
|
+
const items = this.getInputData();
|
|
71
|
+
const returnData: INodeExecutionData[] = [];
|
|
72
|
+
const credentials = await this.getCredentials("dominusNodeApi");
|
|
73
|
+
|
|
74
|
+
const apiKey = credentials.apiKey as string;
|
|
75
|
+
const baseUrl = (credentials.baseUrl as string) || "https://api.dominusnode.com";
|
|
76
|
+
|
|
77
|
+
if (!apiKey) {
|
|
78
|
+
throw new NodeOperationError(this.getNode(), "API Key is required");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const agentSecret = (credentials.agentSecret as string) || undefined;
|
|
82
|
+
const auth = new DominusNodeAuth(apiKey, baseUrl, 30000, agentSecret);
|
|
83
|
+
const operation = this.getNodeParameter("operation", 0) as string;
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < items.length; i++) {
|
|
86
|
+
try {
|
|
87
|
+
let result: unknown;
|
|
88
|
+
|
|
89
|
+
switch (operation) {
|
|
90
|
+
case "getPlan": {
|
|
91
|
+
result = await auth.apiRequest("GET", "/api/plans/user/plan");
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
case "listPlans": {
|
|
96
|
+
result = await auth.apiRequest("GET", "/api/plans");
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
case "changePlan": {
|
|
101
|
+
const planId = this.getNodeParameter("planId", i) as string;
|
|
102
|
+
validateUuid(this, planId, "planId", i);
|
|
103
|
+
result = await auth.apiRequest("PUT", "/api/plans/user/plan", { planId });
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
throw new NodeOperationError(
|
|
109
|
+
this.getNode(),
|
|
110
|
+
`Unknown operation: ${operation}`,
|
|
111
|
+
{ itemIndex: i },
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
returnData.push({ json: (result ?? {}) as IDataObject });
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (this.continueOnFail()) {
|
|
118
|
+
returnData.push({
|
|
119
|
+
json: {
|
|
120
|
+
error: sanitizeError(err instanceof Error ? err.message : String(err)),
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (err instanceof NodeOperationError) throw err;
|
|
126
|
+
throw new NodeOperationError(
|
|
127
|
+
this.getNode(),
|
|
128
|
+
sanitizeError(err instanceof Error ? err.message : String(err)),
|
|
129
|
+
{ itemIndex: i },
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return [returnData];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Validation helpers
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
function validateUuid(
|
|
143
|
+
ctx: IExecuteFunctions,
|
|
144
|
+
value: string,
|
|
145
|
+
fieldName: string,
|
|
146
|
+
itemIndex: number,
|
|
147
|
+
): void {
|
|
148
|
+
if (!value || typeof value !== "string") {
|
|
149
|
+
throw new NodeOperationError(ctx.getNode(), `${fieldName} is required`, { itemIndex });
|
|
150
|
+
}
|
|
151
|
+
if (!UUID_RE.test(value)) {
|
|
152
|
+
throw new NodeOperationError(ctx.getNode(), `${fieldName} must be a valid UUID`, { itemIndex });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dominus Node Proxy n8n community node.
|
|
3
3
|
*
|
|
4
|
-
* Operations:
|
|
4
|
+
* Operations (4 tools):
|
|
5
5
|
* - Proxied Fetch: Make HTTP requests through Dominus Node's rotating proxy network
|
|
6
6
|
* - Get Proxy Config: Retrieve proxy endpoint configuration
|
|
7
7
|
* - List Active Sessions: List currently active proxy sessions
|
|
8
|
+
* - Get Proxy Status: Get proxy pool health and availability status
|
|
8
9
|
*
|
|
9
10
|
* Security:
|
|
10
11
|
* - Full SSRF prevention (private IPs, hex/octal/decimal, IPv6 variants,
|
|
@@ -86,6 +87,12 @@ export class DominusNodeProxy implements INodeType {
|
|
|
86
87
|
description: "List currently active proxy sessions",
|
|
87
88
|
action: "List active sessions",
|
|
88
89
|
},
|
|
90
|
+
{
|
|
91
|
+
name: "Get Proxy Status",
|
|
92
|
+
value: "getProxyStatus",
|
|
93
|
+
description: "Get proxy pool health and availability status",
|
|
94
|
+
action: "Get proxy status",
|
|
95
|
+
},
|
|
89
96
|
],
|
|
90
97
|
default: "proxiedFetch",
|
|
91
98
|
},
|
|
@@ -437,6 +444,9 @@ export class DominusNodeProxy implements INodeType {
|
|
|
437
444
|
} else if (operation === "listActiveSessions") {
|
|
438
445
|
const result = await auth.apiRequest("GET", "/api/sessions/active");
|
|
439
446
|
returnData.push({ json: result as IDataObject });
|
|
447
|
+
} else if (operation === "getProxyStatus") {
|
|
448
|
+
const result = await auth.apiRequest("GET", "/api/proxy/status");
|
|
449
|
+
returnData.push({ json: result as IDataObject });
|
|
440
450
|
}
|
|
441
451
|
} catch (err) {
|
|
442
452
|
if (this.continueOnFail()) {
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dominus Node Teams n8n community node.
|
|
3
|
+
*
|
|
4
|
+
* Operations (9 tools):
|
|
5
|
+
* - Delete Team: Delete a team
|
|
6
|
+
* - Revoke Team Key: Revoke a team API key
|
|
7
|
+
* - List Team Keys: List all API keys for a team
|
|
8
|
+
* - List Team Members: List all members of a team
|
|
9
|
+
* - Add Team Member: Add a user to a team
|
|
10
|
+
* - Remove Team Member: Remove a user from a team
|
|
11
|
+
* - Invite Team Member: Send an email invitation to join a team
|
|
12
|
+
* - List Team Invites: List pending invitations for a team
|
|
13
|
+
* - Cancel Team Invite: Cancel a pending team invitation
|
|
14
|
+
*
|
|
15
|
+
* Security:
|
|
16
|
+
* - UUID validation for team/user/key/invite IDs
|
|
17
|
+
* - Email validation for invitations
|
|
18
|
+
* - Control character rejection
|
|
19
|
+
* - Credential sanitization in error messages
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
IDataObject,
|
|
26
|
+
IExecuteFunctions,
|
|
27
|
+
INodeExecutionData,
|
|
28
|
+
INodeType,
|
|
29
|
+
INodeTypeDescription,
|
|
30
|
+
NodeOperationError,
|
|
31
|
+
} from "n8n-workflow";
|
|
32
|
+
|
|
33
|
+
import { DominusNodeAuth, sanitizeError } from "../../shared/auth";
|
|
34
|
+
|
|
35
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
36
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
37
|
+
|
|
38
|
+
export class DominusNodeTeams implements INodeType {
|
|
39
|
+
description: INodeTypeDescription = {
|
|
40
|
+
displayName: "Dominus Node Teams",
|
|
41
|
+
name: "dominusNodeTeams",
|
|
42
|
+
icon: "file:dominusnode.svg",
|
|
43
|
+
group: ["transform"],
|
|
44
|
+
version: 1,
|
|
45
|
+
subtitle: '={{$parameter["operation"]}}',
|
|
46
|
+
description: "Manage Dominus Node team members, keys, and invitations",
|
|
47
|
+
defaults: { name: "Dominus Node Teams" },
|
|
48
|
+
inputs: ["main"],
|
|
49
|
+
outputs: ["main"],
|
|
50
|
+
credentials: [{ name: "dominusNodeApi", required: true }],
|
|
51
|
+
properties: [
|
|
52
|
+
{
|
|
53
|
+
displayName: "Operation",
|
|
54
|
+
name: "operation",
|
|
55
|
+
type: "options",
|
|
56
|
+
noDataExpression: true,
|
|
57
|
+
options: [
|
|
58
|
+
{ name: "Delete Team", value: "teamDelete", description: "Delete a team", action: "Delete team" },
|
|
59
|
+
{ name: "Revoke Team Key", value: "teamRevokeKey", description: "Revoke a team API key", action: "Revoke team key" },
|
|
60
|
+
{ name: "List Team Keys", value: "teamListKeys", description: "List all API keys for a team", action: "List team keys" },
|
|
61
|
+
{ name: "List Team Members", value: "teamListMembers", description: "List all members of a team", action: "List team members" },
|
|
62
|
+
{ name: "Add Team Member", value: "teamAddMember", description: "Add a user to a team", action: "Add team member" },
|
|
63
|
+
{ name: "Remove Team Member", value: "teamRemoveMember", description: "Remove a user from a team", action: "Remove team member" },
|
|
64
|
+
{ name: "Invite Team Member", value: "teamInviteMember", description: "Send an email invitation to join a team", action: "Invite team member" },
|
|
65
|
+
{ name: "List Team Invites", value: "teamListInvites", description: "List pending invitations for a team", action: "List team invites" },
|
|
66
|
+
{ name: "Cancel Team Invite", value: "teamCancelInvite", description: "Cancel a pending team invitation", action: "Cancel team invite" },
|
|
67
|
+
],
|
|
68
|
+
default: "teamListMembers",
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// --- Team ID (shared) ---
|
|
72
|
+
{
|
|
73
|
+
displayName: "Team ID",
|
|
74
|
+
name: "teamId",
|
|
75
|
+
type: "string",
|
|
76
|
+
default: "",
|
|
77
|
+
required: true,
|
|
78
|
+
description: "Team UUID",
|
|
79
|
+
displayOptions: {
|
|
80
|
+
show: {
|
|
81
|
+
operation: [
|
|
82
|
+
"teamDelete",
|
|
83
|
+
"teamRevokeKey",
|
|
84
|
+
"teamListKeys",
|
|
85
|
+
"teamListMembers",
|
|
86
|
+
"teamAddMember",
|
|
87
|
+
"teamRemoveMember",
|
|
88
|
+
"teamInviteMember",
|
|
89
|
+
"teamListInvites",
|
|
90
|
+
"teamCancelInvite",
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// --- Key ID for revoke ---
|
|
97
|
+
{
|
|
98
|
+
displayName: "Key ID",
|
|
99
|
+
name: "keyId",
|
|
100
|
+
type: "string",
|
|
101
|
+
default: "",
|
|
102
|
+
required: true,
|
|
103
|
+
description: "UUID of the team API key to revoke",
|
|
104
|
+
displayOptions: { show: { operation: ["teamRevokeKey"] } },
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// --- User ID for add/remove member ---
|
|
108
|
+
{
|
|
109
|
+
displayName: "User ID",
|
|
110
|
+
name: "userId",
|
|
111
|
+
type: "string",
|
|
112
|
+
default: "",
|
|
113
|
+
required: true,
|
|
114
|
+
description: "UUID of the user to add or remove",
|
|
115
|
+
displayOptions: { show: { operation: ["teamAddMember", "teamRemoveMember"] } },
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// --- Role for add member / invite ---
|
|
119
|
+
{
|
|
120
|
+
displayName: "Role",
|
|
121
|
+
name: "role",
|
|
122
|
+
type: "options",
|
|
123
|
+
options: [
|
|
124
|
+
{ name: "Admin", value: "admin" },
|
|
125
|
+
{ name: "Member", value: "member" },
|
|
126
|
+
],
|
|
127
|
+
default: "member",
|
|
128
|
+
description: "Role for the team member",
|
|
129
|
+
displayOptions: { show: { operation: ["teamAddMember", "teamInviteMember"] } },
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// --- Invite email ---
|
|
133
|
+
{
|
|
134
|
+
displayName: "Email",
|
|
135
|
+
name: "inviteEmail",
|
|
136
|
+
type: "string",
|
|
137
|
+
default: "",
|
|
138
|
+
required: true,
|
|
139
|
+
description: "Email address to send the invitation to",
|
|
140
|
+
displayOptions: { show: { operation: ["teamInviteMember"] } },
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// --- Invite ID for cancel ---
|
|
144
|
+
{
|
|
145
|
+
displayName: "Invite ID",
|
|
146
|
+
name: "inviteId",
|
|
147
|
+
type: "string",
|
|
148
|
+
default: "",
|
|
149
|
+
required: true,
|
|
150
|
+
description: "UUID of the invitation to cancel",
|
|
151
|
+
displayOptions: { show: { operation: ["teamCancelInvite"] } },
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
157
|
+
const items = this.getInputData();
|
|
158
|
+
const returnData: INodeExecutionData[] = [];
|
|
159
|
+
const credentials = await this.getCredentials("dominusNodeApi");
|
|
160
|
+
|
|
161
|
+
const apiKey = credentials.apiKey as string;
|
|
162
|
+
const baseUrl = (credentials.baseUrl as string) || "https://api.dominusnode.com";
|
|
163
|
+
|
|
164
|
+
if (!apiKey) {
|
|
165
|
+
throw new NodeOperationError(this.getNode(), "API Key is required");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const agentSecret = (credentials.agentSecret as string) || undefined;
|
|
169
|
+
const auth = new DominusNodeAuth(apiKey, baseUrl, 30000, agentSecret);
|
|
170
|
+
const operation = this.getNodeParameter("operation", 0) as string;
|
|
171
|
+
|
|
172
|
+
for (let i = 0; i < items.length; i++) {
|
|
173
|
+
try {
|
|
174
|
+
let result: unknown;
|
|
175
|
+
|
|
176
|
+
// All operations require teamId
|
|
177
|
+
const teamId = this.getNodeParameter("teamId", i) as string;
|
|
178
|
+
validateUuid(this, teamId, "teamId", i);
|
|
179
|
+
|
|
180
|
+
switch (operation) {
|
|
181
|
+
case "teamDelete": {
|
|
182
|
+
result = await auth.apiRequest(
|
|
183
|
+
"DELETE",
|
|
184
|
+
`/api/teams/${encodeURIComponent(teamId)}`,
|
|
185
|
+
);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
case "teamRevokeKey": {
|
|
190
|
+
const keyId = this.getNodeParameter("keyId", i) as string;
|
|
191
|
+
validateUuid(this, keyId, "keyId", i);
|
|
192
|
+
result = await auth.apiRequest(
|
|
193
|
+
"DELETE",
|
|
194
|
+
`/api/teams/${encodeURIComponent(teamId)}/keys/${encodeURIComponent(keyId)}`,
|
|
195
|
+
);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
case "teamListKeys": {
|
|
200
|
+
result = await auth.apiRequest(
|
|
201
|
+
"GET",
|
|
202
|
+
`/api/teams/${encodeURIComponent(teamId)}/keys`,
|
|
203
|
+
);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case "teamListMembers": {
|
|
208
|
+
result = await auth.apiRequest(
|
|
209
|
+
"GET",
|
|
210
|
+
`/api/teams/${encodeURIComponent(teamId)}/members`,
|
|
211
|
+
);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
case "teamAddMember": {
|
|
216
|
+
const userId = this.getNodeParameter("userId", i) as string;
|
|
217
|
+
const role = this.getNodeParameter("role", i, "member") as string;
|
|
218
|
+
validateUuid(this, userId, "userId", i);
|
|
219
|
+
if (role !== "member" && role !== "admin") {
|
|
220
|
+
throw new NodeOperationError(
|
|
221
|
+
this.getNode(),
|
|
222
|
+
"Role must be 'member' or 'admin'",
|
|
223
|
+
{ itemIndex: i },
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
result = await auth.apiRequest(
|
|
227
|
+
"POST",
|
|
228
|
+
`/api/teams/${encodeURIComponent(teamId)}/members`,
|
|
229
|
+
{ userId, role },
|
|
230
|
+
);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
case "teamRemoveMember": {
|
|
235
|
+
const userId = this.getNodeParameter("userId", i) as string;
|
|
236
|
+
validateUuid(this, userId, "userId", i);
|
|
237
|
+
result = await auth.apiRequest(
|
|
238
|
+
"DELETE",
|
|
239
|
+
`/api/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(userId)}`,
|
|
240
|
+
);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
case "teamInviteMember": {
|
|
245
|
+
const email = this.getNodeParameter("inviteEmail", i) as string;
|
|
246
|
+
const role = this.getNodeParameter("role", i, "member") as string;
|
|
247
|
+
const safeEmail = validateEmail(this, email, i);
|
|
248
|
+
if (role !== "member" && role !== "admin") {
|
|
249
|
+
throw new NodeOperationError(
|
|
250
|
+
this.getNode(),
|
|
251
|
+
"Role must be 'member' or 'admin'",
|
|
252
|
+
{ itemIndex: i },
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
result = await auth.apiRequest(
|
|
256
|
+
"POST",
|
|
257
|
+
`/api/teams/${encodeURIComponent(teamId)}/invites`,
|
|
258
|
+
{ email: safeEmail, role },
|
|
259
|
+
);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case "teamListInvites": {
|
|
264
|
+
result = await auth.apiRequest(
|
|
265
|
+
"GET",
|
|
266
|
+
`/api/teams/${encodeURIComponent(teamId)}/invites`,
|
|
267
|
+
);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case "teamCancelInvite": {
|
|
272
|
+
const inviteId = this.getNodeParameter("inviteId", i) as string;
|
|
273
|
+
validateUuid(this, inviteId, "inviteId", i);
|
|
274
|
+
result = await auth.apiRequest(
|
|
275
|
+
"DELETE",
|
|
276
|
+
`/api/teams/${encodeURIComponent(teamId)}/invites/${encodeURIComponent(inviteId)}`,
|
|
277
|
+
);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
default:
|
|
282
|
+
throw new NodeOperationError(
|
|
283
|
+
this.getNode(),
|
|
284
|
+
`Unknown operation: ${operation}`,
|
|
285
|
+
{ itemIndex: i },
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
returnData.push({ json: (result ?? {}) as IDataObject });
|
|
290
|
+
} catch (err) {
|
|
291
|
+
if (this.continueOnFail()) {
|
|
292
|
+
returnData.push({
|
|
293
|
+
json: {
|
|
294
|
+
error: sanitizeError(err instanceof Error ? err.message : String(err)),
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (err instanceof NodeOperationError) throw err;
|
|
300
|
+
throw new NodeOperationError(
|
|
301
|
+
this.getNode(),
|
|
302
|
+
sanitizeError(err instanceof Error ? err.message : String(err)),
|
|
303
|
+
{ itemIndex: i },
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return [returnData];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
// Validation helpers
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
function validateUuid(
|
|
317
|
+
ctx: IExecuteFunctions,
|
|
318
|
+
value: string,
|
|
319
|
+
fieldName: string,
|
|
320
|
+
itemIndex: number,
|
|
321
|
+
): void {
|
|
322
|
+
if (!value || typeof value !== "string") {
|
|
323
|
+
throw new NodeOperationError(ctx.getNode(), `${fieldName} is required`, { itemIndex });
|
|
324
|
+
}
|
|
325
|
+
if (!UUID_RE.test(value)) {
|
|
326
|
+
throw new NodeOperationError(ctx.getNode(), `${fieldName} must be a valid UUID`, { itemIndex });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function validateEmail(
|
|
331
|
+
ctx: IExecuteFunctions,
|
|
332
|
+
email: string,
|
|
333
|
+
itemIndex: number,
|
|
334
|
+
): string {
|
|
335
|
+
const trimmed = (email ?? "").trim();
|
|
336
|
+
if (!trimmed || !EMAIL_RE.test(trimmed)) {
|
|
337
|
+
throw new NodeOperationError(
|
|
338
|
+
ctx.getNode(),
|
|
339
|
+
"A valid email address is required",
|
|
340
|
+
{ itemIndex },
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
if (trimmed.length > 254) {
|
|
344
|
+
throw new NodeOperationError(
|
|
345
|
+
ctx.getNode(),
|
|
346
|
+
"Email address too long (max 254 characters)",
|
|
347
|
+
{ itemIndex },
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return trimmed;
|
|
351
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dominus Node Usage n8n community node.
|
|
3
3
|
*
|
|
4
|
-
* Operations:
|
|
4
|
+
* Operations (3 tools):
|
|
5
5
|
* - Check Usage: Get proxy usage statistics for a given period
|
|
6
|
+
* - Get Daily Usage: Get daily usage breakdown for the last N days
|
|
7
|
+
* - Get Top Hosts: Get top accessed hosts by bandwidth
|
|
6
8
|
*
|
|
7
9
|
* @module
|
|
8
10
|
*/
|
|
@@ -46,6 +48,18 @@ export class DominusNodeUsage implements INodeType {
|
|
|
46
48
|
description: "Get proxy usage statistics for a given period",
|
|
47
49
|
action: "Check usage",
|
|
48
50
|
},
|
|
51
|
+
{
|
|
52
|
+
name: "Get Daily Usage",
|
|
53
|
+
value: "getDailyUsage",
|
|
54
|
+
description: "Get daily usage breakdown for the last N days",
|
|
55
|
+
action: "Get daily usage",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "Get Top Hosts",
|
|
59
|
+
value: "getTopHosts",
|
|
60
|
+
description: "Get top accessed hosts by bandwidth",
|
|
61
|
+
action: "Get top hosts",
|
|
62
|
+
},
|
|
49
63
|
],
|
|
50
64
|
default: "checkUsage",
|
|
51
65
|
},
|
|
@@ -62,6 +76,22 @@ export class DominusNodeUsage implements INodeType {
|
|
|
62
76
|
description: "Time period for usage statistics",
|
|
63
77
|
displayOptions: { show: { operation: ["checkUsage"] } },
|
|
64
78
|
},
|
|
79
|
+
{
|
|
80
|
+
displayName: "Days",
|
|
81
|
+
name: "days",
|
|
82
|
+
type: "number",
|
|
83
|
+
default: 7,
|
|
84
|
+
description: "Number of days of daily usage to return (1-365)",
|
|
85
|
+
displayOptions: { show: { operation: ["getDailyUsage"] } },
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
displayName: "Limit",
|
|
89
|
+
name: "topHostsLimit",
|
|
90
|
+
type: "number",
|
|
91
|
+
default: 10,
|
|
92
|
+
description: "Number of top hosts to return (1-100)",
|
|
93
|
+
displayOptions: { show: { operation: ["getTopHosts"] } },
|
|
94
|
+
},
|
|
65
95
|
],
|
|
66
96
|
};
|
|
67
97
|
|
|
@@ -100,6 +130,28 @@ export class DominusNodeUsage implements INodeType {
|
|
|
100
130
|
const params = new URLSearchParams({ since, until });
|
|
101
131
|
const result = await auth.apiRequest("GET", `/api/usage?${params.toString()}`);
|
|
102
132
|
|
|
133
|
+
returnData.push({ json: (result ?? {}) as IDataObject });
|
|
134
|
+
} else if (operation === "getDailyUsage") {
|
|
135
|
+
const days = this.getNodeParameter("days", i, 7) as number;
|
|
136
|
+
if (!Number.isInteger(days) || days < 1 || days > 365) {
|
|
137
|
+
throw new NodeOperationError(
|
|
138
|
+
this.getNode(),
|
|
139
|
+
"Days must be an integer between 1 and 365",
|
|
140
|
+
{ itemIndex: i },
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const result = await auth.apiRequest("GET", `/api/usage/daily?days=${days}`);
|
|
144
|
+
returnData.push({ json: (result ?? {}) as IDataObject });
|
|
145
|
+
} else if (operation === "getTopHosts") {
|
|
146
|
+
const limit = this.getNodeParameter("topHostsLimit", i, 10) as number;
|
|
147
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
|
148
|
+
throw new NodeOperationError(
|
|
149
|
+
this.getNode(),
|
|
150
|
+
"Limit must be an integer between 1 and 100",
|
|
151
|
+
{ itemIndex: i },
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const result = await auth.apiRequest("GET", `/api/usage/top-hosts?limit=${limit}`);
|
|
103
155
|
returnData.push({ json: (result ?? {}) as IDataObject });
|
|
104
156
|
} else {
|
|
105
157
|
throw new NodeOperationError(
|