n8n-nodes-pinflow-pinterest 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/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # n8n-nodes-pinflow-pinterest
2
+
3
+ n8n community node package for PinFlow.
4
+
5
+ ## Features
6
+
7
+ - Schedule pin (`POST /pins`)
8
+ - Get pin (`GET /pins/{id}`)
9
+ - List pins (`GET /pins`)
10
+ - Delete pin (`DELETE /pins/{id}`)
11
+
12
+ ## Install (for development)
13
+
14
+ ```bash
15
+ cd n8n-nodes-pinflow
16
+ npm install
17
+ npm run build
18
+ ```
19
+
20
+ ## Publish to npm
21
+
22
+ ```bash
23
+ npm login
24
+ npm publish --access public
25
+ ```
26
+
27
+ ## Install in n8n
28
+
29
+ 1. Go to `Settings` -> `Community Nodes`
30
+ 2. Click `Install`
31
+ 3. Enter package name: `n8n-nodes-pinflow-pinterest`
32
+
33
+ ## Credentials
34
+
35
+ - `Base URL`: for example `https://pinflow.mirajpatterns.com`
36
+ - `API Key`: from PinFlow dashboard API keys
37
+
38
+ ## Notes
39
+
40
+ - Free plan in PinFlow does not support API access.
41
+ - If account billing is locked, API calls can fail with payment-required errors.
42
+ - For multi-profile accounts, pass the correct `Pinterest Profile ID`.
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PinFlowApi = void 0;
4
+ class PinFlowApi {
5
+ constructor() {
6
+ this.name = 'pinFlowApi';
7
+ this.displayName = 'PinFlow API';
8
+ this.documentationUrl = 'https://pinflow.mirajpatterns.com/api-reference';
9
+ this.properties = [
10
+ {
11
+ displayName: 'Base URL',
12
+ name: 'baseUrl',
13
+ type: 'string',
14
+ default: 'https://pinflow.mirajpatterns.com',
15
+ placeholder: 'https://pinflow.mirajpatterns.com',
16
+ required: true,
17
+ },
18
+ {
19
+ displayName: 'API Key',
20
+ name: 'apiKey',
21
+ type: 'string',
22
+ typeOptions: {
23
+ password: true,
24
+ },
25
+ default: '',
26
+ required: true,
27
+ },
28
+ ];
29
+ }
30
+ }
31
+ exports.PinFlowApi = PinFlowApi;
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,351 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PinFlow = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ async function pinFlowRequest(ctx, method, endpoint, body, qs) {
6
+ const credentials = await ctx.getCredentials('pinFlowApi');
7
+ const baseUrl = String(credentials.baseUrl || '').trim().replace(/\/+$/, '');
8
+ const apiKey = String(credentials.apiKey || '').trim();
9
+ if (!baseUrl) {
10
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'PinFlow Base URL is required in credentials.');
11
+ }
12
+ if (!apiKey) {
13
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'PinFlow API key is required in credentials.');
14
+ }
15
+ const options = {
16
+ method,
17
+ url: `${baseUrl}${endpoint}`,
18
+ headers: {
19
+ Authorization: `Bearer ${apiKey}`,
20
+ Accept: 'application/json',
21
+ },
22
+ json: true,
23
+ };
24
+ if (body && Object.keys(body).length) {
25
+ options.body = body;
26
+ }
27
+ if (qs && Object.keys(qs).length) {
28
+ options.qs = qs;
29
+ }
30
+ return await ctx.helpers.httpRequest(options);
31
+ }
32
+ class PinFlow {
33
+ constructor() {
34
+ this.description = {
35
+ displayName: 'PinFlow',
36
+ name: 'pinFlow',
37
+ icon: 'fa:pinterest',
38
+ group: ['transform'],
39
+ version: 1,
40
+ description: 'Create and manage scheduled Pinterest pins in PinFlow',
41
+ defaults: {
42
+ name: 'PinFlow',
43
+ },
44
+ inputs: ['main'],
45
+ outputs: ['main'],
46
+ credentials: [
47
+ {
48
+ name: 'pinFlowApi',
49
+ required: true,
50
+ },
51
+ ],
52
+ properties: [
53
+ {
54
+ displayName: 'Resource',
55
+ name: 'resource',
56
+ type: 'options',
57
+ noDataExpression: true,
58
+ options: [
59
+ {
60
+ name: 'Pin',
61
+ value: 'pin',
62
+ },
63
+ ],
64
+ default: 'pin',
65
+ },
66
+ {
67
+ displayName: 'Operation',
68
+ name: 'operation',
69
+ type: 'options',
70
+ noDataExpression: true,
71
+ displayOptions: {
72
+ show: {
73
+ resource: ['pin'],
74
+ },
75
+ },
76
+ options: [
77
+ {
78
+ name: 'Schedule',
79
+ value: 'schedule',
80
+ description: 'Create and schedule a new pin',
81
+ action: 'Schedule a pin',
82
+ },
83
+ {
84
+ name: 'Get',
85
+ value: 'get',
86
+ description: 'Get a pin by ID',
87
+ action: 'Get a pin',
88
+ },
89
+ {
90
+ name: 'List',
91
+ value: 'list',
92
+ description: 'List pins',
93
+ action: 'List pins',
94
+ },
95
+ {
96
+ name: 'Delete',
97
+ value: 'delete',
98
+ description: 'Delete a pin by ID',
99
+ action: 'Delete a pin',
100
+ },
101
+ ],
102
+ default: 'schedule',
103
+ },
104
+ {
105
+ displayName: 'Pinterest Profile ID',
106
+ name: 'pinterestProfileId',
107
+ type: 'number',
108
+ typeOptions: {
109
+ minValue: 1,
110
+ },
111
+ required: true,
112
+ default: 1,
113
+ description: 'PinFlow Pinterest profile ID that this pin should use',
114
+ displayOptions: {
115
+ show: {
116
+ resource: ['pin'],
117
+ operation: ['schedule'],
118
+ },
119
+ },
120
+ },
121
+ {
122
+ displayName: 'Board ID',
123
+ name: 'boardId',
124
+ type: 'string',
125
+ required: true,
126
+ default: '',
127
+ description: 'Target Pinterest board ID',
128
+ displayOptions: {
129
+ show: {
130
+ resource: ['pin'],
131
+ operation: ['schedule'],
132
+ },
133
+ },
134
+ },
135
+ {
136
+ displayName: 'Media URL',
137
+ name: 'mediaUrl',
138
+ type: 'string',
139
+ required: true,
140
+ default: '',
141
+ placeholder: 'https://example.com/image.jpg',
142
+ description: 'Public image URL to publish',
143
+ displayOptions: {
144
+ show: {
145
+ resource: ['pin'],
146
+ operation: ['schedule'],
147
+ },
148
+ },
149
+ },
150
+ {
151
+ displayName: 'Title',
152
+ name: 'title',
153
+ type: 'string',
154
+ default: '',
155
+ description: 'Pin title',
156
+ displayOptions: {
157
+ show: {
158
+ resource: ['pin'],
159
+ operation: ['schedule'],
160
+ },
161
+ },
162
+ },
163
+ {
164
+ displayName: 'Description',
165
+ name: 'description',
166
+ type: 'string',
167
+ typeOptions: {
168
+ rows: 4,
169
+ },
170
+ default: '',
171
+ description: 'Pin description',
172
+ displayOptions: {
173
+ show: {
174
+ resource: ['pin'],
175
+ operation: ['schedule'],
176
+ },
177
+ },
178
+ },
179
+ {
180
+ displayName: 'Destination Link',
181
+ name: 'link',
182
+ type: 'string',
183
+ default: '',
184
+ placeholder: 'https://example.com/product-page',
185
+ description: 'External link to attach to the pin',
186
+ displayOptions: {
187
+ show: {
188
+ resource: ['pin'],
189
+ operation: ['schedule'],
190
+ },
191
+ },
192
+ },
193
+ {
194
+ displayName: 'Schedule At (ISO UTC)',
195
+ name: 'scheduledAt',
196
+ type: 'string',
197
+ required: true,
198
+ default: '',
199
+ placeholder: '2026-05-16T11:55:00Z',
200
+ description: 'ISO datetime in UTC for scheduling',
201
+ displayOptions: {
202
+ show: {
203
+ resource: ['pin'],
204
+ operation: ['schedule'],
205
+ },
206
+ },
207
+ },
208
+ {
209
+ displayName: 'Pin ID',
210
+ name: 'pinId',
211
+ type: 'number',
212
+ typeOptions: {
213
+ minValue: 1,
214
+ },
215
+ required: true,
216
+ default: 1,
217
+ displayOptions: {
218
+ show: {
219
+ resource: ['pin'],
220
+ operation: ['get', 'delete'],
221
+ },
222
+ },
223
+ },
224
+ {
225
+ displayName: 'Status',
226
+ name: 'status',
227
+ type: 'options',
228
+ options: [
229
+ { name: 'All', value: '' },
230
+ { name: 'Draft', value: 'draft' },
231
+ { name: 'Review', value: 'review' },
232
+ { name: 'Queued', value: 'queued' },
233
+ { name: 'Scheduled', value: 'scheduled' },
234
+ { name: 'Publishing', value: 'publishing' },
235
+ { name: 'Published', value: 'published' },
236
+ { name: 'Failed', value: 'failed' },
237
+ ],
238
+ default: '',
239
+ displayOptions: {
240
+ show: {
241
+ resource: ['pin'],
242
+ operation: ['list'],
243
+ },
244
+ },
245
+ },
246
+ {
247
+ displayName: 'Limit',
248
+ name: 'limit',
249
+ type: 'number',
250
+ typeOptions: {
251
+ minValue: 1,
252
+ },
253
+ default: 50,
254
+ description: 'Maximum number of pins to return',
255
+ displayOptions: {
256
+ show: {
257
+ resource: ['pin'],
258
+ operation: ['list'],
259
+ },
260
+ },
261
+ },
262
+ ],
263
+ };
264
+ }
265
+ async execute() {
266
+ const items = this.getInputData();
267
+ const returnData = [];
268
+ for (let i = 0; i < items.length; i++) {
269
+ try {
270
+ const resource = this.getNodeParameter('resource', i);
271
+ const operation = this.getNodeParameter('operation', i);
272
+ let responseData;
273
+ if (resource === 'pin') {
274
+ if (operation === 'schedule') {
275
+ const payload = {
276
+ pinterest_profile_id: this.getNodeParameter('pinterestProfileId', i),
277
+ board_id: this.getNodeParameter('boardId', i),
278
+ media_url: this.getNodeParameter('mediaUrl', i),
279
+ scheduled_at: this.getNodeParameter('scheduledAt', i),
280
+ };
281
+ const title = this.getNodeParameter('title', i);
282
+ const description = this.getNodeParameter('description', i);
283
+ const link = this.getNodeParameter('link', i);
284
+ if (title)
285
+ payload.title = title;
286
+ if (description)
287
+ payload.description = description;
288
+ if (link)
289
+ payload.link = link;
290
+ responseData = await pinFlowRequest(this, 'POST', '/pins', payload);
291
+ }
292
+ else if (operation === 'get') {
293
+ const pinId = this.getNodeParameter('pinId', i);
294
+ responseData = await pinFlowRequest(this, 'GET', `/pins/${pinId}`);
295
+ }
296
+ else if (operation === 'list') {
297
+ const status = this.getNodeParameter('status', i);
298
+ const limit = this.getNodeParameter('limit', i);
299
+ const qs = {};
300
+ if (status)
301
+ qs.status = status;
302
+ if (limit > 0)
303
+ qs.limit = limit;
304
+ responseData = await pinFlowRequest(this, 'GET', '/pins', undefined, qs);
305
+ }
306
+ else if (operation === 'delete') {
307
+ const pinId = this.getNodeParameter('pinId', i);
308
+ responseData = await pinFlowRequest(this, 'DELETE', `/pins/${pinId}`);
309
+ }
310
+ else {
311
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unsupported operation: ${operation}`);
312
+ }
313
+ }
314
+ else {
315
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unsupported resource: ${resource}`);
316
+ }
317
+ if (Array.isArray(responseData)) {
318
+ for (const row of responseData) {
319
+ returnData.push({ json: row, pairedItem: { item: i } });
320
+ }
321
+ }
322
+ else if (responseData && typeof responseData === 'object') {
323
+ returnData.push({ json: responseData, pairedItem: { item: i } });
324
+ }
325
+ else {
326
+ const primitive = typeof responseData === 'string' ||
327
+ typeof responseData === 'number' ||
328
+ typeof responseData === 'boolean' ||
329
+ responseData === null
330
+ ? responseData
331
+ : String(responseData);
332
+ returnData.push({ json: { success: true, data: primitive }, pairedItem: { item: i } });
333
+ }
334
+ }
335
+ catch (error) {
336
+ if (this.continueOnFail()) {
337
+ returnData.push({
338
+ json: {
339
+ error: error.message,
340
+ },
341
+ pairedItem: { item: i },
342
+ });
343
+ continue;
344
+ }
345
+ throw error;
346
+ }
347
+ }
348
+ return [returnData];
349
+ }
350
+ }
351
+ exports.PinFlow = PinFlow;
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "n8n-nodes-pinflow-pinterest",
3
+ "version": "0.1.0",
4
+ "description": "n8n community node for PinFlow",
5
+ "license": "MIT",
6
+ "homepage": "https://pinflow.mirajpatterns.com",
7
+ "author": "PinFlow",
8
+ "keywords": [
9
+ "n8n-community-node-package",
10
+ "n8n",
11
+ "pinflow",
12
+ "pinterest"
13
+ ],
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "main": "dist/index.js",
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "dev": "tsc -w -p tsconfig.json"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/soudou98/pinflow.git"
25
+ },
26
+ "n8n": {
27
+ "n8nNodesApiVersion": 1,
28
+ "credentials": [
29
+ "dist/credentials/PinFlowApi.credentials.js"
30
+ ],
31
+ "nodes": [
32
+ "dist/nodes/PinFlow/PinFlow.node.js"
33
+ ]
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.17.57",
37
+ "n8n-core": "^1.109.0",
38
+ "n8n-workflow": "^1.109.0",
39
+ "typescript": "^5.8.3"
40
+ }
41
+ }