n8n-nodes-sudomock 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.
@@ -0,0 +1,809 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SudoMock = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ class SudoMock {
6
+ description = {
7
+ displayName: 'SudoMock',
8
+ name: 'sudoMock',
9
+ icon: 'file:sudomock.svg',
10
+ group: ['transform'],
11
+ version: 1,
12
+ subtitle: '={{$parameter["operation"]}}',
13
+ description: 'Generate mockup images for Print-on-Demand automation. Upload PSDs, render with your designs.',
14
+ defaults: {
15
+ name: 'SudoMock',
16
+ },
17
+ inputs: ['main'],
18
+ outputs: ['main'],
19
+ credentials: [
20
+ {
21
+ name: 'sudoMockApi',
22
+ required: true,
23
+ },
24
+ ],
25
+ requestDefaults: {
26
+ baseURL: 'https://api.sudomock.com',
27
+ headers: {
28
+ 'Content-Type': 'application/json',
29
+ },
30
+ },
31
+ properties: [
32
+ // ============================================
33
+ // OPERATION SELECT
34
+ // ============================================
35
+ {
36
+ displayName: 'Operation',
37
+ name: 'operation',
38
+ type: 'options',
39
+ noDataExpression: true,
40
+ options: [
41
+ {
42
+ name: 'Upload PSD',
43
+ value: 'uploadPsd',
44
+ description: 'Upload a PSD template from URL',
45
+ action: 'Upload a PSD template',
46
+ },
47
+ {
48
+ name: 'Render Mockup',
49
+ value: 'render',
50
+ description: 'Render mockup with your design',
51
+ action: 'Render a mockup',
52
+ },
53
+ {
54
+ name: 'Get Account Info',
55
+ value: 'getAccountInfo',
56
+ description: 'Get account details, subscription, and usage statistics',
57
+ action: 'Get account information',
58
+ },
59
+ {
60
+ name: 'List Mockups',
61
+ value: 'listMockups',
62
+ description: 'List all your uploaded mockup templates',
63
+ action: 'List mockups',
64
+ },
65
+ {
66
+ name: 'Get Mockup',
67
+ value: 'getMockup',
68
+ description: 'Get details of a specific mockup template',
69
+ action: 'Get mockup details',
70
+ },
71
+ {
72
+ name: 'Update Mockup',
73
+ value: 'updateMockup',
74
+ description: 'Update mockup template name',
75
+ action: 'Update mockup name',
76
+ },
77
+ {
78
+ name: 'Delete Mockup',
79
+ value: 'deleteMockup',
80
+ description: 'Delete a specific mockup template',
81
+ action: 'Delete a mockup',
82
+ },
83
+ {
84
+ name: 'Delete All Mockups',
85
+ value: 'deleteAllMockups',
86
+ description: 'Delete all your mockup templates',
87
+ action: 'Delete all mockups',
88
+ },
89
+ ],
90
+ default: 'render',
91
+ },
92
+ // ============================================
93
+ // UPLOAD PSD PARAMETERS
94
+ // ============================================
95
+ {
96
+ displayName: 'PSD File URL',
97
+ name: 'psdFileUrl',
98
+ type: 'string',
99
+ required: true,
100
+ displayOptions: {
101
+ show: {
102
+ operation: ['uploadPsd'],
103
+ },
104
+ },
105
+ default: '',
106
+ placeholder: 'https://your-storage.com/mockup-template.psd',
107
+ description: 'Public URL to your PSD file (max 300MB). Use S3, GCS, or any public URL.',
108
+ },
109
+ {
110
+ displayName: 'Template Name',
111
+ name: 'psdName',
112
+ type: 'string',
113
+ displayOptions: {
114
+ show: {
115
+ operation: ['uploadPsd'],
116
+ },
117
+ },
118
+ default: '',
119
+ placeholder: 'T-Shirt Mockup Front',
120
+ description: 'Human-readable name for the template. Auto-generated from filename if empty.',
121
+ },
122
+ // ============================================
123
+ // RENDER PARAMETERS
124
+ // ============================================
125
+ {
126
+ displayName: 'Mockup UUID',
127
+ name: 'mockupUuid',
128
+ type: 'string',
129
+ required: true,
130
+ displayOptions: {
131
+ show: {
132
+ operation: ['render'],
133
+ },
134
+ },
135
+ default: '',
136
+ placeholder: 'c315f78f-d2c7-4541-b240-a9372842de94',
137
+ description: 'UUID of the uploaded mockup template (from Upload PSD response)',
138
+ },
139
+ {
140
+ displayName: 'Smart Objects',
141
+ name: 'smartObjects',
142
+ type: 'fixedCollection',
143
+ typeOptions: {
144
+ multipleValues: true,
145
+ },
146
+ displayOptions: {
147
+ show: {
148
+ operation: ['render'],
149
+ },
150
+ },
151
+ default: {},
152
+ placeholder: 'Add Smart Object',
153
+ description: 'Configure which smart objects to fill with your designs',
154
+ options: [
155
+ {
156
+ name: 'items',
157
+ displayName: 'Smart Object',
158
+ values: [
159
+ {
160
+ displayName: 'Smart Object UUID',
161
+ name: 'uuid',
162
+ type: 'string',
163
+ default: '',
164
+ required: true,
165
+ description: 'UUID of the smart object (from Upload PSD response)',
166
+ },
167
+ {
168
+ displayName: 'Design URL',
169
+ name: 'assetUrl',
170
+ type: 'string',
171
+ default: '',
172
+ required: true,
173
+ placeholder: 'https://cdn.example.com/design.png',
174
+ description: 'URL to your design image (PNG, JPG, WebP)',
175
+ },
176
+ {
177
+ displayName: 'Fit Mode',
178
+ name: 'fit',
179
+ type: 'options',
180
+ options: [
181
+ {
182
+ name: 'Fill',
183
+ value: 'fill',
184
+ description: 'Stretch to fill entire area',
185
+ },
186
+ {
187
+ name: 'Contain',
188
+ value: 'contain',
189
+ description: 'Fit inside, may leave space',
190
+ },
191
+ {
192
+ name: 'Cover',
193
+ value: 'cover',
194
+ description: 'Fill area, may crop edges (recommended)',
195
+ },
196
+ ],
197
+ default: 'cover',
198
+ description: 'How to fit the design in the smart object bounds',
199
+ },
200
+ {
201
+ displayName: 'Additional Options',
202
+ name: 'additionalOptions',
203
+ type: 'collection',
204
+ placeholder: 'Add Option',
205
+ default: {},
206
+ options: [
207
+ {
208
+ displayName: 'Rotation',
209
+ name: 'rotate',
210
+ type: 'number',
211
+ typeOptions: {
212
+ minValue: -360,
213
+ maxValue: 360,
214
+ },
215
+ default: 0,
216
+ description: 'Rotation angle in degrees',
217
+ },
218
+ {
219
+ displayName: 'Color Overlay (Hex)',
220
+ name: 'colorHex',
221
+ type: 'string',
222
+ default: '',
223
+ placeholder: '#FF5733',
224
+ description: 'Apply color overlay to the design',
225
+ },
226
+ {
227
+ displayName: 'Color Blend Mode',
228
+ name: 'colorBlendMode',
229
+ type: 'options',
230
+ options: [
231
+ { name: 'Normal', value: 'normal' },
232
+ { name: 'Multiply', value: 'multiply' },
233
+ { name: 'Screen', value: 'screen' },
234
+ { name: 'Overlay', value: 'overlay' },
235
+ { name: 'Darken', value: 'darken' },
236
+ { name: 'Lighten', value: 'lighten' },
237
+ { name: 'Color Dodge', value: 'color-dodge' },
238
+ { name: 'Color Burn', value: 'color-burn' },
239
+ { name: 'Hard Light', value: 'hard-light' },
240
+ { name: 'Soft Light', value: 'soft-light' },
241
+ ],
242
+ default: 'multiply',
243
+ description: 'Blend mode for color overlay',
244
+ },
245
+ {
246
+ displayName: 'Brightness',
247
+ name: 'brightness',
248
+ type: 'number',
249
+ typeOptions: {
250
+ minValue: -150,
251
+ maxValue: 150,
252
+ },
253
+ default: 0,
254
+ description: 'Brightness adjustment (-150 to 150)',
255
+ },
256
+ {
257
+ displayName: 'Contrast',
258
+ name: 'contrast',
259
+ type: 'number',
260
+ typeOptions: {
261
+ minValue: -100,
262
+ maxValue: 100,
263
+ },
264
+ default: 0,
265
+ description: 'Contrast adjustment (-100 to 100)',
266
+ },
267
+ {
268
+ displayName: 'Opacity',
269
+ name: 'opacity',
270
+ type: 'number',
271
+ typeOptions: {
272
+ minValue: 0,
273
+ maxValue: 100,
274
+ },
275
+ default: 100,
276
+ description: 'Layer opacity (0-100)',
277
+ },
278
+ ],
279
+ },
280
+ ],
281
+ },
282
+ ],
283
+ },
284
+ {
285
+ displayName: 'Export Options',
286
+ name: 'exportOptions',
287
+ type: 'collection',
288
+ placeholder: 'Add Export Option',
289
+ displayOptions: {
290
+ show: {
291
+ operation: ['render'],
292
+ },
293
+ },
294
+ default: {},
295
+ options: [
296
+ {
297
+ displayName: 'Image Format',
298
+ name: 'imageFormat',
299
+ type: 'options',
300
+ options: [
301
+ {
302
+ name: 'WebP (Recommended)',
303
+ value: 'webp',
304
+ description: '~30% smaller than PNG with similar quality',
305
+ },
306
+ {
307
+ name: 'PNG',
308
+ value: 'png',
309
+ description: 'Lossless, supports transparency',
310
+ },
311
+ {
312
+ name: 'JPEG',
313
+ value: 'jpg',
314
+ description: 'Smaller file size, no transparency',
315
+ },
316
+ ],
317
+ default: 'webp',
318
+ },
319
+ {
320
+ displayName: 'Image Size (Width)',
321
+ name: 'imageSize',
322
+ type: 'number',
323
+ typeOptions: {
324
+ minValue: 100,
325
+ maxValue: 8000,
326
+ },
327
+ default: 1920,
328
+ description: 'Output width in pixels (100-8000). Height scales proportionally.',
329
+ },
330
+ {
331
+ displayName: 'Quality',
332
+ name: 'quality',
333
+ type: 'number',
334
+ typeOptions: {
335
+ minValue: 1,
336
+ maxValue: 100,
337
+ },
338
+ default: 95,
339
+ description: 'Quality for JPG/WebP output (1-100)',
340
+ },
341
+ {
342
+ displayName: 'Export Label',
343
+ name: 'exportLabel',
344
+ type: 'string',
345
+ default: '',
346
+ description: 'Optional label for the output file naming',
347
+ },
348
+ ],
349
+ },
350
+ // ============================================
351
+ // LIST MOCKUPS PARAMETERS
352
+ // ============================================
353
+ {
354
+ displayName: 'Return All',
355
+ name: 'returnAll',
356
+ type: 'boolean',
357
+ displayOptions: {
358
+ show: {
359
+ operation: ['listMockups'],
360
+ },
361
+ },
362
+ default: false,
363
+ description: 'Whether to return all results or only up to a given limit',
364
+ },
365
+ {
366
+ displayName: 'Limit',
367
+ name: 'limit',
368
+ type: 'number',
369
+ displayOptions: {
370
+ show: {
371
+ operation: ['listMockups'],
372
+ returnAll: [false],
373
+ },
374
+ },
375
+ typeOptions: {
376
+ minValue: 1,
377
+ maxValue: 100,
378
+ },
379
+ default: 20,
380
+ description: 'Max number of results to return (1-100)',
381
+ },
382
+ {
383
+ displayName: 'Additional Options',
384
+ name: 'additionalOptions',
385
+ type: 'collection',
386
+ placeholder: 'Add Option',
387
+ displayOptions: {
388
+ show: {
389
+ operation: ['listMockups'],
390
+ },
391
+ },
392
+ default: {},
393
+ options: [
394
+ {
395
+ displayName: 'Filter by Name',
396
+ name: 'name',
397
+ type: 'string',
398
+ default: '',
399
+ description: 'Filter mockups by exact name match',
400
+ },
401
+ {
402
+ displayName: 'Created After',
403
+ name: 'created_after',
404
+ type: 'dateTime',
405
+ default: '',
406
+ description: 'Filter mockups created after this date',
407
+ },
408
+ {
409
+ displayName: 'Created Before',
410
+ name: 'created_before',
411
+ type: 'dateTime',
412
+ default: '',
413
+ description: 'Filter mockups created before this date',
414
+ },
415
+ {
416
+ displayName: 'Sort By',
417
+ name: 'sort',
418
+ type: 'options',
419
+ options: [
420
+ { name: 'Created At', value: 'created_at' },
421
+ { name: 'Updated At', value: 'updated_at' },
422
+ { name: 'Name', value: 'name' },
423
+ ],
424
+ default: 'created_at',
425
+ description: 'Field to sort results by',
426
+ },
427
+ {
428
+ displayName: 'Sort Order',
429
+ name: 'order',
430
+ type: 'options',
431
+ options: [
432
+ { name: 'Ascending', value: 'asc' },
433
+ { name: 'Descending', value: 'desc' },
434
+ ],
435
+ default: 'desc',
436
+ description: 'Sort order for results',
437
+ },
438
+ ],
439
+ },
440
+ // ============================================
441
+ // GET MOCKUP PARAMETERS
442
+ // ============================================
443
+ {
444
+ displayName: 'Mockup UUID',
445
+ name: 'getMockupUuid',
446
+ type: 'string',
447
+ required: true,
448
+ displayOptions: {
449
+ show: {
450
+ operation: ['getMockup'],
451
+ },
452
+ },
453
+ default: '',
454
+ placeholder: 'c315f78f-d2c7-4541-b240-a9372842de94',
455
+ description: 'UUID of the mockup template to retrieve',
456
+ },
457
+ // ============================================
458
+ // UPDATE MOCKUP PARAMETERS
459
+ // ============================================
460
+ {
461
+ displayName: 'Mockup UUID',
462
+ name: 'updateMockupUuid',
463
+ type: 'string',
464
+ required: true,
465
+ displayOptions: {
466
+ show: {
467
+ operation: ['updateMockup'],
468
+ },
469
+ },
470
+ default: '',
471
+ placeholder: 'c315f78f-d2c7-4541-b240-a9372842de94',
472
+ description: 'UUID of the mockup template to update',
473
+ },
474
+ {
475
+ displayName: 'New Name',
476
+ name: 'newName',
477
+ type: 'string',
478
+ required: true,
479
+ displayOptions: {
480
+ show: {
481
+ operation: ['updateMockup'],
482
+ },
483
+ },
484
+ default: '',
485
+ placeholder: 'Updated Mockup Name',
486
+ description: 'New name for the mockup template',
487
+ },
488
+ // ============================================
489
+ // DELETE MOCKUP PARAMETERS
490
+ // ============================================
491
+ {
492
+ displayName: 'Mockup UUID',
493
+ name: 'deleteMockupUuid',
494
+ type: 'string',
495
+ required: true,
496
+ displayOptions: {
497
+ show: {
498
+ operation: ['deleteMockup'],
499
+ },
500
+ },
501
+ default: '',
502
+ description: 'UUID of the mockup template to delete',
503
+ },
504
+ ],
505
+ };
506
+ async execute() {
507
+ const items = this.getInputData();
508
+ const returnData = [];
509
+ const operation = this.getNodeParameter('operation', 0);
510
+ for (let i = 0; i < items.length; i++) {
511
+ try {
512
+ // ========================================
513
+ // UPLOAD PSD
514
+ // ========================================
515
+ if (operation === 'uploadPsd') {
516
+ const psdFileUrl = this.getNodeParameter('psdFileUrl', i);
517
+ const psdName = this.getNodeParameter('psdName', i);
518
+ const body = {
519
+ psd_file_url: psdFileUrl,
520
+ };
521
+ if (psdName) {
522
+ body.psd_name = psdName;
523
+ }
524
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
525
+ method: 'POST',
526
+ url: 'https://api.sudomock.com/api/v1/psd/upload',
527
+ body,
528
+ json: true,
529
+ });
530
+ // Response: { success: true, data: { uuid, name, thumbnail, smart_objects, ... } }
531
+ returnData.push({
532
+ json: response,
533
+ pairedItem: { item: i },
534
+ });
535
+ }
536
+ // ========================================
537
+ // RENDER MOCKUP
538
+ // ========================================
539
+ else if (operation === 'render') {
540
+ const mockupUuid = this.getNodeParameter('mockupUuid', i);
541
+ const smartObjectsData = this.getNodeParameter('smartObjects.items', i, []);
542
+ const exportOptions = this.getNodeParameter('exportOptions', i, {});
543
+ // Convert smart objects array to API format
544
+ const smartObjects = smartObjectsData.map((so) => {
545
+ const smartObject = {
546
+ uuid: so.uuid,
547
+ asset: {
548
+ url: so.assetUrl,
549
+ fit: so.fit,
550
+ },
551
+ };
552
+ // Add additional options if present
553
+ if (so.additionalOptions) {
554
+ const opts = so.additionalOptions;
555
+ // Rotation
556
+ if (opts.rotate !== undefined && opts.rotate !== 0) {
557
+ smartObject.asset.rotate = opts.rotate;
558
+ }
559
+ // Color overlay
560
+ if (opts.colorHex) {
561
+ smartObject.color = {
562
+ hex: opts.colorHex,
563
+ blending_mode: opts.colorBlendMode || 'multiply',
564
+ };
565
+ }
566
+ // Adjustment layers
567
+ const adjustments = {};
568
+ if (opts.brightness !== undefined && opts.brightness !== 0) {
569
+ adjustments.brightness = opts.brightness;
570
+ }
571
+ if (opts.contrast !== undefined && opts.contrast !== 0) {
572
+ adjustments.contrast = opts.contrast;
573
+ }
574
+ if (opts.opacity !== undefined && opts.opacity !== 100) {
575
+ adjustments.opacity = opts.opacity;
576
+ }
577
+ if (Object.keys(adjustments).length > 0) {
578
+ smartObject.adjustment_layers = adjustments;
579
+ }
580
+ }
581
+ return smartObject;
582
+ });
583
+ // Request body
584
+ const body = {
585
+ mockup_uuid: mockupUuid,
586
+ smart_objects: smartObjects,
587
+ };
588
+ // Export options
589
+ if (Object.keys(exportOptions).length > 0) {
590
+ const expOpts = {};
591
+ if (exportOptions.imageFormat) {
592
+ expOpts.image_format = exportOptions.imageFormat;
593
+ }
594
+ if (exportOptions.imageSize) {
595
+ expOpts.image_size = exportOptions.imageSize;
596
+ }
597
+ if (exportOptions.quality) {
598
+ expOpts.quality = exportOptions.quality;
599
+ }
600
+ if (Object.keys(expOpts).length > 0) {
601
+ body.export_options = expOpts;
602
+ }
603
+ if (exportOptions.exportLabel) {
604
+ body.export_label = exportOptions.exportLabel;
605
+ }
606
+ }
607
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
608
+ method: 'POST',
609
+ url: 'https://api.sudomock.com/api/v1/renders',
610
+ body,
611
+ json: true,
612
+ });
613
+ // Response: { success: true, data: { print_files: [{ export_path, smart_object_uuid }] } }
614
+ // Make export paths more easily accessible
615
+ const outputJson = { ...response };
616
+ if (response.data?.print_files?.length > 0) {
617
+ // Extract first rendered image URL to top level
618
+ outputJson.renderedImageUrl = response.data.print_files[0].export_path;
619
+ // Also add all URLs as an array
620
+ outputJson.allRenderedUrls = response.data.print_files.map((pf) => pf.export_path);
621
+ }
622
+ returnData.push({
623
+ json: outputJson,
624
+ pairedItem: { item: i },
625
+ });
626
+ }
627
+ // ========================================
628
+ // GET ACCOUNT INFO
629
+ // ========================================
630
+ else if (operation === 'getAccountInfo') {
631
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
632
+ method: 'GET',
633
+ url: 'https://api.sudomock.com/api/v1/me',
634
+ json: true,
635
+ });
636
+ returnData.push({
637
+ json: response,
638
+ pairedItem: { item: i },
639
+ });
640
+ }
641
+ // ========================================
642
+ // LIST MOCKUPS
643
+ // ========================================
644
+ else if (operation === 'listMockups') {
645
+ const returnAll = this.getNodeParameter('returnAll', i);
646
+ const additionalOptions = this.getNodeParameter('additionalOptions', i, {});
647
+ let allMockups = [];
648
+ let offset = 0;
649
+ const limit = returnAll ? 100 : this.getNodeParameter('limit', i);
650
+ do {
651
+ // Build query parameters
652
+ const queryParams = {
653
+ limit: limit.toString(),
654
+ offset: offset.toString(),
655
+ };
656
+ if (additionalOptions.name) {
657
+ queryParams.name = additionalOptions.name;
658
+ }
659
+ if (additionalOptions.created_after) {
660
+ queryParams.created_after = additionalOptions.created_after;
661
+ }
662
+ if (additionalOptions.created_before) {
663
+ queryParams.created_before = additionalOptions.created_before;
664
+ }
665
+ if (additionalOptions.sort) {
666
+ queryParams.sort = additionalOptions.sort;
667
+ }
668
+ if (additionalOptions.order) {
669
+ queryParams.order = additionalOptions.order;
670
+ }
671
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
672
+ method: 'GET',
673
+ url: 'https://api.sudomock.com/api/v1/mockups',
674
+ qs: queryParams,
675
+ json: true,
676
+ });
677
+ const mockups = response.data?.mockups || [];
678
+ allMockups = allMockups.concat(mockups);
679
+ if (!returnAll || mockups.length < limit) {
680
+ break;
681
+ }
682
+ offset += limit;
683
+ } while (returnAll);
684
+ // Return all mockups as separate items
685
+ allMockups.forEach((mockup) => {
686
+ returnData.push({
687
+ json: mockup,
688
+ pairedItem: { item: i },
689
+ });
690
+ });
691
+ }
692
+ // ========================================
693
+ // GET MOCKUP
694
+ // ========================================
695
+ else if (operation === 'getMockup') {
696
+ const mockupUuid = this.getNodeParameter('getMockupUuid', i);
697
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
698
+ method: 'GET',
699
+ url: `https://api.sudomock.com/api/v1/mockups/${mockupUuid}`,
700
+ json: true,
701
+ });
702
+ returnData.push({
703
+ json: response,
704
+ pairedItem: { item: i },
705
+ });
706
+ }
707
+ // ========================================
708
+ // UPDATE MOCKUP
709
+ // ========================================
710
+ else if (operation === 'updateMockup') {
711
+ const mockupUuid = this.getNodeParameter('updateMockupUuid', i);
712
+ const newName = this.getNodeParameter('newName', i);
713
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
714
+ method: 'PATCH',
715
+ url: `https://api.sudomock.com/api/v1/mockups/${mockupUuid}`,
716
+ body: {
717
+ name: newName,
718
+ },
719
+ json: true,
720
+ });
721
+ returnData.push({
722
+ json: response,
723
+ pairedItem: { item: i },
724
+ });
725
+ }
726
+ // ========================================
727
+ // DELETE MOCKUP
728
+ // ========================================
729
+ else if (operation === 'deleteMockup') {
730
+ const mockupUuid = this.getNodeParameter('deleteMockupUuid', i);
731
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
732
+ method: 'DELETE',
733
+ url: `https://api.sudomock.com/api/v1/psd/${mockupUuid}`,
734
+ json: true,
735
+ });
736
+ returnData.push({
737
+ json: response,
738
+ pairedItem: { item: i },
739
+ });
740
+ }
741
+ // ========================================
742
+ // DELETE ALL MOCKUPS
743
+ // ========================================
744
+ else if (operation === 'deleteAllMockups') {
745
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'sudoMockApi', {
746
+ method: 'DELETE',
747
+ url: 'https://api.sudomock.com/api/v1/mockups/all',
748
+ json: true,
749
+ });
750
+ returnData.push({
751
+ json: response,
752
+ pairedItem: { item: i },
753
+ });
754
+ }
755
+ }
756
+ catch (error) {
757
+ // Enhanced rate limit error handling
758
+ if (error.statusCode === 429) {
759
+ const headers = error.response?.headers || {};
760
+ const retryAfter = headers['retry-after'] || headers['Retry-After'] || '60';
761
+ const rateLimitReset = headers['ratelimit-reset'] || headers['RateLimit-Reset'];
762
+ const errorBody = error.response?.body?.error || {};
763
+ const errorType = errorBody.type;
764
+ // Construct user-friendly error message
765
+ let errorMessage = '';
766
+ if (errorType === 'concurrent_limit_exceeded') {
767
+ const resource = errorBody.resource?.replace('concurrent-', '') || 'request';
768
+ errorMessage = `Concurrent ${resource} limit reached (${errorBody.current}/${errorBody.limit}). Please wait ${retryAfter} seconds and try again.`;
769
+ }
770
+ else {
771
+ errorMessage = `Rate limit exceeded (${errorBody.limit} requests/minute). Please retry after ${retryAfter} seconds.`;
772
+ }
773
+ if (this.continueOnFail()) {
774
+ returnData.push({
775
+ json: {
776
+ error: errorMessage,
777
+ operation,
778
+ statusCode: 429,
779
+ retryAfter: parseInt(retryAfter),
780
+ rateLimitReset: rateLimitReset ? parseInt(rateLimitReset) : undefined,
781
+ errorType: errorType || 'rate_limit_exceeded',
782
+ errorDetails: errorBody,
783
+ },
784
+ pairedItem: { item: i },
785
+ });
786
+ continue;
787
+ }
788
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage, { itemIndex: i });
789
+ }
790
+ // Handle other errors
791
+ if (this.continueOnFail()) {
792
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
793
+ returnData.push({
794
+ json: {
795
+ error: errorMessage,
796
+ operation,
797
+ statusCode: error.statusCode,
798
+ },
799
+ pairedItem: { item: i },
800
+ });
801
+ continue;
802
+ }
803
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error instanceof Error ? error : new Error('Unknown error'), { itemIndex: i });
804
+ }
805
+ }
806
+ return [returnData];
807
+ }
808
+ }
809
+ exports.SudoMock = SudoMock;