@inneropen/marvin-sdk 2.0.0-next.17 → 2.0.0-next.2
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 +11 -136
- package/dist/{HttpClient-CdF3IHM1.d.cts → HttpClient-CLtgQGXH.d.cts} +2 -49
- package/dist/{HttpClient-CdF3IHM1.d.ts → HttpClient-CLtgQGXH.d.ts} +2 -49
- package/dist/chunk-25F3IF6S.js +396 -0
- package/dist/index.cjs +25 -536
- package/dist/index.d.cts +544 -20
- package/dist/index.d.ts +544 -20
- package/dist/index.js +791 -27
- package/dist/platform.cjs +42 -672
- package/dist/platform.d.cts +76 -804
- package/dist/platform.d.ts +76 -804
- package/dist/platform.js +32 -440
- package/dist/types/index.d.cts +1 -9
- package/dist/types/index.d.ts +1 -9
- package/package.json +7 -18
- package/dist/chunk-GJNOYXUQ.js +0 -980
- package/dist/chunk-WJC4X7TB.js +0 -738
- package/dist/marvin-Bx5k6tsF.d.cts +0 -654
- package/dist/marvin-g7dayc83.d.ts +0 -654
- package/dist/publish.cjs +0 -1487
- package/dist/publish.d.cts +0 -49
- package/dist/publish.d.ts +0 -49
- package/dist/publish.js +0 -75
package/dist/platform.cjs
CHANGED
|
@@ -27,24 +27,20 @@ __export(platform_exports, {
|
|
|
27
27
|
AssetsModule: () => AssetsModule,
|
|
28
28
|
AuthModule: () => AuthModule,
|
|
29
29
|
CollectionsModule: () => CollectionsModule,
|
|
30
|
-
EmailTemplatesClient: () => EmailTemplatesClient,
|
|
31
30
|
EntriesModule: () => EntriesModule,
|
|
32
31
|
EntryTypesModule: () => EntryTypesModule,
|
|
33
32
|
EventLogModule: () => EventLogModule,
|
|
34
33
|
EventsModule: () => EventsModule,
|
|
35
|
-
FormsModule: () => FormsModule,
|
|
36
34
|
InvitesModule: () => InvitesModule,
|
|
37
35
|
NotificationsModule: () => NotificationsModule,
|
|
38
36
|
PlatformClient: () => PlatformClient,
|
|
39
37
|
ResourcesModule: () => ResourcesModule,
|
|
40
|
-
ScheduledTasksModule: () => ScheduledTasksModule,
|
|
41
38
|
ThemeModule: () => ThemeModule,
|
|
42
39
|
UserModule: () => UserModule,
|
|
43
40
|
WebhooksModule: () => WebhooksModule,
|
|
44
41
|
WorkspaceMembersModule: () => WorkspaceMembersModule,
|
|
45
42
|
WorkspacesModule: () => WorkspacesModule,
|
|
46
|
-
createPlatformClient: () => createPlatformClient
|
|
47
|
-
createPlatformConfigFromEnv: () => createPlatformConfigFromEnv
|
|
43
|
+
createPlatformClient: () => createPlatformClient
|
|
48
44
|
});
|
|
49
45
|
module.exports = __toCommonJS(platform_exports);
|
|
50
46
|
|
|
@@ -78,49 +74,6 @@ ${body}` : ""}`;
|
|
|
78
74
|
return new _MarvinApiError(message, status, endpoint, body);
|
|
79
75
|
}
|
|
80
76
|
};
|
|
81
|
-
var MarvinAuthError = class _MarvinAuthError extends MarvinError {
|
|
82
|
-
constructor(message, statusCode) {
|
|
83
|
-
super(message, "AUTH_ERROR", statusCode);
|
|
84
|
-
this.name = "MarvinAuthError";
|
|
85
|
-
Object.setPrototypeOf(this, _MarvinAuthError.prototype);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
var MarvinValidationError = class _MarvinValidationError extends MarvinError {
|
|
89
|
-
constructor(message, field) {
|
|
90
|
-
super(message, "VALIDATION_ERROR");
|
|
91
|
-
this.field = field;
|
|
92
|
-
this.name = "MarvinValidationError";
|
|
93
|
-
Object.setPrototypeOf(this, _MarvinValidationError.prototype);
|
|
94
|
-
}
|
|
95
|
-
field;
|
|
96
|
-
};
|
|
97
|
-
var MarvinNotFoundError = class _MarvinNotFoundError extends MarvinError {
|
|
98
|
-
constructor(message, endpoint) {
|
|
99
|
-
super(message, "NOT_FOUND", 404);
|
|
100
|
-
this.endpoint = endpoint;
|
|
101
|
-
this.name = "MarvinNotFoundError";
|
|
102
|
-
Object.setPrototypeOf(this, _MarvinNotFoundError.prototype);
|
|
103
|
-
}
|
|
104
|
-
endpoint;
|
|
105
|
-
};
|
|
106
|
-
var MarvinNetworkError = class _MarvinNetworkError extends MarvinError {
|
|
107
|
-
constructor(message, originalError) {
|
|
108
|
-
super(message, "NETWORK_ERROR");
|
|
109
|
-
this.originalError = originalError;
|
|
110
|
-
this.name = "MarvinNetworkError";
|
|
111
|
-
Object.setPrototypeOf(this, _MarvinNetworkError.prototype);
|
|
112
|
-
}
|
|
113
|
-
originalError;
|
|
114
|
-
};
|
|
115
|
-
var MarvinServerError = class _MarvinServerError extends MarvinError {
|
|
116
|
-
constructor(message, statusCode, endpoint) {
|
|
117
|
-
super(message, "SERVER_ERROR", statusCode);
|
|
118
|
-
this.endpoint = endpoint;
|
|
119
|
-
this.name = "MarvinServerError";
|
|
120
|
-
Object.setPrototypeOf(this, _MarvinServerError.prototype);
|
|
121
|
-
}
|
|
122
|
-
endpoint;
|
|
123
|
-
};
|
|
124
77
|
|
|
125
78
|
// src/core/http/HttpClient.ts
|
|
126
79
|
var HttpClient = class {
|
|
@@ -129,47 +82,12 @@ var HttpClient = class {
|
|
|
129
82
|
defaultHeaders;
|
|
130
83
|
credentials;
|
|
131
84
|
timeout;
|
|
132
|
-
debug;
|
|
133
|
-
retryConfig;
|
|
134
|
-
logger;
|
|
135
85
|
constructor(config) {
|
|
136
86
|
this.baseUrl = config.baseUrl;
|
|
137
87
|
this.auth = config.auth;
|
|
138
88
|
this.defaultHeaders = config.defaultHeaders || {};
|
|
139
89
|
this.credentials = config.credentials || "same-origin";
|
|
140
|
-
|
|
141
|
-
this.timeout = Math.min(config.timeout || 3e4, MAX_TIMEOUT);
|
|
142
|
-
this.debug = config.debug ?? false;
|
|
143
|
-
this.logger = config.logger ?? console;
|
|
144
|
-
this.retryConfig = {
|
|
145
|
-
maxRetries: config.retry?.maxRetries ?? 3,
|
|
146
|
-
initialDelay: config.retry?.initialDelay ?? 1e3,
|
|
147
|
-
maxDelay: config.retry?.maxDelay ?? 1e4,
|
|
148
|
-
retryableStatuses: config.retry?.retryableStatuses ?? [408, 429, 500, 502, 503, 504]
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Validate path parameter to prevent path traversal attacks
|
|
153
|
-
*/
|
|
154
|
-
validatePathParam(param, paramName = "parameter") {
|
|
155
|
-
if (typeof param === "number") {
|
|
156
|
-
return String(param);
|
|
157
|
-
}
|
|
158
|
-
if (!param || typeof param !== "string") {
|
|
159
|
-
throw new MarvinValidationError(`Invalid ${paramName}: must be a non-empty string or number`);
|
|
160
|
-
}
|
|
161
|
-
if (param.includes("..") || param.includes("/") || param.includes("\\")) {
|
|
162
|
-
throw new MarvinValidationError(
|
|
163
|
-
`Invalid ${paramName}: cannot contain path traversal characters (.. / \\)`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
if (param.includes("\0")) {
|
|
167
|
-
throw new MarvinValidationError(`Invalid ${paramName}: cannot contain null bytes`);
|
|
168
|
-
}
|
|
169
|
-
if (param.length > 255) {
|
|
170
|
-
throw new MarvinValidationError(`Invalid ${paramName}: exceeds maximum length of 255 characters`);
|
|
171
|
-
}
|
|
172
|
-
return param;
|
|
90
|
+
this.timeout = config.timeout || 3e4;
|
|
173
91
|
}
|
|
174
92
|
/**
|
|
175
93
|
* Build full URL from endpoint
|
|
@@ -203,78 +121,14 @@ var HttpClient = class {
|
|
|
203
121
|
return filtered.length > 0 ? `?${filtered.join("&")}` : "";
|
|
204
122
|
}
|
|
205
123
|
/**
|
|
206
|
-
*
|
|
207
|
-
*/
|
|
208
|
-
sanitizeForLogging(data) {
|
|
209
|
-
if (!data || typeof data !== "object") {
|
|
210
|
-
return data;
|
|
211
|
-
}
|
|
212
|
-
const sensitiveFields = [
|
|
213
|
-
"token",
|
|
214
|
-
"access_token",
|
|
215
|
-
"refresh_token",
|
|
216
|
-
"password",
|
|
217
|
-
"secret",
|
|
218
|
-
"apiKey",
|
|
219
|
-
"api_key",
|
|
220
|
-
"bearer",
|
|
221
|
-
"authorization",
|
|
222
|
-
"csrf_token",
|
|
223
|
-
"session",
|
|
224
|
-
"cookie"
|
|
225
|
-
];
|
|
226
|
-
const sanitize = (obj) => {
|
|
227
|
-
const sanitized = {};
|
|
228
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
229
|
-
const keyLower = key.toLowerCase();
|
|
230
|
-
const isSensitive = sensitiveFields.some((field) => keyLower.includes(field));
|
|
231
|
-
if (isSensitive) {
|
|
232
|
-
sanitized[key] = "[REDACTED]";
|
|
233
|
-
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
234
|
-
sanitized[key] = sanitize(value);
|
|
235
|
-
} else if (Array.isArray(value)) {
|
|
236
|
-
sanitized[key] = value.map(
|
|
237
|
-
(item) => item && typeof item === "object" ? sanitize(item) : item
|
|
238
|
-
);
|
|
239
|
-
} else {
|
|
240
|
-
sanitized[key] = value;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return sanitized;
|
|
244
|
-
};
|
|
245
|
-
if (Array.isArray(data)) {
|
|
246
|
-
return data.map(
|
|
247
|
-
(item) => item && typeof item === "object" ? sanitize(item) : item
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
return sanitize(data);
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Calculate delay for retry with exponential backoff
|
|
254
|
-
*/
|
|
255
|
-
calculateRetryDelay(attempt) {
|
|
256
|
-
const delay = this.retryConfig.initialDelay * Math.pow(2, attempt);
|
|
257
|
-
return Math.min(delay, this.retryConfig.maxDelay);
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Sleep for specified milliseconds
|
|
124
|
+
* Perform HTTP request
|
|
261
125
|
*/
|
|
262
|
-
|
|
263
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Perform HTTP request with automatic retry on transient failures
|
|
267
|
-
*/
|
|
268
|
-
async request(method, endpoint, options, retryAttempt = 0) {
|
|
126
|
+
async request(method, endpoint, options) {
|
|
269
127
|
const url = this.buildUrl(endpoint) + (options?.params ? this.buildQueryString(options.params) : "");
|
|
270
128
|
const isFormData = options?.body instanceof FormData;
|
|
271
129
|
const headers = this.buildHeaders(options?.headers, isFormData);
|
|
272
130
|
const controller = new AbortController();
|
|
273
131
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
274
|
-
const startTime = Date.now();
|
|
275
|
-
if (this.debug) {
|
|
276
|
-
this.logger.log(`[Marvin] ${method} ${url}`);
|
|
277
|
-
}
|
|
278
132
|
try {
|
|
279
133
|
const response = await fetch(url, {
|
|
280
134
|
method,
|
|
@@ -284,54 +138,11 @@ var HttpClient = class {
|
|
|
284
138
|
signal: controller.signal
|
|
285
139
|
});
|
|
286
140
|
clearTimeout(timeoutId);
|
|
287
|
-
const duration = Date.now() - startTime;
|
|
288
|
-
if (this.debug) {
|
|
289
|
-
this.logger.log(
|
|
290
|
-
`[Marvin] ${response.status} ${response.statusText} (${duration}ms)`
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
141
|
if (response.status === 401 && this.auth.handleUnauthorized) {
|
|
294
142
|
await this.auth.handleUnauthorized();
|
|
295
143
|
}
|
|
296
144
|
if (!response.ok) {
|
|
297
145
|
const errorText = await response.text().catch(() => "");
|
|
298
|
-
if (this.debug) {
|
|
299
|
-
this.logger.error(
|
|
300
|
-
`[Marvin] Error response: ${response.status} ${response.statusText}`,
|
|
301
|
-
errorText ? `Body: ${errorText}` : ""
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
const isRetryable = this.retryConfig.retryableStatuses.includes(response.status);
|
|
305
|
-
const canRetry = retryAttempt < this.retryConfig.maxRetries;
|
|
306
|
-
if (isRetryable && canRetry) {
|
|
307
|
-
const delay = this.calculateRetryDelay(retryAttempt);
|
|
308
|
-
if (this.debug) {
|
|
309
|
-
this.logger.warn(
|
|
310
|
-
`[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (status: ${response.status})`
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
await this.sleep(delay);
|
|
314
|
-
return this.request(method, endpoint, options, retryAttempt + 1);
|
|
315
|
-
}
|
|
316
|
-
if (response.status === 404) {
|
|
317
|
-
throw new MarvinNotFoundError(
|
|
318
|
-
`Resource not found: ${endpoint}`,
|
|
319
|
-
endpoint
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
if (response.status === 401 || response.status === 403) {
|
|
323
|
-
throw new MarvinAuthError(
|
|
324
|
-
`Authentication failed: ${response.statusText}`,
|
|
325
|
-
response.status
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
if (response.status >= 500) {
|
|
329
|
-
throw new MarvinServerError(
|
|
330
|
-
`Server error: ${response.status} ${response.statusText}`,
|
|
331
|
-
response.status,
|
|
332
|
-
endpoint
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
146
|
throw MarvinApiError.fromResponse(
|
|
336
147
|
response.status,
|
|
337
148
|
response.statusText,
|
|
@@ -343,39 +154,21 @@ var HttpClient = class {
|
|
|
343
154
|
if (!contentType || !contentType.includes("application/json")) {
|
|
344
155
|
return void 0;
|
|
345
156
|
}
|
|
346
|
-
|
|
347
|
-
if (this.debug) {
|
|
348
|
-
const sanitized = this.sanitizeForLogging(data);
|
|
349
|
-
this.logger.log("[Marvin] Response:", JSON.stringify(sanitized, null, 2));
|
|
350
|
-
}
|
|
351
|
-
return data;
|
|
157
|
+
return await response.json();
|
|
352
158
|
} catch (error) {
|
|
353
159
|
clearTimeout(timeoutId);
|
|
354
|
-
if (error instanceof MarvinApiError
|
|
160
|
+
if (error instanceof MarvinApiError) {
|
|
355
161
|
throw error;
|
|
356
162
|
}
|
|
357
163
|
if (error instanceof Error) {
|
|
358
|
-
const canRetry = retryAttempt < this.retryConfig.maxRetries;
|
|
359
|
-
if (canRetry && error.name !== "AbortError") {
|
|
360
|
-
const delay = this.calculateRetryDelay(retryAttempt);
|
|
361
|
-
if (this.debug) {
|
|
362
|
-
this.logger.warn(
|
|
363
|
-
`[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (network error)`
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
await this.sleep(delay);
|
|
367
|
-
return this.request(method, endpoint, options, retryAttempt + 1);
|
|
368
|
-
}
|
|
369
164
|
if (error.name === "AbortError") {
|
|
370
|
-
throw new
|
|
165
|
+
throw new MarvinApiError(
|
|
371
166
|
`Request timeout after ${this.timeout}ms`,
|
|
372
|
-
|
|
167
|
+
408,
|
|
168
|
+
endpoint
|
|
373
169
|
);
|
|
374
170
|
}
|
|
375
|
-
throw new
|
|
376
|
-
`Network error: ${error.message}`,
|
|
377
|
-
error
|
|
378
|
-
);
|
|
171
|
+
throw new MarvinApiError(error.message, 0, endpoint);
|
|
379
172
|
}
|
|
380
173
|
throw error;
|
|
381
174
|
}
|
|
@@ -420,27 +213,10 @@ var SessionAuth = class {
|
|
|
420
213
|
}
|
|
421
214
|
sessionToken;
|
|
422
215
|
onUnauthorized;
|
|
423
|
-
csrfToken;
|
|
424
|
-
/**
|
|
425
|
-
* Set CSRF token for cross-site request forgery protection
|
|
426
|
-
* The token should be obtained from the server after login
|
|
427
|
-
*/
|
|
428
|
-
setCsrfToken(token) {
|
|
429
|
-
this.csrfToken = token;
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Get current CSRF token
|
|
433
|
-
*/
|
|
434
|
-
getCsrfToken() {
|
|
435
|
-
return this.csrfToken;
|
|
436
|
-
}
|
|
437
216
|
injectAuth(headers) {
|
|
438
217
|
if (this.sessionToken) {
|
|
439
218
|
headers.set("Authorization", `Bearer ${this.sessionToken}`);
|
|
440
219
|
}
|
|
441
|
-
if (this.csrfToken) {
|
|
442
|
-
headers.set("X-CSRF-Token", this.csrfToken);
|
|
443
|
-
}
|
|
444
220
|
}
|
|
445
221
|
async handleUnauthorized() {
|
|
446
222
|
if (this.onUnauthorized) {
|
|
@@ -448,9 +224,7 @@ var SessionAuth = class {
|
|
|
448
224
|
}
|
|
449
225
|
}
|
|
450
226
|
getDescription() {
|
|
451
|
-
|
|
452
|
-
const csrf = this.csrfToken ? " with CSRF protection" : "";
|
|
453
|
-
return auth + csrf;
|
|
227
|
+
return this.sessionToken ? "Session authentication (explicit token)" : "Session authentication (cookies)";
|
|
454
228
|
}
|
|
455
229
|
};
|
|
456
230
|
|
|
@@ -470,8 +244,7 @@ var EntriesModule = class {
|
|
|
470
244
|
* Get a single entry by ID
|
|
471
245
|
*/
|
|
472
246
|
async get(id) {
|
|
473
|
-
|
|
474
|
-
return this.http.get(`/api/platform/entries/${validId}`);
|
|
247
|
+
return this.http.get(`/api/platform/entries/${id}`);
|
|
475
248
|
}
|
|
476
249
|
/**
|
|
477
250
|
* Create a new entry
|
|
@@ -483,31 +256,26 @@ var EntriesModule = class {
|
|
|
483
256
|
* Update an entry
|
|
484
257
|
*/
|
|
485
258
|
async update(id, data) {
|
|
486
|
-
|
|
487
|
-
return this.http.patch(`/api/platform/entries/${validId}`, data);
|
|
259
|
+
return this.http.patch(`/api/platform/entries/${id}`, data);
|
|
488
260
|
}
|
|
489
261
|
/**
|
|
490
262
|
* Delete an entry
|
|
491
263
|
*/
|
|
492
264
|
async delete(id) {
|
|
493
|
-
|
|
494
|
-
return this.http.delete(`/api/platform/entries/${validId}`);
|
|
265
|
+
return this.http.delete(`/api/platform/entries/${id}`);
|
|
495
266
|
}
|
|
496
267
|
/**
|
|
497
268
|
* Get collections that this entry belongs to
|
|
498
269
|
*/
|
|
499
270
|
async listCollections(entryId) {
|
|
500
|
-
|
|
501
|
-
return this.http.get(`/api/platform/entries/${validId}/collections`);
|
|
271
|
+
return this.http.get(`/api/platform/entries/${entryId}/collections`);
|
|
502
272
|
}
|
|
503
273
|
/**
|
|
504
274
|
* Add entry to a collection
|
|
505
275
|
*/
|
|
506
276
|
async addToCollection(entryId, collectionId) {
|
|
507
|
-
const validEntryId = this.http.validatePathParam(entryId, "entry ID");
|
|
508
|
-
const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
|
|
509
277
|
return this.http.post(
|
|
510
|
-
`/api/platform/entries/${
|
|
278
|
+
`/api/platform/entries/${entryId}/collections/${collectionId}`,
|
|
511
279
|
{}
|
|
512
280
|
);
|
|
513
281
|
}
|
|
@@ -515,9 +283,7 @@ var EntriesModule = class {
|
|
|
515
283
|
* Remove entry from a collection
|
|
516
284
|
*/
|
|
517
285
|
async removeFromCollection(entryId, collectionId) {
|
|
518
|
-
|
|
519
|
-
const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
|
|
520
|
-
return this.http.delete(`/api/platform/entries/${validEntryId}/collections/${validCollectionId}`);
|
|
286
|
+
return this.http.delete(`/api/platform/entries/${entryId}/collections/${collectionId}`);
|
|
521
287
|
}
|
|
522
288
|
};
|
|
523
289
|
|
|
@@ -537,8 +303,7 @@ var CollectionsModule = class {
|
|
|
537
303
|
* Get a single collection by ID
|
|
538
304
|
*/
|
|
539
305
|
async get(id) {
|
|
540
|
-
|
|
541
|
-
return this.http.get(`/api/platform/collections/${validId}`);
|
|
306
|
+
return this.http.get(`/api/platform/collections/${id}`);
|
|
542
307
|
}
|
|
543
308
|
/**
|
|
544
309
|
* Create a new collection
|
|
@@ -550,29 +315,25 @@ var CollectionsModule = class {
|
|
|
550
315
|
* Update a collection
|
|
551
316
|
*/
|
|
552
317
|
async update(id, data) {
|
|
553
|
-
|
|
554
|
-
return this.http.patch(`/api/platform/collections/${validId}`, data);
|
|
318
|
+
return this.http.patch(`/api/platform/collections/${id}`, data);
|
|
555
319
|
}
|
|
556
320
|
/**
|
|
557
321
|
* Delete a collection
|
|
558
322
|
*/
|
|
559
323
|
async delete(id) {
|
|
560
|
-
|
|
561
|
-
return this.http.delete(`/api/platform/collections/${validId}`);
|
|
324
|
+
return this.http.delete(`/api/platform/collections/${id}`);
|
|
562
325
|
}
|
|
563
326
|
/**
|
|
564
327
|
* Get entries in a collection
|
|
565
328
|
*/
|
|
566
329
|
async getEntries(id) {
|
|
567
|
-
|
|
568
|
-
return this.http.get(`/api/platform/collections/${validId}/entries`);
|
|
330
|
+
return this.http.get(`/api/platform/collections/${id}/entries`);
|
|
569
331
|
}
|
|
570
332
|
/**
|
|
571
333
|
* Reorder entries in a collection
|
|
572
334
|
*/
|
|
573
335
|
async reorderEntries(id, entries) {
|
|
574
|
-
|
|
575
|
-
return this.http.patch(`/api/platform/collections/${validId}/entries/order`, { entries });
|
|
336
|
+
return this.http.patch(`/api/platform/collections/${id}/entries/order`, { entries });
|
|
576
337
|
}
|
|
577
338
|
};
|
|
578
339
|
|
|
@@ -592,8 +353,7 @@ var ResourcesModule = class {
|
|
|
592
353
|
* Get a single resource by ID
|
|
593
354
|
*/
|
|
594
355
|
async get(id) {
|
|
595
|
-
|
|
596
|
-
return this.http.get(`/api/platform/resources/${validId}`);
|
|
356
|
+
return this.http.get(`/api/platform/resources/${id}`);
|
|
597
357
|
}
|
|
598
358
|
/**
|
|
599
359
|
* Create a new resource
|
|
@@ -605,8 +365,7 @@ var ResourcesModule = class {
|
|
|
605
365
|
* Update a resource
|
|
606
366
|
*/
|
|
607
367
|
async update(id, data) {
|
|
608
|
-
|
|
609
|
-
return this.http.patch(`/api/platform/resources/${validId}`, data);
|
|
368
|
+
return this.http.patch(`/api/platform/resources/${id}`, data);
|
|
610
369
|
}
|
|
611
370
|
/**
|
|
612
371
|
* Delete a resource
|
|
@@ -628,47 +387,6 @@ var AssetsModule = class {
|
|
|
628
387
|
this.http = http;
|
|
629
388
|
}
|
|
630
389
|
http;
|
|
631
|
-
/**
|
|
632
|
-
* Validate file before upload
|
|
633
|
-
*/
|
|
634
|
-
validateFile(file, options) {
|
|
635
|
-
const maxSize = options?.maxFileSize || 100 * 1024 * 1024;
|
|
636
|
-
const allowedTypes = options?.allowedMimeTypes;
|
|
637
|
-
if (file.size === 0) {
|
|
638
|
-
throw new MarvinValidationError("File is empty");
|
|
639
|
-
}
|
|
640
|
-
if (file.size > maxSize) {
|
|
641
|
-
const maxMB = Math.round(maxSize / (1024 * 1024));
|
|
642
|
-
const fileMB = Math.round(file.size / (1024 * 1024));
|
|
643
|
-
throw new MarvinValidationError(
|
|
644
|
-
`File size (${fileMB}MB) exceeds maximum allowed size of ${maxMB}MB`
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
if (allowedTypes && allowedTypes.length > 0) {
|
|
648
|
-
if (file instanceof File && file.type) {
|
|
649
|
-
const isAllowed = allowedTypes.some((allowed) => {
|
|
650
|
-
if (allowed.endsWith("/*")) {
|
|
651
|
-
const prefix = allowed.slice(0, -2);
|
|
652
|
-
return file.type.startsWith(prefix);
|
|
653
|
-
}
|
|
654
|
-
return file.type === allowed;
|
|
655
|
-
});
|
|
656
|
-
if (!isAllowed) {
|
|
657
|
-
throw new MarvinValidationError(
|
|
658
|
-
`File type "${file.type}" is not allowed. Allowed types: ${allowedTypes.join(", ")}`
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
if (file instanceof File) {
|
|
664
|
-
if (!file.name || file.name.trim().length === 0) {
|
|
665
|
-
throw new MarvinValidationError("File must have a valid name");
|
|
666
|
-
}
|
|
667
|
-
if (file.name.includes("..") || file.name.includes("/") || file.name.includes("\\")) {
|
|
668
|
-
throw new MarvinValidationError("Invalid filename: cannot contain path separators");
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
390
|
/**
|
|
673
391
|
* List all assets
|
|
674
392
|
*/
|
|
@@ -679,31 +397,16 @@ var AssetsModule = class {
|
|
|
679
397
|
* Get a single asset by ID
|
|
680
398
|
*/
|
|
681
399
|
async get(id) {
|
|
682
|
-
|
|
683
|
-
return this.http.get(`/api/platform/assets/${validId}`);
|
|
400
|
+
return this.http.get(`/api/platform/assets/${id}`);
|
|
684
401
|
}
|
|
685
402
|
/**
|
|
686
403
|
* Upload a new asset file
|
|
687
404
|
*
|
|
688
405
|
* @param file - The file to upload (File or Blob)
|
|
689
406
|
* @param metadata - Asset metadata (slug, name, altText, description, metadata)
|
|
690
|
-
* @param options - Upload options (file size limits, allowed types)
|
|
691
407
|
* @returns The created asset with extracted metadata
|
|
692
|
-
*
|
|
693
|
-
* @example
|
|
694
|
-
* ```typescript
|
|
695
|
-
* // Upload with default limits (100MB, all types)
|
|
696
|
-
* const asset = await assets.upload(file, { slug: 'my-image', name: 'My Image' });
|
|
697
|
-
*
|
|
698
|
-
* // Upload with restrictions
|
|
699
|
-
* const asset = await assets.upload(file,
|
|
700
|
-
* { slug: 'avatar', name: 'Avatar' },
|
|
701
|
-
* { maxFileSize: 5 * 1024 * 1024, allowedMimeTypes: ['image/*'] }
|
|
702
|
-
* );
|
|
703
|
-
* ```
|
|
704
408
|
*/
|
|
705
|
-
async upload(file, metadata
|
|
706
|
-
this.validateFile(file, options);
|
|
409
|
+
async upload(file, metadata) {
|
|
707
410
|
const formData = new FormData();
|
|
708
411
|
formData.append("file", file);
|
|
709
412
|
formData.append("slug", metadata.slug);
|
|
@@ -723,24 +426,13 @@ var AssetsModule = class {
|
|
|
723
426
|
* Update an asset (editable fields only: slug, name, altText, description, metadata)
|
|
724
427
|
*/
|
|
725
428
|
async update(id, data) {
|
|
726
|
-
|
|
727
|
-
return this.http.patch(`/api/platform/assets/${validId}`, data);
|
|
429
|
+
return this.http.patch(`/api/platform/assets/${id}`, data);
|
|
728
430
|
}
|
|
729
431
|
/**
|
|
730
432
|
* Delete an asset (removes file from storage and database)
|
|
731
433
|
*/
|
|
732
434
|
async delete(id) {
|
|
733
|
-
|
|
734
|
-
return this.http.delete(`/api/platform/assets/${validId}`);
|
|
735
|
-
}
|
|
736
|
-
/**
|
|
737
|
-
* Get download URL for an asset file
|
|
738
|
-
* Use this to create download links or fetch the file directly
|
|
739
|
-
*/
|
|
740
|
-
getDownloadUrl(id, baseUrl) {
|
|
741
|
-
const validId = this.http.validatePathParam(id, "asset ID");
|
|
742
|
-
const base = baseUrl || (typeof window !== "undefined" && window.location ? window.location.origin : "");
|
|
743
|
-
return `${base}/api/platform/assets/${validId}/file`;
|
|
435
|
+
return this.http.delete(`/api/platform/assets/${id}`);
|
|
744
436
|
}
|
|
745
437
|
};
|
|
746
438
|
|
|
@@ -760,8 +452,7 @@ var APIClientsModule = class {
|
|
|
760
452
|
* Get a single API client by ID
|
|
761
453
|
*/
|
|
762
454
|
async get(id) {
|
|
763
|
-
|
|
764
|
-
return this.http.get(`/api/platform/api-clients/${validId}`);
|
|
455
|
+
return this.http.get(`/api/platform/api-clients/${id}`);
|
|
765
456
|
}
|
|
766
457
|
/**
|
|
767
458
|
* Create a new API client (returns client with token)
|
|
@@ -773,8 +464,7 @@ var APIClientsModule = class {
|
|
|
773
464
|
* Update an API client
|
|
774
465
|
*/
|
|
775
466
|
async update(id, data) {
|
|
776
|
-
|
|
777
|
-
return this.http.patch(`/api/platform/api-clients/${validId}`, data);
|
|
467
|
+
return this.http.patch(`/api/platform/api-clients/${id}`, data);
|
|
778
468
|
}
|
|
779
469
|
/**
|
|
780
470
|
* Delete an API client
|
|
@@ -985,23 +675,11 @@ var InvitesModule = class {
|
|
|
985
675
|
}
|
|
986
676
|
/**
|
|
987
677
|
* Generate invitation URL from a token
|
|
988
|
-
*
|
|
989
|
-
* @param token - Invitation token
|
|
990
|
-
* @param baseUrl - Base URL for the frontend (required in browser environments)
|
|
991
|
-
* @returns Full invitation URL
|
|
992
|
-
*
|
|
993
|
-
* @example
|
|
994
|
-
* ```typescript
|
|
995
|
-
* // Node.js (can use env vars)
|
|
996
|
-
* const url = invites.getInvitationUrl(token);
|
|
997
|
-
*
|
|
998
|
-
* // Browser (must provide baseUrl)
|
|
999
|
-
* const url = invites.getInvitationUrl(token, 'https://app.example.com');
|
|
1000
|
-
* ```
|
|
678
|
+
* Uses MARVIN_FRONTEND_URL for the base URL, falls back to MARVIN_API_URL with common port mappings
|
|
1001
679
|
*/
|
|
1002
680
|
getInvitationUrl(token, baseUrl) {
|
|
1003
681
|
let base = baseUrl;
|
|
1004
|
-
if (!base
|
|
682
|
+
if (!base) {
|
|
1005
683
|
base = process.env.MARVIN_FRONTEND_URL;
|
|
1006
684
|
if (!base && process.env.MARVIN_API_URL) {
|
|
1007
685
|
const apiUrl = process.env.MARVIN_API_URL;
|
|
@@ -1013,17 +691,11 @@ var InvitesModule = class {
|
|
|
1013
691
|
base = apiUrl;
|
|
1014
692
|
}
|
|
1015
693
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
if (typeof window !== "undefined" && window.location) {
|
|
1019
|
-
base = window.location.origin;
|
|
1020
|
-
} else {
|
|
1021
|
-
throw new Error(
|
|
1022
|
-
'baseUrl is required when not in Node.js environment. Pass the frontend URL as second argument: getInvitationUrl(token, "https://app.example.com")'
|
|
1023
|
-
);
|
|
694
|
+
if (!base) {
|
|
695
|
+
base = "http://localhost:4321";
|
|
1024
696
|
}
|
|
1025
697
|
}
|
|
1026
|
-
return `${base}/register?token=${
|
|
698
|
+
return `${base}/register?token=${token}`;
|
|
1027
699
|
}
|
|
1028
700
|
};
|
|
1029
701
|
|
|
@@ -1077,57 +749,6 @@ var WebhooksModule = class {
|
|
|
1077
749
|
this.http = http;
|
|
1078
750
|
}
|
|
1079
751
|
http;
|
|
1080
|
-
/**
|
|
1081
|
-
* Validate webhook URL to prevent SSRF attacks
|
|
1082
|
-
*/
|
|
1083
|
-
validateWebhookUrl(url) {
|
|
1084
|
-
if (!url || typeof url !== "string") {
|
|
1085
|
-
throw new MarvinValidationError("Webhook URL is required and must be a string");
|
|
1086
|
-
}
|
|
1087
|
-
let parsed;
|
|
1088
|
-
try {
|
|
1089
|
-
parsed = new URL(url);
|
|
1090
|
-
} catch {
|
|
1091
|
-
throw new MarvinValidationError("Invalid webhook URL format. Expected: https://example.com/webhook");
|
|
1092
|
-
}
|
|
1093
|
-
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
1094
|
-
throw new MarvinValidationError(
|
|
1095
|
-
`Webhook URL must use HTTP or HTTPS protocol. Got: ${parsed.protocol}`
|
|
1096
|
-
);
|
|
1097
|
-
}
|
|
1098
|
-
const hostname = parsed.hostname.toLowerCase();
|
|
1099
|
-
const blockedHosts = [
|
|
1100
|
-
"localhost",
|
|
1101
|
-
"127.0.0.1",
|
|
1102
|
-
"0.0.0.0",
|
|
1103
|
-
"::1",
|
|
1104
|
-
"169.254.169.254",
|
|
1105
|
-
// AWS metadata service
|
|
1106
|
-
"[::]"
|
|
1107
|
-
];
|
|
1108
|
-
if (blockedHosts.some((blocked) => hostname === blocked || hostname.includes(blocked))) {
|
|
1109
|
-
throw new MarvinValidationError(
|
|
1110
|
-
"Webhook URL cannot target localhost or internal network addresses"
|
|
1111
|
-
);
|
|
1112
|
-
}
|
|
1113
|
-
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
1114
|
-
const match = hostname.match(ipv4Regex);
|
|
1115
|
-
if (match) {
|
|
1116
|
-
const octets = match.slice(1).map(Number);
|
|
1117
|
-
if (octets[0] === 10) {
|
|
1118
|
-
throw new MarvinValidationError("Webhook URL cannot target private IP range (10.x.x.x)");
|
|
1119
|
-
}
|
|
1120
|
-
if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
|
|
1121
|
-
throw new MarvinValidationError("Webhook URL cannot target private IP range (172.16-31.x.x)");
|
|
1122
|
-
}
|
|
1123
|
-
if (octets[0] === 192 && octets[1] === 168) {
|
|
1124
|
-
throw new MarvinValidationError("Webhook URL cannot target private IP range (192.168.x.x)");
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
if (url.length > 2048) {
|
|
1128
|
-
throw new MarvinValidationError("Webhook URL exceeds maximum length of 2048 characters");
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
752
|
/**
|
|
1132
753
|
* List all webhooks for the current workspace
|
|
1133
754
|
*/
|
|
@@ -1139,43 +760,31 @@ var WebhooksModule = class {
|
|
|
1139
760
|
* Get a webhook by ID
|
|
1140
761
|
*/
|
|
1141
762
|
async get(id) {
|
|
1142
|
-
|
|
1143
|
-
return this.http.get(`/api/groups/webhooks/${validId}`);
|
|
763
|
+
return this.http.get(`/api/groups/webhooks/${id}`);
|
|
1144
764
|
}
|
|
1145
765
|
/**
|
|
1146
766
|
* Create a new webhook
|
|
1147
|
-
* Validates URL to prevent SSRF attacks
|
|
1148
767
|
*/
|
|
1149
768
|
async create(data) {
|
|
1150
|
-
if (data.url) {
|
|
1151
|
-
this.validateWebhookUrl(data.url);
|
|
1152
|
-
}
|
|
1153
769
|
return this.http.post("/api/groups/webhooks", data);
|
|
1154
770
|
}
|
|
1155
771
|
/**
|
|
1156
772
|
* Update a webhook
|
|
1157
|
-
* Validates URL if provided to prevent SSRF attacks
|
|
1158
773
|
*/
|
|
1159
774
|
async update(id, data) {
|
|
1160
|
-
|
|
1161
|
-
if (data.url) {
|
|
1162
|
-
this.validateWebhookUrl(data.url);
|
|
1163
|
-
}
|
|
1164
|
-
return this.http.put(`/api/groups/webhooks/${validId}`, data);
|
|
775
|
+
return this.http.put(`/api/groups/webhooks/${id}`, data);
|
|
1165
776
|
}
|
|
1166
777
|
/**
|
|
1167
778
|
* Delete a webhook
|
|
1168
779
|
*/
|
|
1169
780
|
async delete(id) {
|
|
1170
|
-
|
|
1171
|
-
await this.http.delete(`/api/groups/webhooks/${validId}`);
|
|
781
|
+
await this.http.delete(`/api/groups/webhooks/${id}`);
|
|
1172
782
|
}
|
|
1173
783
|
/**
|
|
1174
784
|
* Test a webhook
|
|
1175
785
|
*/
|
|
1176
786
|
async test(id) {
|
|
1177
|
-
|
|
1178
|
-
return this.http.post(`/api/groups/webhooks/${validId}/test`, {});
|
|
787
|
+
return this.http.post(`/api/groups/webhooks/${id}/test`, {});
|
|
1179
788
|
}
|
|
1180
789
|
/**
|
|
1181
790
|
* Rerun failed webhooks
|
|
@@ -1267,7 +876,6 @@ var AdminSystemModule = class {
|
|
|
1267
876
|
}
|
|
1268
877
|
/**
|
|
1269
878
|
* Update email settings
|
|
1270
|
-
* Note: Password field is write-only and will not be returned in response
|
|
1271
879
|
*/
|
|
1272
880
|
async updateEmailSettings(data) {
|
|
1273
881
|
return this.http.put("/api/admin/email", data);
|
|
@@ -1543,234 +1151,7 @@ var ThemeModule = class {
|
|
|
1543
1151
|
}
|
|
1544
1152
|
};
|
|
1545
1153
|
|
|
1546
|
-
// src/platform/emailTemplates.ts
|
|
1547
|
-
var EmailTemplatesClient = class {
|
|
1548
|
-
constructor(http) {
|
|
1549
|
-
this.http = http;
|
|
1550
|
-
}
|
|
1551
|
-
http;
|
|
1552
|
-
/**
|
|
1553
|
-
* List all email templates for the workspace
|
|
1554
|
-
* @param groupId - Workspace ID
|
|
1555
|
-
*/
|
|
1556
|
-
async list(groupId) {
|
|
1557
|
-
return this.http.get(`/api/platform/workspaces/${groupId}/email-templates`);
|
|
1558
|
-
}
|
|
1559
|
-
/**
|
|
1560
|
-
* Get a specific email template
|
|
1561
|
-
* @param groupId - Workspace ID
|
|
1562
|
-
* @param templateId - Template ID
|
|
1563
|
-
*/
|
|
1564
|
-
async get(groupId, templateId) {
|
|
1565
|
-
return this.http.get(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
|
|
1566
|
-
}
|
|
1567
|
-
/**
|
|
1568
|
-
* Create a new email template
|
|
1569
|
-
* @param groupId - Workspace ID
|
|
1570
|
-
* @param data - Template data
|
|
1571
|
-
*/
|
|
1572
|
-
async create(groupId, data) {
|
|
1573
|
-
return this.http.post(`/api/platform/workspaces/${groupId}/email-templates`, data);
|
|
1574
|
-
}
|
|
1575
|
-
/**
|
|
1576
|
-
* Update an email template
|
|
1577
|
-
* @param groupId - Workspace ID
|
|
1578
|
-
* @param templateId - Template ID
|
|
1579
|
-
* @param data - Updated template data
|
|
1580
|
-
*/
|
|
1581
|
-
async update(groupId, templateId, data) {
|
|
1582
|
-
return this.http.patch(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`, data);
|
|
1583
|
-
}
|
|
1584
|
-
/**
|
|
1585
|
-
* Delete an email template
|
|
1586
|
-
* @param groupId - Workspace ID
|
|
1587
|
-
* @param templateId - Template ID
|
|
1588
|
-
*/
|
|
1589
|
-
async delete(groupId, templateId) {
|
|
1590
|
-
await this.http.delete(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
|
|
1591
|
-
}
|
|
1592
|
-
/**
|
|
1593
|
-
* Send a test email using a template
|
|
1594
|
-
* @param groupId - Workspace ID
|
|
1595
|
-
* @param templateId - Template ID
|
|
1596
|
-
* @param recipientEmail - Test recipient email address
|
|
1597
|
-
*/
|
|
1598
|
-
async sendTest(groupId, templateId, recipientEmail) {
|
|
1599
|
-
return this.http.post(`/api/platform/workspaces/${groupId}/email-templates/${templateId}/test`, {
|
|
1600
|
-
recipient_email: recipientEmail
|
|
1601
|
-
});
|
|
1602
|
-
}
|
|
1603
|
-
};
|
|
1604
|
-
|
|
1605
|
-
// src/platform/scheduledTasks.ts
|
|
1606
|
-
var ScheduledTasksModule = class {
|
|
1607
|
-
constructor(http) {
|
|
1608
|
-
this.http = http;
|
|
1609
|
-
}
|
|
1610
|
-
http;
|
|
1611
|
-
/**
|
|
1612
|
-
* List all scheduled tasks for the current workspace
|
|
1613
|
-
*/
|
|
1614
|
-
async list() {
|
|
1615
|
-
return this.http.get("/api/platform/scheduled-tasks");
|
|
1616
|
-
}
|
|
1617
|
-
/**
|
|
1618
|
-
* Get a scheduled task by ID or slug
|
|
1619
|
-
*/
|
|
1620
|
-
async get(idOrSlug) {
|
|
1621
|
-
const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
|
|
1622
|
-
return this.http.get(`/api/platform/scheduled-tasks/${validIdOrSlug}`);
|
|
1623
|
-
}
|
|
1624
|
-
/**
|
|
1625
|
-
* Create a new scheduled task
|
|
1626
|
-
*/
|
|
1627
|
-
async create(data) {
|
|
1628
|
-
return this.http.post("/api/platform/scheduled-tasks", data);
|
|
1629
|
-
}
|
|
1630
|
-
/**
|
|
1631
|
-
* Update a scheduled task
|
|
1632
|
-
*/
|
|
1633
|
-
async update(idOrSlug, data) {
|
|
1634
|
-
const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
|
|
1635
|
-
return this.http.patch(`/api/platform/scheduled-tasks/${validIdOrSlug}`, data);
|
|
1636
|
-
}
|
|
1637
|
-
/**
|
|
1638
|
-
* Delete a scheduled task
|
|
1639
|
-
*/
|
|
1640
|
-
async delete(idOrSlug) {
|
|
1641
|
-
await this.http.delete(`/api/platform/scheduled-tasks/${idOrSlug}`);
|
|
1642
|
-
}
|
|
1643
|
-
/**
|
|
1644
|
-
* Manually trigger task execution (bypass schedule)
|
|
1645
|
-
*/
|
|
1646
|
-
async execute(idOrSlug) {
|
|
1647
|
-
await this.http.post(`/api/platform/scheduled-tasks/${idOrSlug}/execute`, {});
|
|
1648
|
-
}
|
|
1649
|
-
/**
|
|
1650
|
-
* Get task execution history
|
|
1651
|
-
*/
|
|
1652
|
-
async history(idOrSlug, options) {
|
|
1653
|
-
const params = new URLSearchParams();
|
|
1654
|
-
if (options?.limit) {
|
|
1655
|
-
params.set("limit", options.limit.toString());
|
|
1656
|
-
}
|
|
1657
|
-
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1658
|
-
return this.http.get(
|
|
1659
|
-
`/api/platform/scheduled-tasks/${idOrSlug}/history${query}`
|
|
1660
|
-
);
|
|
1661
|
-
}
|
|
1662
|
-
/**
|
|
1663
|
-
* Get available task types
|
|
1664
|
-
*/
|
|
1665
|
-
async taskTypes(options) {
|
|
1666
|
-
const params = new URLSearchParams();
|
|
1667
|
-
if (options?.detailed) {
|
|
1668
|
-
params.set("detailed", "true");
|
|
1669
|
-
}
|
|
1670
|
-
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1671
|
-
return this.http.get(`/api/platform/scheduled-tasks/task-types${query}`);
|
|
1672
|
-
}
|
|
1673
|
-
};
|
|
1674
|
-
|
|
1675
|
-
// src/platform/forms.ts
|
|
1676
|
-
var FormsModule = class {
|
|
1677
|
-
constructor(http) {
|
|
1678
|
-
this.http = http;
|
|
1679
|
-
}
|
|
1680
|
-
http;
|
|
1681
|
-
/**
|
|
1682
|
-
* Validate form submission data
|
|
1683
|
-
* - Prevents XSS by checking for script tags
|
|
1684
|
-
* - Validates data is a proper object
|
|
1685
|
-
* - Checks for reasonable size limits
|
|
1686
|
-
*/
|
|
1687
|
-
validateFormData(data) {
|
|
1688
|
-
if (!data || typeof data !== "object") {
|
|
1689
|
-
throw new MarvinValidationError("Form data must be a valid object");
|
|
1690
|
-
}
|
|
1691
|
-
const jsonString = JSON.stringify(data);
|
|
1692
|
-
const sizeInBytes = new Blob([jsonString]).size;
|
|
1693
|
-
if (sizeInBytes > 1024 * 1024) {
|
|
1694
|
-
throw new MarvinValidationError("Form data exceeds maximum size of 1MB");
|
|
1695
|
-
}
|
|
1696
|
-
const checkForScripts = (value) => {
|
|
1697
|
-
if (typeof value === "string") {
|
|
1698
|
-
if (/<script[\s\S]*?>[\s\S]*?<\/script>/gi.test(value)) {
|
|
1699
|
-
throw new MarvinValidationError("Form data contains potentially malicious content");
|
|
1700
|
-
}
|
|
1701
|
-
} else if (Array.isArray(value)) {
|
|
1702
|
-
value.forEach(checkForScripts);
|
|
1703
|
-
} else if (value && typeof value === "object") {
|
|
1704
|
-
Object.values(value).forEach(checkForScripts);
|
|
1705
|
-
}
|
|
1706
|
-
};
|
|
1707
|
-
Object.values(data).forEach(checkForScripts);
|
|
1708
|
-
}
|
|
1709
|
-
/**
|
|
1710
|
-
* List all forms
|
|
1711
|
-
*/
|
|
1712
|
-
async list() {
|
|
1713
|
-
return this.http.get("/api/platform/forms");
|
|
1714
|
-
}
|
|
1715
|
-
/**
|
|
1716
|
-
* Get a single form by ID
|
|
1717
|
-
*/
|
|
1718
|
-
async get(id) {
|
|
1719
|
-
const validId = this.http.validatePathParam(id, "id");
|
|
1720
|
-
return this.http.get(`/api/platform/forms/${validId}`);
|
|
1721
|
-
}
|
|
1722
|
-
/**
|
|
1723
|
-
* Create a new form
|
|
1724
|
-
*/
|
|
1725
|
-
async create(data) {
|
|
1726
|
-
return this.http.post("/api/platform/forms", data);
|
|
1727
|
-
}
|
|
1728
|
-
/**
|
|
1729
|
-
* Update a form
|
|
1730
|
-
*/
|
|
1731
|
-
async update(id, data) {
|
|
1732
|
-
const validId = this.http.validatePathParam(id, "id");
|
|
1733
|
-
return this.http.patch(`/api/platform/forms/${validId}`, data);
|
|
1734
|
-
}
|
|
1735
|
-
/**
|
|
1736
|
-
* Delete a form
|
|
1737
|
-
*/
|
|
1738
|
-
async delete(id) {
|
|
1739
|
-
return this.http.delete(`/api/platform/forms/${id}`);
|
|
1740
|
-
}
|
|
1741
|
-
/**
|
|
1742
|
-
* Get submissions for a form
|
|
1743
|
-
*/
|
|
1744
|
-
async getSubmissions(formId) {
|
|
1745
|
-
return this.http.get(`/api/platform/forms/${formId}/submissions`);
|
|
1746
|
-
}
|
|
1747
|
-
/**
|
|
1748
|
-
* Get a published form by slug (Publishing API - public access)
|
|
1749
|
-
*/
|
|
1750
|
-
async getPublishedForm(slug) {
|
|
1751
|
-
return this.http.get(`/api/publish/forms/${slug}`);
|
|
1752
|
-
}
|
|
1753
|
-
/**
|
|
1754
|
-
* Submit data to a published form (Publishing API - public access)
|
|
1755
|
-
* Validates data before submission to prevent XSS and malicious content
|
|
1756
|
-
*/
|
|
1757
|
-
async submitForm(slug, data) {
|
|
1758
|
-
const validSlug = this.http.validatePathParam(slug, "form slug");
|
|
1759
|
-
this.validateFormData(data);
|
|
1760
|
-
return this.http.post(`/api/publish/forms/${validSlug}/submit`, {
|
|
1761
|
-
dataJson: data
|
|
1762
|
-
});
|
|
1763
|
-
}
|
|
1764
|
-
};
|
|
1765
|
-
|
|
1766
1154
|
// src/platform/client.ts
|
|
1767
|
-
function createPlatformConfigFromEnv(overrides) {
|
|
1768
|
-
return {
|
|
1769
|
-
apiUrl: overrides?.apiUrl || process.env.MARVIN_API_URL || "http://localhost:8080",
|
|
1770
|
-
userToken: overrides?.userToken || process.env.MARVIN_USER_TOKEN,
|
|
1771
|
-
credentials: overrides?.credentials || "include"
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
1155
|
var PlatformClient = class extends HttpClient {
|
|
1775
1156
|
entries;
|
|
1776
1157
|
collections;
|
|
@@ -1788,16 +1169,13 @@ var PlatformClient = class extends HttpClient {
|
|
|
1788
1169
|
eventLog;
|
|
1789
1170
|
session;
|
|
1790
1171
|
theme;
|
|
1791
|
-
emailTemplates;
|
|
1792
|
-
scheduledTasks;
|
|
1793
|
-
forms;
|
|
1794
1172
|
// Admin modules
|
|
1795
1173
|
adminUsers;
|
|
1796
1174
|
adminSystem;
|
|
1797
1175
|
adminMaintenance;
|
|
1798
1176
|
constructor(config) {
|
|
1799
1177
|
const httpConfig = {
|
|
1800
|
-
baseUrl: config.apiUrl
|
|
1178
|
+
baseUrl: config.apiUrl,
|
|
1801
1179
|
auth: new SessionAuth(config.userToken),
|
|
1802
1180
|
credentials: config.credentials || "include"
|
|
1803
1181
|
};
|
|
@@ -1818,17 +1196,13 @@ var PlatformClient = class extends HttpClient {
|
|
|
1818
1196
|
this.eventLog = new EventLogModule(this);
|
|
1819
1197
|
this.session = new AuthModule(this);
|
|
1820
1198
|
this.theme = new ThemeModule(this);
|
|
1821
|
-
this.emailTemplates = new EmailTemplatesClient(this);
|
|
1822
|
-
this.scheduledTasks = new ScheduledTasksModule(this);
|
|
1823
|
-
this.forms = new FormsModule(this);
|
|
1824
1199
|
this.adminUsers = new AdminUsersModule(this);
|
|
1825
1200
|
this.adminSystem = new AdminSystemModule(this);
|
|
1826
1201
|
this.adminMaintenance = new AdminMaintenanceModule(this);
|
|
1827
1202
|
}
|
|
1828
1203
|
};
|
|
1829
1204
|
function createPlatformClient(config) {
|
|
1830
|
-
|
|
1831
|
-
return new PlatformClient(finalConfig);
|
|
1205
|
+
return new PlatformClient(config);
|
|
1832
1206
|
}
|
|
1833
1207
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1834
1208
|
0 && (module.exports = {
|
|
@@ -1839,22 +1213,18 @@ function createPlatformClient(config) {
|
|
|
1839
1213
|
AssetsModule,
|
|
1840
1214
|
AuthModule,
|
|
1841
1215
|
CollectionsModule,
|
|
1842
|
-
EmailTemplatesClient,
|
|
1843
1216
|
EntriesModule,
|
|
1844
1217
|
EntryTypesModule,
|
|
1845
1218
|
EventLogModule,
|
|
1846
1219
|
EventsModule,
|
|
1847
|
-
FormsModule,
|
|
1848
1220
|
InvitesModule,
|
|
1849
1221
|
NotificationsModule,
|
|
1850
1222
|
PlatformClient,
|
|
1851
1223
|
ResourcesModule,
|
|
1852
|
-
ScheduledTasksModule,
|
|
1853
1224
|
ThemeModule,
|
|
1854
1225
|
UserModule,
|
|
1855
1226
|
WebhooksModule,
|
|
1856
1227
|
WorkspaceMembersModule,
|
|
1857
1228
|
WorkspacesModule,
|
|
1858
|
-
createPlatformClient
|
|
1859
|
-
createPlatformConfigFromEnv
|
|
1229
|
+
createPlatformClient
|
|
1860
1230
|
});
|