@structcms/api 0.1.0 → 0.1.1
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 +2 -2
- package/dist/index.cjs +266 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +241 -12
- package/dist/index.d.ts +241 -12
- package/dist/index.js +250 -20
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +147 -33
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +3 -2
- package/dist/next/index.d.ts +3 -2
- package/dist/next/index.js +147 -33
- package/dist/next/index.js.map +1 -1
- package/dist/supabase/index.cjs +37 -1
- package/dist/supabase/index.cjs.map +1 -1
- package/dist/supabase/index.d.cts +1 -1
- package/dist/supabase/index.d.ts +1 -1
- package/dist/supabase/index.js +37 -1
- package/dist/supabase/index.js.map +1 -1
- package/dist/{types-Zi0Iyow1.d.cts → types-Cdui_Ets.d.cts} +21 -2
- package/dist/{types-Zi0Iyow1.d.ts → types-Cdui_Ets.d.ts} +21 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Storage, domain API, delivery API, and content export for StructCMS. Provides Supabase-agnostic adapter interfaces, handler functions for content CRUD, media management, and JSON export.
|
|
4
4
|
|
|
5
|
-
For architectural context, see [ARCHITECTURE.md](../../ARCHITECTURE.md) (Layer 3: Storage, Layer 4: Domain API, Layer 5: Delivery API).
|
|
5
|
+
For architectural context, see [ARCHITECTURE.md](../../docs/ARCHITECTURE.md) (Layer 3: Storage, Layer 4: Domain API, Layer 5: Delivery API).
|
|
6
6
|
|
|
7
7
|
**[← Back to main README](../../README.md)**
|
|
8
8
|
|
|
@@ -164,7 +164,7 @@ Rich text content is sanitized on write using `sanitize-html` to prevent XSS att
|
|
|
164
164
|
|
|
165
165
|
## Database
|
|
166
166
|
|
|
167
|
-
Migrations live in `supabase/migrations/` at the monorepo root. See [ARCHITECTURE.md](../../ARCHITECTURE.md) for the database schema.
|
|
167
|
+
Migrations live in `supabase/migrations/` at the monorepo root. See [ARCHITECTURE.md](../../docs/ARCHITECTURE.md) for the database schema.
|
|
168
168
|
|
|
169
169
|
## Development
|
|
170
170
|
|
package/dist/index.cjs
CHANGED
|
@@ -30,22 +30,34 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
ALLOWED_DOCUMENT_MIME_TYPES: () => ALLOWED_DOCUMENT_MIME_TYPES,
|
|
33
34
|
ALLOWED_MIME_TYPES: () => ALLOWED_MIME_TYPES,
|
|
35
|
+
ALL_ALLOWED_MIME_TYPES: () => ALL_ALLOWED_MIME_TYPES,
|
|
34
36
|
AuthError: () => AuthError,
|
|
35
37
|
AuthValidationError: () => AuthValidationError,
|
|
38
|
+
CreateNavigationSchema: () => CreateNavigationSchema,
|
|
39
|
+
CreatePageSchema: () => CreatePageSchema,
|
|
40
|
+
MAX_FILE_SIZE: () => MAX_FILE_SIZE2,
|
|
36
41
|
MediaError: () => MediaError,
|
|
42
|
+
MediaUploadSchema: () => MediaUploadSchema,
|
|
37
43
|
MediaValidationError: () => MediaValidationError,
|
|
44
|
+
SignInSchema: () => SignInSchema,
|
|
38
45
|
StorageError: () => StorageError,
|
|
39
46
|
StorageValidationError: () => StorageValidationError,
|
|
40
47
|
SupabaseAuthAdapter: () => SupabaseAuthAdapter,
|
|
41
48
|
SupabaseMediaAdapter: () => SupabaseMediaAdapter,
|
|
42
49
|
SupabaseStorageAdapter: () => SupabaseStorageAdapter,
|
|
50
|
+
UpdateNavigationSchema: () => UpdateNavigationSchema,
|
|
51
|
+
UpdatePageSchema: () => UpdatePageSchema,
|
|
52
|
+
createAuditLogger: () => createAuditLogger,
|
|
43
53
|
createAuthAdapter: () => createAuthAdapter,
|
|
44
54
|
createAuthMiddleware: () => createAuthMiddleware,
|
|
45
55
|
createMediaAdapter: () => createMediaAdapter,
|
|
56
|
+
createRateLimiter: () => createRateLimiter,
|
|
46
57
|
createStorageAdapter: () => createStorageAdapter,
|
|
47
58
|
createSupabaseAdapters: () => createSupabaseAdapters,
|
|
48
59
|
ensureUniqueSlug: () => ensureUniqueSlug,
|
|
60
|
+
generateCsrfToken: () => generateCsrfToken,
|
|
49
61
|
generateSlug: () => generateSlug,
|
|
50
62
|
handleCreateNavigation: () => handleCreateNavigation,
|
|
51
63
|
handleCreatePage: () => handleCreatePage,
|
|
@@ -70,7 +82,10 @@ __export(index_exports, {
|
|
|
70
82
|
handleUpdatePage: () => handleUpdatePage,
|
|
71
83
|
handleUploadMedia: () => handleUploadMedia,
|
|
72
84
|
handleVerifySession: () => handleVerifySession,
|
|
73
|
-
resolveMediaReferences: () => resolveMediaReferences
|
|
85
|
+
resolveMediaReferences: () => resolveMediaReferences,
|
|
86
|
+
stripTags: () => stripTags,
|
|
87
|
+
validateCsrfToken: () => validateCsrfToken,
|
|
88
|
+
withAuditLog: () => withAuditLog
|
|
74
89
|
});
|
|
75
90
|
module.exports = __toCommonJS(index_exports);
|
|
76
91
|
|
|
@@ -291,6 +306,12 @@ var SANITIZE_OPTIONS = {
|
|
|
291
306
|
function sanitizeString(value) {
|
|
292
307
|
return (0, import_sanitize_html.default)(value, SANITIZE_OPTIONS);
|
|
293
308
|
}
|
|
309
|
+
function stripTags(value) {
|
|
310
|
+
return (0, import_sanitize_html.default)(value, {
|
|
311
|
+
allowedTags: [],
|
|
312
|
+
allowedAttributes: {}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
294
315
|
function sanitizeValue(value) {
|
|
295
316
|
if (typeof value === "string") {
|
|
296
317
|
return sanitizeString(value);
|
|
@@ -357,10 +378,11 @@ var StorageValidationError = class extends Error {
|
|
|
357
378
|
}
|
|
358
379
|
};
|
|
359
380
|
async function handleCreatePage(adapter, input) {
|
|
360
|
-
|
|
381
|
+
const sanitizedTitle = stripTags(input.title).trim();
|
|
382
|
+
if (!sanitizedTitle) {
|
|
361
383
|
throw new StorageValidationError("Page title must not be empty", "EMPTY_TITLE");
|
|
362
384
|
}
|
|
363
|
-
const slug = input.slug?.trim() || generateSlug(
|
|
385
|
+
const slug = input.slug?.trim() || generateSlug(sanitizedTitle);
|
|
364
386
|
if (!slug) {
|
|
365
387
|
throw new StorageValidationError(
|
|
366
388
|
"Could not generate a valid slug from the provided title",
|
|
@@ -373,6 +395,7 @@ async function handleCreatePage(adapter, input) {
|
|
|
373
395
|
const sanitizedSections = input.sections ? sanitizeSectionData(input.sections) : void 0;
|
|
374
396
|
return adapter.createPage({
|
|
375
397
|
...input,
|
|
398
|
+
title: sanitizedTitle,
|
|
376
399
|
slug: uniqueSlug,
|
|
377
400
|
sections: sanitizedSections
|
|
378
401
|
});
|
|
@@ -381,8 +404,12 @@ async function handleUpdatePage(adapter, input) {
|
|
|
381
404
|
if (!input.id.trim()) {
|
|
382
405
|
throw new StorageValidationError("Page ID must not be empty", "EMPTY_ID");
|
|
383
406
|
}
|
|
384
|
-
|
|
385
|
-
|
|
407
|
+
let sanitizedTitle;
|
|
408
|
+
if (input.title !== void 0) {
|
|
409
|
+
sanitizedTitle = stripTags(input.title).trim();
|
|
410
|
+
if (!sanitizedTitle) {
|
|
411
|
+
throw new StorageValidationError("Page title must not be empty", "EMPTY_TITLE");
|
|
412
|
+
}
|
|
386
413
|
}
|
|
387
414
|
if (input.slug !== void 0) {
|
|
388
415
|
const slug = input.slug.trim();
|
|
@@ -395,7 +422,11 @@ async function handleUpdatePage(adapter, input) {
|
|
|
395
422
|
throw new StorageValidationError(`Slug "${slug}" is already in use`, "DUPLICATE_SLUG");
|
|
396
423
|
}
|
|
397
424
|
}
|
|
398
|
-
const sanitizedInput =
|
|
425
|
+
const sanitizedInput = {
|
|
426
|
+
...input,
|
|
427
|
+
...sanitizedTitle !== void 0 && { title: sanitizedTitle },
|
|
428
|
+
...input.sections && { sections: sanitizeSectionData(input.sections) }
|
|
429
|
+
};
|
|
399
430
|
return adapter.updatePage(sanitizedInput);
|
|
400
431
|
}
|
|
401
432
|
async function handleDeletePage(adapter, id) {
|
|
@@ -405,38 +436,46 @@ async function handleDeletePage(adapter, id) {
|
|
|
405
436
|
return adapter.deletePage(id);
|
|
406
437
|
}
|
|
407
438
|
async function handleCreateNavigation(adapter, input) {
|
|
408
|
-
|
|
439
|
+
const sanitizedName = stripTags(input.name).trim();
|
|
440
|
+
if (!sanitizedName) {
|
|
409
441
|
throw new StorageValidationError("Navigation name must not be empty", "EMPTY_NAME");
|
|
410
442
|
}
|
|
411
443
|
const existingNavigations = await adapter.listNavigations();
|
|
412
444
|
const existingNames = existingNavigations.map((n) => n.name);
|
|
413
|
-
if (existingNames.includes(
|
|
445
|
+
if (existingNames.includes(sanitizedName)) {
|
|
414
446
|
throw new StorageValidationError(
|
|
415
|
-
`Navigation name "${
|
|
447
|
+
`Navigation name "${sanitizedName}" is already in use`,
|
|
416
448
|
"DUPLICATE_NAME"
|
|
417
449
|
);
|
|
418
450
|
}
|
|
419
|
-
return adapter.createNavigation(
|
|
451
|
+
return adapter.createNavigation({
|
|
452
|
+
...input,
|
|
453
|
+
name: sanitizedName
|
|
454
|
+
});
|
|
420
455
|
}
|
|
421
456
|
async function handleUpdateNavigation(adapter, input) {
|
|
422
457
|
if (!input.id.trim()) {
|
|
423
458
|
throw new StorageValidationError("Navigation ID must not be empty", "EMPTY_ID");
|
|
424
459
|
}
|
|
460
|
+
let sanitizedName;
|
|
425
461
|
if (input.name !== void 0) {
|
|
426
|
-
|
|
427
|
-
if (!
|
|
462
|
+
sanitizedName = stripTags(input.name).trim();
|
|
463
|
+
if (!sanitizedName) {
|
|
428
464
|
throw new StorageValidationError("Navigation name must not be empty", "EMPTY_NAME");
|
|
429
465
|
}
|
|
430
466
|
const existingNavigations = await adapter.listNavigations();
|
|
431
467
|
const existingNames = existingNavigations.filter((n) => n.id !== input.id).map((n) => n.name);
|
|
432
|
-
if (existingNames.includes(
|
|
468
|
+
if (existingNames.includes(sanitizedName)) {
|
|
433
469
|
throw new StorageValidationError(
|
|
434
|
-
`Navigation name "${
|
|
470
|
+
`Navigation name "${sanitizedName}" is already in use`,
|
|
435
471
|
"DUPLICATE_NAME"
|
|
436
472
|
);
|
|
437
473
|
}
|
|
438
474
|
}
|
|
439
|
-
return adapter.updateNavigation(
|
|
475
|
+
return adapter.updateNavigation({
|
|
476
|
+
...input,
|
|
477
|
+
...sanitizedName !== void 0 && { name: sanitizedName }
|
|
478
|
+
});
|
|
440
479
|
}
|
|
441
480
|
async function handleDeleteNavigation(adapter, id) {
|
|
442
481
|
if (!id.trim()) {
|
|
@@ -445,12 +484,61 @@ async function handleDeleteNavigation(adapter, id) {
|
|
|
445
484
|
return adapter.deleteNavigation(id);
|
|
446
485
|
}
|
|
447
486
|
|
|
487
|
+
// src/validation/schemas.ts
|
|
488
|
+
var import_zod = require("zod");
|
|
489
|
+
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
490
|
+
var PageSectionSchema = import_zod.z.object({
|
|
491
|
+
id: import_zod.z.string().optional(),
|
|
492
|
+
type: import_zod.z.string(),
|
|
493
|
+
data: import_zod.z.record(import_zod.z.unknown())
|
|
494
|
+
});
|
|
495
|
+
var CreatePageSchema = import_zod.z.object({
|
|
496
|
+
title: import_zod.z.string().max(200, "Title must not exceed 200 characters"),
|
|
497
|
+
pageType: import_zod.z.string(),
|
|
498
|
+
slug: import_zod.z.string().max(200, "Slug must not exceed 200 characters").optional(),
|
|
499
|
+
sections: import_zod.z.array(PageSectionSchema).optional()
|
|
500
|
+
});
|
|
501
|
+
var UpdatePageSchema = CreatePageSchema.partial();
|
|
502
|
+
var NavigationItemSchema = import_zod.z.lazy(
|
|
503
|
+
() => import_zod.z.object({
|
|
504
|
+
label: import_zod.z.string(),
|
|
505
|
+
href: import_zod.z.string(),
|
|
506
|
+
children: import_zod.z.array(NavigationItemSchema).optional()
|
|
507
|
+
})
|
|
508
|
+
);
|
|
509
|
+
var CreateNavigationSchema = import_zod.z.object({
|
|
510
|
+
name: import_zod.z.string().max(100, "Name must not exceed 100 characters"),
|
|
511
|
+
items: import_zod.z.array(NavigationItemSchema)
|
|
512
|
+
});
|
|
513
|
+
var UpdateNavigationSchema = CreateNavigationSchema.partial();
|
|
514
|
+
var SignInSchema = import_zod.z.object({
|
|
515
|
+
email: import_zod.z.string().email("Invalid email address"),
|
|
516
|
+
password: import_zod.z.string().min(1, "Password is required")
|
|
517
|
+
});
|
|
518
|
+
var MediaUploadSchema = import_zod.z.object({
|
|
519
|
+
filename: import_zod.z.string().max(255, "Filename must not exceed 255 characters"),
|
|
520
|
+
mimeType: import_zod.z.string(),
|
|
521
|
+
size: import_zod.z.number().max(MAX_FILE_SIZE, `File size must not exceed ${MAX_FILE_SIZE / 1024 / 1024}MB`),
|
|
522
|
+
data: import_zod.z.instanceof(ArrayBuffer)
|
|
523
|
+
});
|
|
524
|
+
|
|
448
525
|
// src/media/resolve.ts
|
|
449
526
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
450
|
-
var MEDIA_FIELD_SUFFIXES = [
|
|
527
|
+
var MEDIA_FIELD_SUFFIXES = [
|
|
528
|
+
"_image",
|
|
529
|
+
"_media",
|
|
530
|
+
"_photo",
|
|
531
|
+
"_thumbnail",
|
|
532
|
+
"_avatar",
|
|
533
|
+
"_icon",
|
|
534
|
+
"_file",
|
|
535
|
+
"_document",
|
|
536
|
+
"_attachment",
|
|
537
|
+
"_download"
|
|
538
|
+
];
|
|
451
539
|
function isMediaField(fieldName) {
|
|
452
540
|
const lower = fieldName.toLowerCase();
|
|
453
|
-
if (lower === "image" || lower === "media" || lower === "photo" || lower === "thumbnail" || lower === "avatar" || lower === "icon") {
|
|
541
|
+
if (lower === "image" || lower === "media" || lower === "photo" || lower === "thumbnail" || lower === "avatar" || lower === "icon" || lower === "file" || lower === "document" || lower === "attachment" || lower === "download") {
|
|
454
542
|
return true;
|
|
455
543
|
}
|
|
456
544
|
return MEDIA_FIELD_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
@@ -538,6 +626,7 @@ async function handleGetNavigation(adapter, name) {
|
|
|
538
626
|
}
|
|
539
627
|
|
|
540
628
|
// src/media/types.ts
|
|
629
|
+
var MAX_FILE_SIZE2 = 50 * 1024 * 1024;
|
|
541
630
|
var ALLOWED_MIME_TYPES = [
|
|
542
631
|
"image/jpeg",
|
|
543
632
|
"image/png",
|
|
@@ -545,8 +634,29 @@ var ALLOWED_MIME_TYPES = [
|
|
|
545
634
|
"image/webp",
|
|
546
635
|
"image/svg+xml"
|
|
547
636
|
];
|
|
637
|
+
var ALLOWED_DOCUMENT_MIME_TYPES = [
|
|
638
|
+
"application/pdf",
|
|
639
|
+
"application/msword",
|
|
640
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
641
|
+
"application/vnd.ms-excel",
|
|
642
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
643
|
+
"application/vnd.ms-powerpoint",
|
|
644
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
645
|
+
"text/plain",
|
|
646
|
+
"text/csv",
|
|
647
|
+
"application/zip",
|
|
648
|
+
"application/gzip"
|
|
649
|
+
];
|
|
650
|
+
var ALL_ALLOWED_MIME_TYPES = [
|
|
651
|
+
...ALLOWED_MIME_TYPES,
|
|
652
|
+
...ALLOWED_DOCUMENT_MIME_TYPES
|
|
653
|
+
];
|
|
548
654
|
|
|
549
655
|
// src/media/supabase-adapter.ts
|
|
656
|
+
function deriveCategory(mimeType) {
|
|
657
|
+
const imageMimes = ALLOWED_MIME_TYPES;
|
|
658
|
+
return imageMimes.includes(mimeType) ? "image" : "document";
|
|
659
|
+
}
|
|
550
660
|
var MediaError = class extends Error {
|
|
551
661
|
constructor(message, code, details) {
|
|
552
662
|
super(message);
|
|
@@ -588,6 +698,7 @@ var SupabaseMediaAdapter = class {
|
|
|
588
698
|
url: this.getPublicUrl(row.storage_path),
|
|
589
699
|
mimeType: row.mime_type,
|
|
590
700
|
size: row.size,
|
|
701
|
+
category: row.category,
|
|
591
702
|
createdAt: new Date(row.created_at)
|
|
592
703
|
};
|
|
593
704
|
}
|
|
@@ -608,7 +719,8 @@ var SupabaseMediaAdapter = class {
|
|
|
608
719
|
filename: input.filename,
|
|
609
720
|
storage_path: storagePath,
|
|
610
721
|
mime_type: input.mimeType,
|
|
611
|
-
size: input.size
|
|
722
|
+
size: input.size,
|
|
723
|
+
category: deriveCategory(input.mimeType)
|
|
612
724
|
}).select().single();
|
|
613
725
|
if (dbError) {
|
|
614
726
|
await this.client.storage.from(this.bucketName).remove([storagePath]);
|
|
@@ -635,6 +747,9 @@ var SupabaseMediaAdapter = class {
|
|
|
635
747
|
if (filter?.mimeType) {
|
|
636
748
|
query = query.eq("mime_type", filter.mimeType);
|
|
637
749
|
}
|
|
750
|
+
if (filter?.category) {
|
|
751
|
+
query = query.eq("category", filter.category);
|
|
752
|
+
}
|
|
638
753
|
query = query.order("created_at", { ascending: false });
|
|
639
754
|
if (filter?.limit) {
|
|
640
755
|
query = query.limit(filter.limit);
|
|
@@ -684,16 +799,25 @@ var MediaValidationError = class extends Error {
|
|
|
684
799
|
}
|
|
685
800
|
};
|
|
686
801
|
function validateMimeType(mimeType) {
|
|
687
|
-
const allowed =
|
|
802
|
+
const allowed = ALL_ALLOWED_MIME_TYPES;
|
|
688
803
|
if (!allowed.includes(mimeType)) {
|
|
689
804
|
throw new MediaValidationError(
|
|
690
|
-
`Invalid file type: ${mimeType}. Allowed types: ${
|
|
805
|
+
`Invalid file type: ${mimeType}. Allowed types: ${ALL_ALLOWED_MIME_TYPES.join(", ")}`,
|
|
691
806
|
"INVALID_MIME_TYPE"
|
|
692
807
|
);
|
|
693
808
|
}
|
|
694
809
|
}
|
|
810
|
+
function validateFileSize(size) {
|
|
811
|
+
if (size > MAX_FILE_SIZE2) {
|
|
812
|
+
throw new MediaValidationError(
|
|
813
|
+
`File size exceeds maximum allowed size of ${MAX_FILE_SIZE2 / 1024 / 1024}MB`,
|
|
814
|
+
"FILE_TOO_LARGE"
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
695
818
|
async function handleUploadMedia(adapter, input) {
|
|
696
819
|
validateMimeType(input.mimeType);
|
|
820
|
+
validateFileSize(input.size);
|
|
697
821
|
return adapter.upload(input);
|
|
698
822
|
}
|
|
699
823
|
async function handleGetMedia(adapter, id) {
|
|
@@ -947,6 +1071,82 @@ function createAuthMiddleware(config) {
|
|
|
947
1071
|
};
|
|
948
1072
|
}
|
|
949
1073
|
|
|
1074
|
+
// src/auth/csrf.ts
|
|
1075
|
+
function generateCsrfToken() {
|
|
1076
|
+
const bytes = new Uint8Array(32);
|
|
1077
|
+
crypto.getRandomValues(bytes);
|
|
1078
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1079
|
+
}
|
|
1080
|
+
function validateCsrfToken(cookieToken, headerToken) {
|
|
1081
|
+
if (!cookieToken || !headerToken) {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
if (cookieToken.trim() === "" || headerToken.trim() === "") {
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
return cookieToken === headerToken;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/auth/rate-limiter.ts
|
|
1091
|
+
function createRateLimiter(config) {
|
|
1092
|
+
const { windowMs, maxRequests } = config;
|
|
1093
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1094
|
+
function cleanupEntry(entry, now) {
|
|
1095
|
+
const windowStart = now - windowMs;
|
|
1096
|
+
entry.timestamps = entry.timestamps.filter((ts) => ts > windowStart);
|
|
1097
|
+
}
|
|
1098
|
+
function scheduleCleanup(key) {
|
|
1099
|
+
const entry = entries.get(key);
|
|
1100
|
+
if (!entry) return;
|
|
1101
|
+
if (entry.timeoutId) {
|
|
1102
|
+
clearTimeout(entry.timeoutId);
|
|
1103
|
+
}
|
|
1104
|
+
entry.timeoutId = setTimeout(() => {
|
|
1105
|
+
const currentEntry = entries.get(key);
|
|
1106
|
+
if (currentEntry) {
|
|
1107
|
+
cleanupEntry(currentEntry, Date.now());
|
|
1108
|
+
if (currentEntry.timestamps.length === 0) {
|
|
1109
|
+
entries.delete(key);
|
|
1110
|
+
} else {
|
|
1111
|
+
scheduleCleanup(key);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}, windowMs);
|
|
1115
|
+
}
|
|
1116
|
+
return {
|
|
1117
|
+
check(key) {
|
|
1118
|
+
const now = Date.now();
|
|
1119
|
+
let entry = entries.get(key);
|
|
1120
|
+
if (!entry) {
|
|
1121
|
+
entry = { timestamps: [] };
|
|
1122
|
+
entries.set(key, entry);
|
|
1123
|
+
}
|
|
1124
|
+
cleanupEntry(entry, now);
|
|
1125
|
+
if (entry.timestamps.length >= maxRequests) {
|
|
1126
|
+
const oldestTimestamp = entry.timestamps[0] ?? now;
|
|
1127
|
+
const retryAfterMs = oldestTimestamp + windowMs - now;
|
|
1128
|
+
const retryAfterSeconds = Math.ceil(retryAfterMs / 1e3);
|
|
1129
|
+
return {
|
|
1130
|
+
allowed: false,
|
|
1131
|
+
retryAfter: retryAfterSeconds
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
entry.timestamps.push(now);
|
|
1135
|
+
scheduleCleanup(key);
|
|
1136
|
+
return {
|
|
1137
|
+
allowed: true
|
|
1138
|
+
};
|
|
1139
|
+
},
|
|
1140
|
+
reset(key) {
|
|
1141
|
+
const entry = entries.get(key);
|
|
1142
|
+
if (entry?.timeoutId) {
|
|
1143
|
+
clearTimeout(entry.timeoutId);
|
|
1144
|
+
}
|
|
1145
|
+
entries.delete(key);
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
|
|
950
1150
|
// src/supabase/factory.ts
|
|
951
1151
|
function readEnv(name) {
|
|
952
1152
|
const processLike = globalThis.process;
|
|
@@ -1077,24 +1277,66 @@ async function handleExportSite(storageAdapter, mediaAdapter) {
|
|
|
1077
1277
|
contentDisposition: contentDisposition("site-export.json")
|
|
1078
1278
|
};
|
|
1079
1279
|
}
|
|
1280
|
+
|
|
1281
|
+
// src/audit/logger.ts
|
|
1282
|
+
var defaultSink = (entry) => {
|
|
1283
|
+
const output = JSON.stringify(entry, null, 2);
|
|
1284
|
+
globalThis.console?.log?.(output);
|
|
1285
|
+
};
|
|
1286
|
+
function createAuditLogger(sink = defaultSink) {
|
|
1287
|
+
return {
|
|
1288
|
+
log: async (entry) => {
|
|
1289
|
+
await sink({
|
|
1290
|
+
...entry,
|
|
1291
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
function withAuditLog(handler, options, sink) {
|
|
1297
|
+
const logger = createAuditLogger(sink);
|
|
1298
|
+
return (async (...args) => {
|
|
1299
|
+
const result = await handler(...args);
|
|
1300
|
+
await logger.log({
|
|
1301
|
+
action: options.action,
|
|
1302
|
+
entity: options.entity,
|
|
1303
|
+
entityId: options.extractEntityId(args),
|
|
1304
|
+
userId: options.extractUserId?.(args),
|
|
1305
|
+
metadata: options.metadata?.(args)
|
|
1306
|
+
});
|
|
1307
|
+
return result;
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1080
1310
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1081
1311
|
0 && (module.exports = {
|
|
1312
|
+
ALLOWED_DOCUMENT_MIME_TYPES,
|
|
1082
1313
|
ALLOWED_MIME_TYPES,
|
|
1314
|
+
ALL_ALLOWED_MIME_TYPES,
|
|
1083
1315
|
AuthError,
|
|
1084
1316
|
AuthValidationError,
|
|
1317
|
+
CreateNavigationSchema,
|
|
1318
|
+
CreatePageSchema,
|
|
1319
|
+
MAX_FILE_SIZE,
|
|
1085
1320
|
MediaError,
|
|
1321
|
+
MediaUploadSchema,
|
|
1086
1322
|
MediaValidationError,
|
|
1323
|
+
SignInSchema,
|
|
1087
1324
|
StorageError,
|
|
1088
1325
|
StorageValidationError,
|
|
1089
1326
|
SupabaseAuthAdapter,
|
|
1090
1327
|
SupabaseMediaAdapter,
|
|
1091
1328
|
SupabaseStorageAdapter,
|
|
1329
|
+
UpdateNavigationSchema,
|
|
1330
|
+
UpdatePageSchema,
|
|
1331
|
+
createAuditLogger,
|
|
1092
1332
|
createAuthAdapter,
|
|
1093
1333
|
createAuthMiddleware,
|
|
1094
1334
|
createMediaAdapter,
|
|
1335
|
+
createRateLimiter,
|
|
1095
1336
|
createStorageAdapter,
|
|
1096
1337
|
createSupabaseAdapters,
|
|
1097
1338
|
ensureUniqueSlug,
|
|
1339
|
+
generateCsrfToken,
|
|
1098
1340
|
generateSlug,
|
|
1099
1341
|
handleCreateNavigation,
|
|
1100
1342
|
handleCreatePage,
|
|
@@ -1119,6 +1361,9 @@ async function handleExportSite(storageAdapter, mediaAdapter) {
|
|
|
1119
1361
|
handleUpdatePage,
|
|
1120
1362
|
handleUploadMedia,
|
|
1121
1363
|
handleVerifySession,
|
|
1122
|
-
resolveMediaReferences
|
|
1364
|
+
resolveMediaReferences,
|
|
1365
|
+
stripTags,
|
|
1366
|
+
validateCsrfToken,
|
|
1367
|
+
withAuditLog
|
|
1123
1368
|
});
|
|
1124
1369
|
//# sourceMappingURL=index.cjs.map
|