@inneropen/marvin-sdk 2.0.0-next.2 → 2.0.0-next.20
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 +136 -11
- package/dist/{HttpClient-CLtgQGXH.d.cts → HttpClient-CdF3IHM1.d.cts} +49 -2
- package/dist/{HttpClient-CLtgQGXH.d.ts → HttpClient-CdF3IHM1.d.ts} +49 -2
- package/dist/chunk-TZ2TZFWP.js +1009 -0
- package/dist/chunk-WJC4X7TB.js +738 -0
- package/dist/index.cjs +565 -25
- package/dist/index.d.cts +20 -544
- package/dist/index.d.ts +20 -544
- package/dist/index.js +27 -791
- package/dist/marvin-BAslemIz.d.cts +666 -0
- package/dist/marvin-BmbMUJXh.d.ts +666 -0
- package/dist/platform.cjs +681 -42
- package/dist/platform.d.cts +968 -94
- package/dist/platform.d.ts +968 -94
- package/dist/platform.js +449 -32
- package/dist/publish.cjs +1509 -0
- package/dist/publish.d.cts +49 -0
- package/dist/publish.d.ts +49 -0
- package/dist/publish.js +75 -0
- package/dist/types/index.d.cts +46 -1
- package/dist/types/index.d.ts +46 -1
- package/package.json +18 -7
- package/dist/chunk-25F3IF6S.js +0 -396
package/dist/platform.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HttpClient,
|
|
3
|
+
MarvinValidationError,
|
|
3
4
|
SessionAuth,
|
|
4
5
|
init_core
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WJC4X7TB.js";
|
|
6
7
|
|
|
7
8
|
// src/platform/client.ts
|
|
8
9
|
init_core();
|
|
@@ -23,7 +24,8 @@ var EntriesModule = class {
|
|
|
23
24
|
* Get a single entry by ID
|
|
24
25
|
*/
|
|
25
26
|
async get(id) {
|
|
26
|
-
|
|
27
|
+
const validId = this.http.validatePathParam(id, "entry ID");
|
|
28
|
+
return this.http.get(`/api/platform/entries/${validId}`);
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Create a new entry
|
|
@@ -35,26 +37,31 @@ var EntriesModule = class {
|
|
|
35
37
|
* Update an entry
|
|
36
38
|
*/
|
|
37
39
|
async update(id, data) {
|
|
38
|
-
|
|
40
|
+
const validId = this.http.validatePathParam(id, "entry ID");
|
|
41
|
+
return this.http.patch(`/api/platform/entries/${validId}`, data);
|
|
39
42
|
}
|
|
40
43
|
/**
|
|
41
44
|
* Delete an entry
|
|
42
45
|
*/
|
|
43
46
|
async delete(id) {
|
|
44
|
-
|
|
47
|
+
const validId = this.http.validatePathParam(id, "entry ID");
|
|
48
|
+
return this.http.delete(`/api/platform/entries/${validId}`);
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
51
|
* Get collections that this entry belongs to
|
|
48
52
|
*/
|
|
49
53
|
async listCollections(entryId) {
|
|
50
|
-
|
|
54
|
+
const validId = this.http.validatePathParam(entryId, "entry ID");
|
|
55
|
+
return this.http.get(`/api/platform/entries/${validId}/collections`);
|
|
51
56
|
}
|
|
52
57
|
/**
|
|
53
58
|
* Add entry to a collection
|
|
54
59
|
*/
|
|
55
60
|
async addToCollection(entryId, collectionId) {
|
|
61
|
+
const validEntryId = this.http.validatePathParam(entryId, "entry ID");
|
|
62
|
+
const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
|
|
56
63
|
return this.http.post(
|
|
57
|
-
`/api/platform/entries/${
|
|
64
|
+
`/api/platform/entries/${validEntryId}/collections/${validCollectionId}`,
|
|
58
65
|
{}
|
|
59
66
|
);
|
|
60
67
|
}
|
|
@@ -62,7 +69,9 @@ var EntriesModule = class {
|
|
|
62
69
|
* Remove entry from a collection
|
|
63
70
|
*/
|
|
64
71
|
async removeFromCollection(entryId, collectionId) {
|
|
65
|
-
|
|
72
|
+
const validEntryId = this.http.validatePathParam(entryId, "entry ID");
|
|
73
|
+
const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
|
|
74
|
+
return this.http.delete(`/api/platform/entries/${validEntryId}/collections/${validCollectionId}`);
|
|
66
75
|
}
|
|
67
76
|
};
|
|
68
77
|
|
|
@@ -82,7 +91,8 @@ var CollectionsModule = class {
|
|
|
82
91
|
* Get a single collection by ID
|
|
83
92
|
*/
|
|
84
93
|
async get(id) {
|
|
85
|
-
|
|
94
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
95
|
+
return this.http.get(`/api/platform/collections/${validId}`);
|
|
86
96
|
}
|
|
87
97
|
/**
|
|
88
98
|
* Create a new collection
|
|
@@ -94,25 +104,29 @@ var CollectionsModule = class {
|
|
|
94
104
|
* Update a collection
|
|
95
105
|
*/
|
|
96
106
|
async update(id, data) {
|
|
97
|
-
|
|
107
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
108
|
+
return this.http.patch(`/api/platform/collections/${validId}`, data);
|
|
98
109
|
}
|
|
99
110
|
/**
|
|
100
111
|
* Delete a collection
|
|
101
112
|
*/
|
|
102
113
|
async delete(id) {
|
|
103
|
-
|
|
114
|
+
const validId = this.http.validatePathParam(id, "collection ID");
|
|
115
|
+
return this.http.delete(`/api/platform/collections/${validId}`);
|
|
104
116
|
}
|
|
105
117
|
/**
|
|
106
118
|
* Get entries in a collection
|
|
107
119
|
*/
|
|
108
120
|
async getEntries(id) {
|
|
109
|
-
|
|
121
|
+
const validId = this.http.validatePathParam(id, "collection ID");
|
|
122
|
+
return this.http.get(`/api/platform/collections/${validId}/entries`);
|
|
110
123
|
}
|
|
111
124
|
/**
|
|
112
125
|
* Reorder entries in a collection
|
|
113
126
|
*/
|
|
114
127
|
async reorderEntries(id, entries) {
|
|
115
|
-
|
|
128
|
+
const validId = this.http.validatePathParam(id, "collection ID");
|
|
129
|
+
return this.http.patch(`/api/platform/collections/${validId}/entries/order`, { entries });
|
|
116
130
|
}
|
|
117
131
|
};
|
|
118
132
|
|
|
@@ -132,7 +146,8 @@ var ResourcesModule = class {
|
|
|
132
146
|
* Get a single resource by ID
|
|
133
147
|
*/
|
|
134
148
|
async get(id) {
|
|
135
|
-
|
|
149
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
150
|
+
return this.http.get(`/api/platform/resources/${validId}`);
|
|
136
151
|
}
|
|
137
152
|
/**
|
|
138
153
|
* Create a new resource
|
|
@@ -144,7 +159,8 @@ var ResourcesModule = class {
|
|
|
144
159
|
* Update a resource
|
|
145
160
|
*/
|
|
146
161
|
async update(id, data) {
|
|
147
|
-
|
|
162
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
163
|
+
return this.http.patch(`/api/platform/resources/${validId}`, data);
|
|
148
164
|
}
|
|
149
165
|
/**
|
|
150
166
|
* Delete a resource
|
|
@@ -161,11 +177,53 @@ var ResourcesModule = class {
|
|
|
161
177
|
};
|
|
162
178
|
|
|
163
179
|
// src/platform/assets.ts
|
|
180
|
+
init_core();
|
|
164
181
|
var AssetsModule = class {
|
|
165
182
|
constructor(http) {
|
|
166
183
|
this.http = http;
|
|
167
184
|
}
|
|
168
185
|
http;
|
|
186
|
+
/**
|
|
187
|
+
* Validate file before upload
|
|
188
|
+
*/
|
|
189
|
+
validateFile(file, options) {
|
|
190
|
+
const maxSize = options?.maxFileSize || 100 * 1024 * 1024;
|
|
191
|
+
const allowedTypes = options?.allowedMimeTypes;
|
|
192
|
+
if (file.size === 0) {
|
|
193
|
+
throw new MarvinValidationError("File is empty");
|
|
194
|
+
}
|
|
195
|
+
if (file.size > maxSize) {
|
|
196
|
+
const maxMB = Math.round(maxSize / (1024 * 1024));
|
|
197
|
+
const fileMB = Math.round(file.size / (1024 * 1024));
|
|
198
|
+
throw new MarvinValidationError(
|
|
199
|
+
`File size (${fileMB}MB) exceeds maximum allowed size of ${maxMB}MB`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (allowedTypes && allowedTypes.length > 0) {
|
|
203
|
+
if (file instanceof File && file.type) {
|
|
204
|
+
const isAllowed = allowedTypes.some((allowed) => {
|
|
205
|
+
if (allowed.endsWith("/*")) {
|
|
206
|
+
const prefix = allowed.slice(0, -2);
|
|
207
|
+
return file.type.startsWith(prefix);
|
|
208
|
+
}
|
|
209
|
+
return file.type === allowed;
|
|
210
|
+
});
|
|
211
|
+
if (!isAllowed) {
|
|
212
|
+
throw new MarvinValidationError(
|
|
213
|
+
`File type "${file.type}" is not allowed. Allowed types: ${allowedTypes.join(", ")}`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (file instanceof File) {
|
|
219
|
+
if (!file.name || file.name.trim().length === 0) {
|
|
220
|
+
throw new MarvinValidationError("File must have a valid name");
|
|
221
|
+
}
|
|
222
|
+
if (file.name.includes("..") || file.name.includes("/") || file.name.includes("\\")) {
|
|
223
|
+
throw new MarvinValidationError("Invalid filename: cannot contain path separators");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
169
227
|
/**
|
|
170
228
|
* List all assets
|
|
171
229
|
*/
|
|
@@ -176,16 +234,31 @@ var AssetsModule = class {
|
|
|
176
234
|
* Get a single asset by ID
|
|
177
235
|
*/
|
|
178
236
|
async get(id) {
|
|
179
|
-
|
|
237
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
238
|
+
return this.http.get(`/api/platform/assets/${validId}`);
|
|
180
239
|
}
|
|
181
240
|
/**
|
|
182
241
|
* Upload a new asset file
|
|
183
242
|
*
|
|
184
243
|
* @param file - The file to upload (File or Blob)
|
|
185
244
|
* @param metadata - Asset metadata (slug, name, altText, description, metadata)
|
|
245
|
+
* @param options - Upload options (file size limits, allowed types)
|
|
186
246
|
* @returns The created asset with extracted metadata
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* // Upload with default limits (100MB, all types)
|
|
251
|
+
* const asset = await assets.upload(file, { slug: 'my-image', name: 'My Image' });
|
|
252
|
+
*
|
|
253
|
+
* // Upload with restrictions
|
|
254
|
+
* const asset = await assets.upload(file,
|
|
255
|
+
* { slug: 'avatar', name: 'Avatar' },
|
|
256
|
+
* { maxFileSize: 5 * 1024 * 1024, allowedMimeTypes: ['image/*'] }
|
|
257
|
+
* );
|
|
258
|
+
* ```
|
|
187
259
|
*/
|
|
188
|
-
async upload(file, metadata) {
|
|
260
|
+
async upload(file, metadata, options) {
|
|
261
|
+
this.validateFile(file, options);
|
|
189
262
|
const formData = new FormData();
|
|
190
263
|
formData.append("file", file);
|
|
191
264
|
formData.append("slug", metadata.slug);
|
|
@@ -205,13 +278,24 @@ var AssetsModule = class {
|
|
|
205
278
|
* Update an asset (editable fields only: slug, name, altText, description, metadata)
|
|
206
279
|
*/
|
|
207
280
|
async update(id, data) {
|
|
208
|
-
|
|
281
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
282
|
+
return this.http.patch(`/api/platform/assets/${validId}`, data);
|
|
209
283
|
}
|
|
210
284
|
/**
|
|
211
285
|
* Delete an asset (removes file from storage and database)
|
|
212
286
|
*/
|
|
213
287
|
async delete(id) {
|
|
214
|
-
|
|
288
|
+
const validId = this.http.validatePathParam(id, "asset ID");
|
|
289
|
+
return this.http.delete(`/api/platform/assets/${validId}`);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get download URL for an asset file
|
|
293
|
+
* Use this to create download links or fetch the file directly
|
|
294
|
+
*/
|
|
295
|
+
getDownloadUrl(id, baseUrl) {
|
|
296
|
+
const validId = this.http.validatePathParam(id, "asset ID");
|
|
297
|
+
const base = baseUrl || (typeof window !== "undefined" && window.location ? window.location.origin : "");
|
|
298
|
+
return `${base}/api/platform/assets/${validId}/file`;
|
|
215
299
|
}
|
|
216
300
|
};
|
|
217
301
|
|
|
@@ -231,7 +315,8 @@ var APIClientsModule = class {
|
|
|
231
315
|
* Get a single API client by ID
|
|
232
316
|
*/
|
|
233
317
|
async get(id) {
|
|
234
|
-
|
|
318
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
319
|
+
return this.http.get(`/api/platform/api-clients/${validId}`);
|
|
235
320
|
}
|
|
236
321
|
/**
|
|
237
322
|
* Create a new API client (returns client with token)
|
|
@@ -243,7 +328,8 @@ var APIClientsModule = class {
|
|
|
243
328
|
* Update an API client
|
|
244
329
|
*/
|
|
245
330
|
async update(id, data) {
|
|
246
|
-
|
|
331
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
332
|
+
return this.http.patch(`/api/platform/api-clients/${validId}`, data);
|
|
247
333
|
}
|
|
248
334
|
/**
|
|
249
335
|
* Delete an API client
|
|
@@ -407,6 +493,15 @@ var WorkspacesModule = class {
|
|
|
407
493
|
const url = `/api/admin/groups/${id}${force ? "?force=true" : ""}`;
|
|
408
494
|
await this.http.delete(url);
|
|
409
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Export workspace data as JSON (collections, entry types, entries, site config)
|
|
498
|
+
*/
|
|
499
|
+
async export(options) {
|
|
500
|
+
const pretty = options?.pretty ?? true;
|
|
501
|
+
const endpoint = pretty ? "/api/platform/workspace/export/pretty" : "/api/platform/workspace/export";
|
|
502
|
+
const params = options?.includeSystemTypes ? { include_system_types: true } : void 0;
|
|
503
|
+
return this.http.get(endpoint, params);
|
|
504
|
+
}
|
|
410
505
|
/**
|
|
411
506
|
* Get workspace preferences
|
|
412
507
|
*/
|
|
@@ -454,11 +549,23 @@ var InvitesModule = class {
|
|
|
454
549
|
}
|
|
455
550
|
/**
|
|
456
551
|
* Generate invitation URL from a token
|
|
457
|
-
*
|
|
552
|
+
*
|
|
553
|
+
* @param token - Invitation token
|
|
554
|
+
* @param baseUrl - Base URL for the frontend (required in browser environments)
|
|
555
|
+
* @returns Full invitation URL
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```typescript
|
|
559
|
+
* // Node.js (can use env vars)
|
|
560
|
+
* const url = invites.getInvitationUrl(token);
|
|
561
|
+
*
|
|
562
|
+
* // Browser (must provide baseUrl)
|
|
563
|
+
* const url = invites.getInvitationUrl(token, 'https://app.example.com');
|
|
564
|
+
* ```
|
|
458
565
|
*/
|
|
459
566
|
getInvitationUrl(token, baseUrl) {
|
|
460
567
|
let base = baseUrl;
|
|
461
|
-
if (!base) {
|
|
568
|
+
if (!base && typeof process !== "undefined" && process.env) {
|
|
462
569
|
base = process.env.MARVIN_FRONTEND_URL;
|
|
463
570
|
if (!base && process.env.MARVIN_API_URL) {
|
|
464
571
|
const apiUrl = process.env.MARVIN_API_URL;
|
|
@@ -470,11 +577,17 @@ var InvitesModule = class {
|
|
|
470
577
|
base = apiUrl;
|
|
471
578
|
}
|
|
472
579
|
}
|
|
473
|
-
|
|
474
|
-
|
|
580
|
+
}
|
|
581
|
+
if (!base) {
|
|
582
|
+
if (typeof window !== "undefined" && window.location) {
|
|
583
|
+
base = window.location.origin;
|
|
584
|
+
} else {
|
|
585
|
+
throw new Error(
|
|
586
|
+
'baseUrl is required when not in Node.js environment. Pass the frontend URL as second argument: getInvitationUrl(token, "https://app.example.com")'
|
|
587
|
+
);
|
|
475
588
|
}
|
|
476
589
|
}
|
|
477
|
-
return `${base}/register?token=${token}`;
|
|
590
|
+
return `${base}/register?token=${encodeURIComponent(token)}`;
|
|
478
591
|
}
|
|
479
592
|
};
|
|
480
593
|
|
|
@@ -523,11 +636,63 @@ var NotificationsModule = class {
|
|
|
523
636
|
};
|
|
524
637
|
|
|
525
638
|
// src/platform/webhooks.ts
|
|
639
|
+
init_core();
|
|
526
640
|
var WebhooksModule = class {
|
|
527
641
|
constructor(http) {
|
|
528
642
|
this.http = http;
|
|
529
643
|
}
|
|
530
644
|
http;
|
|
645
|
+
/**
|
|
646
|
+
* Validate webhook URL to prevent SSRF attacks
|
|
647
|
+
*/
|
|
648
|
+
validateWebhookUrl(url) {
|
|
649
|
+
if (!url || typeof url !== "string") {
|
|
650
|
+
throw new MarvinValidationError("Webhook URL is required and must be a string");
|
|
651
|
+
}
|
|
652
|
+
let parsed;
|
|
653
|
+
try {
|
|
654
|
+
parsed = new URL(url);
|
|
655
|
+
} catch {
|
|
656
|
+
throw new MarvinValidationError("Invalid webhook URL format. Expected: https://example.com/webhook");
|
|
657
|
+
}
|
|
658
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
659
|
+
throw new MarvinValidationError(
|
|
660
|
+
`Webhook URL must use HTTP or HTTPS protocol. Got: ${parsed.protocol}`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
664
|
+
const blockedHosts = [
|
|
665
|
+
"localhost",
|
|
666
|
+
"127.0.0.1",
|
|
667
|
+
"0.0.0.0",
|
|
668
|
+
"::1",
|
|
669
|
+
"169.254.169.254",
|
|
670
|
+
// AWS metadata service
|
|
671
|
+
"[::]"
|
|
672
|
+
];
|
|
673
|
+
if (blockedHosts.some((blocked) => hostname === blocked || hostname.includes(blocked))) {
|
|
674
|
+
throw new MarvinValidationError(
|
|
675
|
+
"Webhook URL cannot target localhost or internal network addresses"
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
679
|
+
const match = hostname.match(ipv4Regex);
|
|
680
|
+
if (match) {
|
|
681
|
+
const octets = match.slice(1).map(Number);
|
|
682
|
+
if (octets[0] === 10) {
|
|
683
|
+
throw new MarvinValidationError("Webhook URL cannot target private IP range (10.x.x.x)");
|
|
684
|
+
}
|
|
685
|
+
if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
|
|
686
|
+
throw new MarvinValidationError("Webhook URL cannot target private IP range (172.16-31.x.x)");
|
|
687
|
+
}
|
|
688
|
+
if (octets[0] === 192 && octets[1] === 168) {
|
|
689
|
+
throw new MarvinValidationError("Webhook URL cannot target private IP range (192.168.x.x)");
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (url.length > 2048) {
|
|
693
|
+
throw new MarvinValidationError("Webhook URL exceeds maximum length of 2048 characters");
|
|
694
|
+
}
|
|
695
|
+
}
|
|
531
696
|
/**
|
|
532
697
|
* List all webhooks for the current workspace
|
|
533
698
|
*/
|
|
@@ -539,31 +704,43 @@ var WebhooksModule = class {
|
|
|
539
704
|
* Get a webhook by ID
|
|
540
705
|
*/
|
|
541
706
|
async get(id) {
|
|
542
|
-
|
|
707
|
+
const validId = this.http.validatePathParam(id, "webhook ID");
|
|
708
|
+
return this.http.get(`/api/groups/webhooks/${validId}`);
|
|
543
709
|
}
|
|
544
710
|
/**
|
|
545
711
|
* Create a new webhook
|
|
712
|
+
* Validates URL to prevent SSRF attacks
|
|
546
713
|
*/
|
|
547
714
|
async create(data) {
|
|
715
|
+
if (data.url) {
|
|
716
|
+
this.validateWebhookUrl(data.url);
|
|
717
|
+
}
|
|
548
718
|
return this.http.post("/api/groups/webhooks", data);
|
|
549
719
|
}
|
|
550
720
|
/**
|
|
551
721
|
* Update a webhook
|
|
722
|
+
* Validates URL if provided to prevent SSRF attacks
|
|
552
723
|
*/
|
|
553
724
|
async update(id, data) {
|
|
554
|
-
|
|
725
|
+
const validId = this.http.validatePathParam(id, "webhook ID");
|
|
726
|
+
if (data.url) {
|
|
727
|
+
this.validateWebhookUrl(data.url);
|
|
728
|
+
}
|
|
729
|
+
return this.http.put(`/api/groups/webhooks/${validId}`, data);
|
|
555
730
|
}
|
|
556
731
|
/**
|
|
557
732
|
* Delete a webhook
|
|
558
733
|
*/
|
|
559
734
|
async delete(id) {
|
|
560
|
-
|
|
735
|
+
const validId = this.http.validatePathParam(id, "webhook ID");
|
|
736
|
+
await this.http.delete(`/api/groups/webhooks/${validId}`);
|
|
561
737
|
}
|
|
562
738
|
/**
|
|
563
739
|
* Test a webhook
|
|
564
740
|
*/
|
|
565
741
|
async test(id) {
|
|
566
|
-
|
|
742
|
+
const validId = this.http.validatePathParam(id, "webhook ID");
|
|
743
|
+
return this.http.post(`/api/groups/webhooks/${validId}/test`, {});
|
|
567
744
|
}
|
|
568
745
|
/**
|
|
569
746
|
* Rerun failed webhooks
|
|
@@ -655,6 +832,7 @@ var AdminSystemModule = class {
|
|
|
655
832
|
}
|
|
656
833
|
/**
|
|
657
834
|
* Update email settings
|
|
835
|
+
* Note: Password field is write-only and will not be returned in response
|
|
658
836
|
*/
|
|
659
837
|
async updateEmailSettings(data) {
|
|
660
838
|
return this.http.put("/api/admin/email", data);
|
|
@@ -930,7 +1108,235 @@ var ThemeModule = class {
|
|
|
930
1108
|
}
|
|
931
1109
|
};
|
|
932
1110
|
|
|
1111
|
+
// src/platform/emailTemplates.ts
|
|
1112
|
+
var EmailTemplatesClient = class {
|
|
1113
|
+
constructor(http) {
|
|
1114
|
+
this.http = http;
|
|
1115
|
+
}
|
|
1116
|
+
http;
|
|
1117
|
+
/**
|
|
1118
|
+
* List all email templates for the workspace
|
|
1119
|
+
* @param groupId - Workspace ID
|
|
1120
|
+
*/
|
|
1121
|
+
async list(groupId) {
|
|
1122
|
+
return this.http.get(`/api/platform/workspaces/${groupId}/email-templates`);
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Get a specific email template
|
|
1126
|
+
* @param groupId - Workspace ID
|
|
1127
|
+
* @param templateId - Template ID
|
|
1128
|
+
*/
|
|
1129
|
+
async get(groupId, templateId) {
|
|
1130
|
+
return this.http.get(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Create a new email template
|
|
1134
|
+
* @param groupId - Workspace ID
|
|
1135
|
+
* @param data - Template data
|
|
1136
|
+
*/
|
|
1137
|
+
async create(groupId, data) {
|
|
1138
|
+
return this.http.post(`/api/platform/workspaces/${groupId}/email-templates`, data);
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Update an email template
|
|
1142
|
+
* @param groupId - Workspace ID
|
|
1143
|
+
* @param templateId - Template ID
|
|
1144
|
+
* @param data - Updated template data
|
|
1145
|
+
*/
|
|
1146
|
+
async update(groupId, templateId, data) {
|
|
1147
|
+
return this.http.patch(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`, data);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Delete an email template
|
|
1151
|
+
* @param groupId - Workspace ID
|
|
1152
|
+
* @param templateId - Template ID
|
|
1153
|
+
*/
|
|
1154
|
+
async delete(groupId, templateId) {
|
|
1155
|
+
await this.http.delete(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Send a test email using a template
|
|
1159
|
+
* @param groupId - Workspace ID
|
|
1160
|
+
* @param templateId - Template ID
|
|
1161
|
+
* @param recipientEmail - Test recipient email address
|
|
1162
|
+
*/
|
|
1163
|
+
async sendTest(groupId, templateId, recipientEmail) {
|
|
1164
|
+
return this.http.post(`/api/platform/workspaces/${groupId}/email-templates/${templateId}/test`, {
|
|
1165
|
+
recipient_email: recipientEmail
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
// src/platform/scheduledTasks.ts
|
|
1171
|
+
var ScheduledTasksModule = class {
|
|
1172
|
+
constructor(http) {
|
|
1173
|
+
this.http = http;
|
|
1174
|
+
}
|
|
1175
|
+
http;
|
|
1176
|
+
/**
|
|
1177
|
+
* List all scheduled tasks for the current workspace
|
|
1178
|
+
*/
|
|
1179
|
+
async list() {
|
|
1180
|
+
return this.http.get("/api/platform/scheduled-tasks");
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Get a scheduled task by ID or slug
|
|
1184
|
+
*/
|
|
1185
|
+
async get(idOrSlug) {
|
|
1186
|
+
const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
|
|
1187
|
+
return this.http.get(`/api/platform/scheduled-tasks/${validIdOrSlug}`);
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Create a new scheduled task
|
|
1191
|
+
*/
|
|
1192
|
+
async create(data) {
|
|
1193
|
+
return this.http.post("/api/platform/scheduled-tasks", data);
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Update a scheduled task
|
|
1197
|
+
*/
|
|
1198
|
+
async update(idOrSlug, data) {
|
|
1199
|
+
const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
|
|
1200
|
+
return this.http.patch(`/api/platform/scheduled-tasks/${validIdOrSlug}`, data);
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Delete a scheduled task
|
|
1204
|
+
*/
|
|
1205
|
+
async delete(idOrSlug) {
|
|
1206
|
+
await this.http.delete(`/api/platform/scheduled-tasks/${idOrSlug}`);
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Manually trigger task execution (bypass schedule)
|
|
1210
|
+
*/
|
|
1211
|
+
async execute(idOrSlug) {
|
|
1212
|
+
await this.http.post(`/api/platform/scheduled-tasks/${idOrSlug}/execute`, {});
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Get task execution history
|
|
1216
|
+
*/
|
|
1217
|
+
async history(idOrSlug, options) {
|
|
1218
|
+
const params = new URLSearchParams();
|
|
1219
|
+
if (options?.limit) {
|
|
1220
|
+
params.set("limit", options.limit.toString());
|
|
1221
|
+
}
|
|
1222
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1223
|
+
return this.http.get(
|
|
1224
|
+
`/api/platform/scheduled-tasks/${idOrSlug}/history${query}`
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Get available task types
|
|
1229
|
+
*/
|
|
1230
|
+
async taskTypes(options) {
|
|
1231
|
+
const params = new URLSearchParams();
|
|
1232
|
+
if (options?.detailed) {
|
|
1233
|
+
params.set("detailed", "true");
|
|
1234
|
+
}
|
|
1235
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1236
|
+
return this.http.get(`/api/platform/scheduled-tasks/task-types${query}`);
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
// src/platform/forms.ts
|
|
1241
|
+
init_core();
|
|
1242
|
+
var FormsModule = class {
|
|
1243
|
+
constructor(http) {
|
|
1244
|
+
this.http = http;
|
|
1245
|
+
}
|
|
1246
|
+
http;
|
|
1247
|
+
/**
|
|
1248
|
+
* Validate form submission data
|
|
1249
|
+
* - Prevents XSS by checking for script tags
|
|
1250
|
+
* - Validates data is a proper object
|
|
1251
|
+
* - Checks for reasonable size limits
|
|
1252
|
+
*/
|
|
1253
|
+
validateFormData(data) {
|
|
1254
|
+
if (!data || typeof data !== "object") {
|
|
1255
|
+
throw new MarvinValidationError("Form data must be a valid object");
|
|
1256
|
+
}
|
|
1257
|
+
const jsonString = JSON.stringify(data);
|
|
1258
|
+
const sizeInBytes = new Blob([jsonString]).size;
|
|
1259
|
+
if (sizeInBytes > 1024 * 1024) {
|
|
1260
|
+
throw new MarvinValidationError("Form data exceeds maximum size of 1MB");
|
|
1261
|
+
}
|
|
1262
|
+
const checkForScripts = (value) => {
|
|
1263
|
+
if (typeof value === "string") {
|
|
1264
|
+
if (/<script[\s\S]*?>[\s\S]*?<\/script>/gi.test(value)) {
|
|
1265
|
+
throw new MarvinValidationError("Form data contains potentially malicious content");
|
|
1266
|
+
}
|
|
1267
|
+
} else if (Array.isArray(value)) {
|
|
1268
|
+
value.forEach(checkForScripts);
|
|
1269
|
+
} else if (value && typeof value === "object") {
|
|
1270
|
+
Object.values(value).forEach(checkForScripts);
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
Object.values(data).forEach(checkForScripts);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* List all forms
|
|
1277
|
+
*/
|
|
1278
|
+
async list() {
|
|
1279
|
+
return this.http.get("/api/platform/forms");
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Get a single form by ID
|
|
1283
|
+
*/
|
|
1284
|
+
async get(id) {
|
|
1285
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
1286
|
+
return this.http.get(`/api/platform/forms/${validId}`);
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Create a new form
|
|
1290
|
+
*/
|
|
1291
|
+
async create(data) {
|
|
1292
|
+
return this.http.post("/api/platform/forms", data);
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Update a form
|
|
1296
|
+
*/
|
|
1297
|
+
async update(id, data) {
|
|
1298
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
1299
|
+
return this.http.patch(`/api/platform/forms/${validId}`, data);
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Delete a form
|
|
1303
|
+
*/
|
|
1304
|
+
async delete(id) {
|
|
1305
|
+
return this.http.delete(`/api/platform/forms/${id}`);
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Get submissions for a form
|
|
1309
|
+
*/
|
|
1310
|
+
async getSubmissions(formId) {
|
|
1311
|
+
return this.http.get(`/api/platform/forms/${formId}/submissions`);
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Get a published form by slug (Publishing API - public access)
|
|
1315
|
+
*/
|
|
1316
|
+
async getPublishedForm(slug) {
|
|
1317
|
+
return this.http.get(`/api/publish/forms/${slug}`);
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Submit data to a published form (Publishing API - public access)
|
|
1321
|
+
* Validates data before submission to prevent XSS and malicious content
|
|
1322
|
+
*/
|
|
1323
|
+
async submitForm(slug, data) {
|
|
1324
|
+
const validSlug = this.http.validatePathParam(slug, "form slug");
|
|
1325
|
+
this.validateFormData(data);
|
|
1326
|
+
return this.http.post(`/api/publish/forms/${validSlug}/submit`, {
|
|
1327
|
+
dataJson: data
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
|
|
933
1332
|
// src/platform/client.ts
|
|
1333
|
+
function createPlatformConfigFromEnv(overrides) {
|
|
1334
|
+
return {
|
|
1335
|
+
apiUrl: overrides?.apiUrl || process.env.MARVIN_API_URL || "http://localhost:8080",
|
|
1336
|
+
userToken: overrides?.userToken || process.env.MARVIN_USER_TOKEN,
|
|
1337
|
+
credentials: overrides?.credentials || "include"
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
934
1340
|
var PlatformClient = class extends HttpClient {
|
|
935
1341
|
entries;
|
|
936
1342
|
collections;
|
|
@@ -948,13 +1354,16 @@ var PlatformClient = class extends HttpClient {
|
|
|
948
1354
|
eventLog;
|
|
949
1355
|
session;
|
|
950
1356
|
theme;
|
|
1357
|
+
emailTemplates;
|
|
1358
|
+
scheduledTasks;
|
|
1359
|
+
forms;
|
|
951
1360
|
// Admin modules
|
|
952
1361
|
adminUsers;
|
|
953
1362
|
adminSystem;
|
|
954
1363
|
adminMaintenance;
|
|
955
1364
|
constructor(config) {
|
|
956
1365
|
const httpConfig = {
|
|
957
|
-
baseUrl: config.apiUrl,
|
|
1366
|
+
baseUrl: config.apiUrl || "http://localhost:8080",
|
|
958
1367
|
auth: new SessionAuth(config.userToken),
|
|
959
1368
|
credentials: config.credentials || "include"
|
|
960
1369
|
};
|
|
@@ -975,13 +1384,17 @@ var PlatformClient = class extends HttpClient {
|
|
|
975
1384
|
this.eventLog = new EventLogModule(this);
|
|
976
1385
|
this.session = new AuthModule(this);
|
|
977
1386
|
this.theme = new ThemeModule(this);
|
|
1387
|
+
this.emailTemplates = new EmailTemplatesClient(this);
|
|
1388
|
+
this.scheduledTasks = new ScheduledTasksModule(this);
|
|
1389
|
+
this.forms = new FormsModule(this);
|
|
978
1390
|
this.adminUsers = new AdminUsersModule(this);
|
|
979
1391
|
this.adminSystem = new AdminSystemModule(this);
|
|
980
1392
|
this.adminMaintenance = new AdminMaintenanceModule(this);
|
|
981
1393
|
}
|
|
982
1394
|
};
|
|
983
1395
|
function createPlatformClient(config) {
|
|
984
|
-
|
|
1396
|
+
const finalConfig = createPlatformConfigFromEnv(config);
|
|
1397
|
+
return new PlatformClient(finalConfig);
|
|
985
1398
|
}
|
|
986
1399
|
export {
|
|
987
1400
|
APIClientsModule,
|
|
@@ -991,18 +1404,22 @@ export {
|
|
|
991
1404
|
AssetsModule,
|
|
992
1405
|
AuthModule,
|
|
993
1406
|
CollectionsModule,
|
|
1407
|
+
EmailTemplatesClient,
|
|
994
1408
|
EntriesModule,
|
|
995
1409
|
EntryTypesModule,
|
|
996
1410
|
EventLogModule,
|
|
997
1411
|
EventsModule,
|
|
1412
|
+
FormsModule,
|
|
998
1413
|
InvitesModule,
|
|
999
1414
|
NotificationsModule,
|
|
1000
1415
|
PlatformClient,
|
|
1001
1416
|
ResourcesModule,
|
|
1417
|
+
ScheduledTasksModule,
|
|
1002
1418
|
ThemeModule,
|
|
1003
1419
|
UserModule,
|
|
1004
1420
|
WebhooksModule,
|
|
1005
1421
|
WorkspaceMembersModule,
|
|
1006
1422
|
WorkspacesModule,
|
|
1007
|
-
createPlatformClient
|
|
1423
|
+
createPlatformClient,
|
|
1424
|
+
createPlatformConfigFromEnv
|
|
1008
1425
|
};
|