@opencloud-eu/n8n-nodes-opencloud 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright 2026 OpenCloud GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ 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.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @opencloud-eu/n8n-nodes-opencloud
2
+
3
+ > n8n community node for [OpenCloud](https://opencloud.eu/) — files, folders
4
+ > and shares against the OpenCloud API.
5
+
6
+ Built on top of [Libre Graph](https://github.com/opencloud-eu/libre-graph-api)
7
+ (metadata, sharing, drive enumeration) and OpenCloud WebDAV (content I/O,
8
+ copy, move, delete).
9
+
10
+ ![Smoke workflow in the n8n editor — every operation in one chain](docs/smoke-workflow.png)
11
+
12
+ ---
13
+
14
+ ## Operations
15
+
16
+ | Resource | Operations |
17
+ | --- | --- |
18
+ | **Space** | List |
19
+ | **File** | Copy · Delete · Download · Move · Share · Upload |
20
+ | **Folder** | Copy · Create · Delete · List · Move · Share |
21
+ | **User** | Create · Delete · Get · Get Many · Update *(admin only)* |
22
+
23
+ ## Credentials
24
+
25
+ | Field | Notes |
26
+ | --- | --- |
27
+ | **Server URL** | e.g. `https://opencloud.example.com` |
28
+ | **User** | username or email |
29
+ | **Password** | app token (preferred) or account password — Basic auth |
30
+ | **Skip TLS verification** | accept self-signed or otherwise untrusted certs |
31
+
32
+ Generate an app token from your OpenCloud profile for automation use; account
33
+ passwords work for quick tests.
34
+
35
+ ## Install
36
+
37
+ > **Status:** not yet published to npm. Once published, this will be installable
38
+ > from n8n's UI: **Settings → Community Nodes → Install →
39
+ > `@opencloud-eu/n8n-nodes-opencloud`**.
40
+
41
+ For now, install from source:
42
+
43
+ ```bash
44
+ git clone https://github.com/opencloud-eu/n8n-nodes-opencloud.git
45
+ cd n8n-nodes-opencloud
46
+ pnpm install
47
+ pnpm build
48
+
49
+ # Then either:
50
+ # a) symlink into your n8n custom-nodes directory
51
+ mkdir -p ~/.n8n/custom
52
+ ln -s "$(pwd)" ~/.n8n/custom/n8n-nodes-opencloud
53
+
54
+ # b) or pack and install via the n8n UI
55
+ pnpm pack
56
+ # Settings → Community Nodes → Install → upload the .tgz
57
+ ```
58
+
59
+ Restart n8n after either step.
60
+
61
+ ---
62
+
63
+ ## Development
64
+
65
+ Requires Node.js ≥ 20.15.
66
+
67
+ ```bash
68
+ pnpm install
69
+ pnpm build # compile to dist/
70
+ pnpm test # vitest unit tests (mocked, fast)
71
+ pnpm test:e2e # Playwright end-to-end (see below)
72
+ pnpm lint # n8n-node lint, strict / Cloud-compatible
73
+ pnpm typecheck
74
+ ```
75
+
76
+ ### Docker dev loop
77
+
78
+ Pinned n8n in a container with this node mounted in; hot-reloads on `dist/`
79
+ changes via `N8N_DEV_RELOAD=true`:
80
+
81
+ ```bash
82
+ pnpm build # one-off before first start
83
+ docker compose up # foreground
84
+ pnpm build:w # in another terminal — tsc --watch
85
+ ```
86
+
87
+ Open <http://localhost:5678> and log in as **admin@example.com / admin**.
88
+ Workflow editor → add node → search **OpenCloud**.
89
+
90
+ Reset to a clean DB:
91
+
92
+ ```bash
93
+ docker compose down -v
94
+ ```
95
+
96
+ ### Sample workflow
97
+
98
+ `examples/smoke-test.workflow.json` exercises every operation against a real
99
+ backend (the chain shown at the top of this README). Patch it with your
100
+ credential id before importing:
101
+
102
+ ```bash
103
+ ./examples/apply-credentials.sh <credential-id> > /tmp/smoke.workflow.json
104
+ ```
105
+
106
+ ### End-to-end smoke
107
+
108
+ `tests/e2e/smoke.spec.ts` is a Playwright test that automates the full path:
109
+ log in to n8n → create the credential → import the example workflow → run from
110
+ the manual trigger → assert success.
111
+
112
+ For self-contained CI-style runs the compose file ships an OpenCloud service
113
+ under the `ci` profile:
114
+
115
+ ```bash
116
+ docker compose --profile ci up -d
117
+ OPENCLOUD_URL=https://opencloud:9200 pnpm test:e2e
118
+ ```
119
+
120
+ To target your own backend:
121
+
122
+ ```bash
123
+ docker compose up -d
124
+ OPENCLOUD_URL=https://host.docker.internal:9200 pnpm test:e2e
125
+ ```
126
+
127
+ Env vars: `OPENCLOUD_URL` (required), `OPENCLOUD_USER` /
128
+ `OPENCLOUD_PASSWORD` (default `admin`/`admin`), `N8N_URL` / `N8N_EMAIL` /
129
+ `N8N_PASSWORD` (defaults match the compose stack). On failure Playwright
130
+ saves traces, screenshots and videos to `test-results/`.
131
+
132
+ ---
133
+
134
+ ## License
135
+
136
+ MIT — see [`LICENSE.md`](./LICENSE.md). Copyright OpenCloud GmbH.
@@ -0,0 +1,10 @@
1
+ import type { ICredentialDataDecryptedObject, ICredentialTestRequest, ICredentialType, IHttpRequestOptions, Icon, INodeProperties } from 'n8n-workflow';
2
+ export declare class OpenCloudApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ icon: Icon;
6
+ documentationUrl: string;
7
+ properties: INodeProperties[];
8
+ authenticate(credentials: ICredentialDataDecryptedObject, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions>;
9
+ test: ICredentialTestRequest;
10
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenCloudApi = void 0;
4
+ class OpenCloudApi {
5
+ constructor() {
6
+ this.name = 'openCloudApi';
7
+ this.displayName = 'OpenCloud API';
8
+ this.icon = 'file:../icons/opencloud.svg';
9
+ this.documentationUrl = 'https://docs.opencloud.eu/';
10
+ this.properties = [
11
+ {
12
+ displayName: 'Server URL',
13
+ name: 'serverUrl',
14
+ type: 'string',
15
+ placeholder: 'https://opencloud.example.com',
16
+ default: '',
17
+ description: 'Base URL of the OpenCloud server',
18
+ },
19
+ {
20
+ displayName: 'User',
21
+ name: 'user',
22
+ type: 'string',
23
+ default: '',
24
+ },
25
+ {
26
+ displayName: 'Password',
27
+ name: 'password',
28
+ type: 'string',
29
+ typeOptions: { password: true },
30
+ default: '',
31
+ description: 'OpenCloud app token (preferred) or account password. Generate an app token from your OpenCloud profile for automation use.',
32
+ },
33
+ {
34
+ displayName: 'Skip TLS Certificate Verification',
35
+ name: 'skipTlsVerification',
36
+ type: 'boolean',
37
+ default: false,
38
+ description: 'Whether to accept self-signed or otherwise untrusted TLS certificates. Useful for local dev OpenCloud instances; never enable against production servers.',
39
+ },
40
+ ];
41
+ this.test = {
42
+ request: {
43
+ baseURL: '={{ $credentials.serverUrl.replace(/\\/+$/, "") }}',
44
+ url: '/graph/v1.0/me/drives',
45
+ skipSslCertificateValidation: '={{$credentials.skipTlsVerification}}',
46
+ },
47
+ };
48
+ }
49
+ async authenticate(credentials, requestOptions) {
50
+ requestOptions.auth = {
51
+ username: credentials.user,
52
+ password: credentials.password,
53
+ };
54
+ if (credentials.skipTlsVerification === true) {
55
+ requestOptions.skipSslCertificateValidation = true;
56
+ }
57
+ return requestOptions;
58
+ }
59
+ }
60
+ exports.OpenCloudApi = OpenCloudApi;
61
+ //# sourceMappingURL=OpenCloudApi.credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenCloudApi.credentials.js","sourceRoot":"","sources":["../../credentials/OpenCloudApi.credentials.ts"],"names":[],"mappings":";;;AASA,MAAa,YAAY;IAAzB;QACC,SAAI,GAAG,cAAc,CAAC;QAEtB,gBAAW,GAAG,eAAe,CAAC;QAE9B,SAAI,GAAS,6BAA6B,CAAC;QAE3C,qBAAgB,GAAG,4BAA4B,CAAC;QAEhD,eAAU,GAAsB;YAC/B;gBACC,WAAW,EAAE,YAAY;gBACzB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,+BAA+B;gBAC5C,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,kCAAkC;aAC/C;YACD;gBACC,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;aACX;YACD;gBACC,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,OAAO,EAAE,EAAE;gBACX,WAAW,EACV,4HAA4H;aAC7H;YACD;gBACC,WAAW,EAAE,mCAAmC;gBAChD,IAAI,EAAE,qBAAqB;gBAC3B,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,KAAK;gBACd,WAAW,EACV,2JAA2J;aAC5J;SACD,CAAC;QAgBF,SAAI,GAA2B;YAC9B,OAAO,EAAE;gBAGR,OAAO,EAAE,oDAAoD;gBAC7D,GAAG,EAAE,uBAAuB;gBAC5B,4BAA4B,EAAE,uCAAuC;aACrE;SACD,CAAC;IACH,CAAC;IAvBA,KAAK,CAAC,YAAY,CACjB,WAA2C,EAC3C,cAAmC;QAEnC,cAAc,CAAC,IAAI,GAAG;YACrB,QAAQ,EAAE,WAAW,CAAC,IAAc;YACpC,QAAQ,EAAE,WAAW,CAAC,QAAkB;SACxC,CAAC;QACF,IAAI,WAAW,CAAC,mBAAmB,KAAK,IAAI,EAAE,CAAC;YAC9C,cAAc,CAAC,4BAA4B,GAAG,IAAI,CAAC;QACpD,CAAC;QACD,OAAO,cAAc,CAAC;IACvB,CAAC;CAWD;AAlED,oCAkEC"}
Binary file
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="512" height="512"><svg id="SvgjsSvg1001" xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><rect x=".02" y="0" width="512" height="512" fill="#20434f"></rect><polygon points="255.98 342.75 271.89 333.57 271.89 267.12 329.08 234.1 329.08 215.78 313.18 206.6 255.6 239.84 198.83 207.06 182.93 216.24 182.93 234.56 240.12 267.58 240.12 333.59 255.98 342.75" fill="#e2baff"></polygon><polygon points="401.95 150.82 256 66.56 256 66.56 256 66.56 110.05 150.82 110.05 187.5 256 103.24 401.95 187.5 401.95 150.82" fill="#e2baff"></polygon><polygon points="401.95 324.5 256 408.76 110.06 324.5 110.06 361.17 256 445.43 256 445.43 256 445.43 401.95 361.17 401.95 324.5" fill="#e2baff"></polygon></svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
2
+ @media (prefers-color-scheme: dark) { :root { filter: none; } }
3
+ </style></svg>
@@ -0,0 +1,37 @@
1
+ import type { IDataObject, IExecuteFunctions, IHookFunctions, IHttpRequestMethods, ILoadOptionsFunctions } from 'n8n-workflow';
2
+ export interface Drive {
3
+ id?: string;
4
+ name?: string;
5
+ driveType?: string;
6
+ }
7
+ export interface DriveItem {
8
+ id?: string;
9
+ name?: string;
10
+ folder?: object;
11
+ file?: {
12
+ mimeType?: string;
13
+ };
14
+ parentReference?: {
15
+ driveId?: string;
16
+ id?: string;
17
+ name?: string;
18
+ path?: string;
19
+ };
20
+ }
21
+ export interface OpenCloudCollection<T> {
22
+ value?: T[];
23
+ '@odata.nextLink'?: string;
24
+ }
25
+ export type DrivesResponse = OpenCloudCollection<Drive>;
26
+ export type DriveItemsResponse = OpenCloudCollection<DriveItem>;
27
+ export interface UnifiedRolePermission {
28
+ allowedResourceActions?: string[];
29
+ condition?: string;
30
+ }
31
+ export interface RoleDefinition {
32
+ id?: string;
33
+ displayName?: string;
34
+ description?: string;
35
+ rolePermissions?: UnifiedRolePermission[];
36
+ }
37
+ export declare function openCloudApiRequest<T = unknown>(this: IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, urlOrPath: string, body?: IDataObject | string | Buffer, headers?: IDataObject, json?: boolean, encoding?: 'arraybuffer'): Promise<T>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.openCloudApiRequest = openCloudApiRequest;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const NETWORK_ERROR_CODES = new Set([
6
+ 'ENOTFOUND',
7
+ 'ECONNREFUSED',
8
+ 'ETIMEDOUT',
9
+ 'EAI_AGAIN',
10
+ 'ECONNRESET',
11
+ 'EHOSTUNREACH',
12
+ 'ENETUNREACH',
13
+ 'EPROTO',
14
+ 'CERT_HAS_EXPIRED',
15
+ 'DEPTH_ZERO_SELF_SIGNED_CERT',
16
+ 'SELF_SIGNED_CERT_IN_CHAIN',
17
+ 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
18
+ ]);
19
+ function extractErrorCode(error) {
20
+ var _a, _b;
21
+ const e = error;
22
+ const candidates = [e.httpCode, e.code, (_a = e.cause) === null || _a === void 0 ? void 0 : _a.code, (_b = e.cause) === null || _b === void 0 ? void 0 : _b.httpCode];
23
+ for (const c of candidates) {
24
+ if (typeof c === 'string' && c.length > 0)
25
+ return c;
26
+ }
27
+ return '';
28
+ }
29
+ async function openCloudApiRequest(method, urlOrPath, body = '', headers = {}, json = false, encoding) {
30
+ let url = urlOrPath;
31
+ if (!/^https?:\/\//i.test(urlOrPath)) {
32
+ const credentials = await this.getCredentials('openCloudApi');
33
+ const base = credentials.serverUrl.replace(/\/+$/, '');
34
+ url = `${base}${urlOrPath.startsWith('/') ? '' : '/'}${urlOrPath}`;
35
+ }
36
+ const options = {
37
+ method,
38
+ url,
39
+ body: body,
40
+ headers,
41
+ json,
42
+ };
43
+ if (encoding === 'arraybuffer') {
44
+ options.encoding = 'arraybuffer';
45
+ }
46
+ try {
47
+ return (await this.helpers.httpRequestWithAuthentication.call(this, 'openCloudApi', options));
48
+ }
49
+ catch (error) {
50
+ const code = extractErrorCode(error);
51
+ if (NETWORK_ERROR_CODES.has(code)) {
52
+ const credentials = await this.getCredentials('openCloudApi');
53
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Cannot reach OpenCloud server at ${credentials.serverUrl} (${code})`, {
54
+ description: 'The request never received a reply from the server. ' +
55
+ 'Verify the credential\'s "Server URL" is correct, that the OpenCloud server is running, ' +
56
+ 'and that n8n can reach it from inside its container (e.g. host.docker.internal for a host-running server).',
57
+ });
58
+ }
59
+ throw error;
60
+ }
61
+ }
62
+ //# sourceMappingURL=GenericFunctions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GenericFunctions.js","sourceRoot":"","sources":["../../../nodes/OpenCloud/GenericFunctions.ts"],"names":[],"mappings":";;AA6FA,kDA4DC;AAjJD,+CAAkD;AAOlD,MAAM,mBAAmB,GAAwB,IAAI,GAAG,CAAC;IACxD,WAAW;IACX,cAAc;IACd,WAAW;IACX,WAAW;IACX,YAAY;IACZ,cAAc;IACd,aAAa;IACb,QAAQ;IACR,kBAAkB;IAClB,6BAA6B;IAC7B,2BAA2B;IAC3B,iCAAiC;CACjC,CAAC,CAAC;AAEH,SAAS,gBAAgB,CAAC,KAAc;;IACvC,MAAM,CAAC,GAAG,KAIT,CAAC;IACF,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,MAAA,CAAC,CAAC,KAAK,0CAAE,IAAI,EAAE,MAAA,CAAC,CAAC,KAAK,0CAAE,QAAQ,CAAC,CAAC;IAC1E,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,EAAE,CAAC;AACX,CAAC;AAoDM,KAAK,UAAU,mBAAmB,CAExC,MAA2B,EAC3B,SAAiB,EACjB,OAAsC,EAAE,EACxC,UAAuB,EAAE,EACzB,OAAgB,KAAK,EACrB,QAAwB;IAExB,IAAI,GAAG,GAAG,SAAS,CAAC;IACpB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,cAAc,CAAwB,cAAc,CAAC,CAAC;QACrF,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACvD,GAAG,GAAG,GAAG,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,SAAS,EAAE,CAAC;IACpE,CAAC;IAED,MAAM,OAAO,GAAwB;QACpC,MAAM;QACN,GAAG;QACH,IAAI,EAAE,IAAmC;QACzC,OAAO;QACP,IAAI;KACJ,CAAC;IACF,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;QAChC,OAAO,CAAC,QAAQ,GAAG,aAAa,CAAC;IAClC,CAAC;IAED,IAAI,CAAC;QACJ,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,6BAA6B,CAAC,IAAI,CAC5D,IAAI,EACJ,cAAc,EACd,OAAO,CACP,CAAM,CAAC;IACT,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAKnC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,cAAc,CAAwB,cAAc,CAAC,CAAC;YACrF,MAAM,IAAI,iCAAkB,CAC3B,IAAI,CAAC,OAAO,EAAE,EACd,oCAAoC,WAAW,CAAC,SAAS,KAAK,IAAI,GAAG,EACrE;gBACC,WAAW,EACV,sDAAsD;oBACtD,0FAA0F;oBAC1F,4GAA4G;aAC7G,CACD,CAAC;QACH,CAAC;QAOD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodeListSearchResult, INodePropertyOptions, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class OpenCloud implements INodeType {
3
+ description: INodeTypeDescription;
4
+ methods: {
5
+ loadOptions: {
6
+ getSpaces(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
7
+ getLinkTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
8
+ getShareRoles(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
9
+ };
10
+ listSearch: {
11
+ searchRecipients(this: ILoadOptionsFunctions, filter?: string, paginationToken?: string): Promise<INodeListSearchResult>;
12
+ };
13
+ };
14
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
15
+ }