@actuate-media/cms-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/actions/document-crud.test.d.ts +2 -0
- package/dist/__tests__/actions/document-crud.test.d.ts.map +1 -0
- package/dist/__tests__/actions/document-crud.test.js +156 -0
- package/dist/__tests__/actions/document-crud.test.js.map +1 -0
- package/dist/__tests__/auth/password.test.d.ts +2 -0
- package/dist/__tests__/auth/password.test.d.ts.map +1 -0
- package/dist/__tests__/auth/password.test.js +102 -0
- package/dist/__tests__/auth/password.test.js.map +1 -0
- package/dist/__tests__/auth/session.test.d.ts +2 -0
- package/dist/__tests__/auth/session.test.d.ts.map +1 -0
- package/dist/__tests__/auth/session.test.js +66 -0
- package/dist/__tests__/auth/session.test.js.map +1 -0
- package/dist/__tests__/codegen/generate-types.test.d.ts +2 -0
- package/dist/__tests__/codegen/generate-types.test.d.ts.map +1 -0
- package/dist/__tests__/codegen/generate-types.test.js +173 -0
- package/dist/__tests__/codegen/generate-types.test.js.map +1 -0
- package/dist/__tests__/scheduling/scheduling.test.d.ts +2 -0
- package/dist/__tests__/scheduling/scheduling.test.d.ts.map +1 -0
- package/dist/__tests__/scheduling/scheduling.test.js +84 -0
- package/dist/__tests__/scheduling/scheduling.test.js.map +1 -0
- package/dist/__tests__/security/access.test.d.ts +2 -0
- package/dist/__tests__/security/access.test.d.ts.map +1 -0
- package/dist/__tests__/security/access.test.js +181 -0
- package/dist/__tests__/security/access.test.js.map +1 -0
- package/dist/__tests__/security/csrf.test.d.ts +2 -0
- package/dist/__tests__/security/csrf.test.d.ts.map +1 -0
- package/dist/__tests__/security/csrf.test.js +40 -0
- package/dist/__tests__/security/csrf.test.js.map +1 -0
- package/dist/__tests__/security/rate-limit.test.d.ts +2 -0
- package/dist/__tests__/security/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/security/rate-limit.test.js +62 -0
- package/dist/__tests__/security/rate-limit.test.js.map +1 -0
- package/dist/__tests__/security/reauth.test.d.ts +2 -0
- package/dist/__tests__/security/reauth.test.d.ts.map +1 -0
- package/dist/__tests__/security/reauth.test.js +30 -0
- package/dist/__tests__/security/reauth.test.js.map +1 -0
- package/dist/__tests__/security/sanitize.test.d.ts +2 -0
- package/dist/__tests__/security/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/security/sanitize.test.js +75 -0
- package/dist/__tests__/security/sanitize.test.js.map +1 -0
- package/dist/__tests__/webhooks/webhooks.test.d.ts +2 -0
- package/dist/__tests__/webhooks/webhooks.test.d.ts.map +1 -0
- package/dist/__tests__/webhooks/webhooks.test.js +96 -0
- package/dist/__tests__/webhooks/webhooks.test.js.map +1 -0
- package/dist/a11y/index.d.ts +25 -0
- package/dist/a11y/index.d.ts.map +1 -0
- package/dist/a11y/index.js +88 -0
- package/dist/a11y/index.js.map +1 -0
- package/dist/actions.d.ts +42 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +391 -0
- package/dist/actions.js.map +1 -0
- package/dist/api/handler-factory.d.ts +7 -0
- package/dist/api/handler-factory.d.ts.map +1 -0
- package/dist/api/handler-factory.js +120 -0
- package/dist/api/handler-factory.js.map +1 -0
- package/dist/api/handlers.d.ts +4 -0
- package/dist/api/handlers.d.ts.map +1 -0
- package/dist/api/handlers.js +2119 -0
- package/dist/api/handlers.js.map +1 -0
- package/dist/api/index.d.ts +23 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +57 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/openapi.d.ts +3 -0
- package/dist/api/openapi.d.ts.map +1 -0
- package/dist/api/openapi.js +348 -0
- package/dist/api/openapi.js.map +1 -0
- package/dist/auth/index.d.ts +11 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +9 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth.d.ts +84 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +201 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/password.d.ts +13 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/password.js +47 -0
- package/dist/auth/password.js.map +1 -0
- package/dist/auth/providers/github.d.ts +9 -0
- package/dist/auth/providers/github.d.ts.map +1 -0
- package/dist/auth/providers/github.js +10 -0
- package/dist/auth/providers/github.js.map +1 -0
- package/dist/auth/providers/google.d.ts +9 -0
- package/dist/auth/providers/google.d.ts.map +1 -0
- package/dist/auth/providers/google.js +10 -0
- package/dist/auth/providers/google.js.map +1 -0
- package/dist/auth/providers/microsoft.d.ts +9 -0
- package/dist/auth/providers/microsoft.d.ts.map +1 -0
- package/dist/auth/providers/microsoft.js +11 -0
- package/dist/auth/providers/microsoft.js.map +1 -0
- package/dist/auth/session.d.ts +21 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/session.js +35 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/totp.d.ts +5 -0
- package/dist/auth/totp.d.ts.map +1 -0
- package/dist/auth/totp.js +86 -0
- package/dist/auth/totp.js.map +1 -0
- package/dist/backup/index.d.ts +19 -0
- package/dist/backup/index.d.ts.map +1 -0
- package/dist/backup/index.js +22 -0
- package/dist/backup/index.js.map +1 -0
- package/dist/cache/index.d.ts +15 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +32 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/client.d.ts +30 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +50 -0
- package/dist/client.js.map +1 -0
- package/dist/codegen/index.d.ts +4 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +370 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/collections/index.d.ts +17 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +29 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +74 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +307 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +3 -0
- package/dist/config/types.js.map +1 -0
- package/dist/content/ai-api.d.ts +21 -0
- package/dist/content/ai-api.d.ts.map +1 -0
- package/dist/content/ai-api.js +19 -0
- package/dist/content/ai-api.js.map +1 -0
- package/dist/content/content-graph.d.ts +25 -0
- package/dist/content/content-graph.d.ts.map +1 -0
- package/dist/content/content-graph.js +40 -0
- package/dist/content/content-graph.js.map +1 -0
- package/dist/content/extract.d.ts +7 -0
- package/dist/content/extract.d.ts.map +1 -0
- package/dist/content/extract.js +33 -0
- package/dist/content/extract.js.map +1 -0
- package/dist/content/index.d.ts +8 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +5 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/structured-data.d.ts +80 -0
- package/dist/content/structured-data.d.ts.map +1 -0
- package/dist/content/structured-data.js +295 -0
- package/dist/content/structured-data.js.map +1 -0
- package/dist/db/adapters/mysql.d.ts +5 -0
- package/dist/db/adapters/mysql.d.ts.map +1 -0
- package/dist/db/adapters/mysql.js +18 -0
- package/dist/db/adapters/mysql.js.map +1 -0
- package/dist/db/adapters/postgres.d.ts +7 -0
- package/dist/db/adapters/postgres.d.ts.map +1 -0
- package/dist/db/adapters/postgres.js +20 -0
- package/dist/db/adapters/postgres.js.map +1 -0
- package/dist/db/adapters/sqlite.d.ts +5 -0
- package/dist/db/adapters/sqlite.d.ts.map +1 -0
- package/dist/db/adapters/sqlite.js +19 -0
- package/dist/db/adapters/sqlite.js.map +1 -0
- package/dist/db/create-adapter.d.ts +11 -0
- package/dist/db/create-adapter.d.ts.map +1 -0
- package/dist/db/create-adapter.js +43 -0
- package/dist/db/create-adapter.js.map +1 -0
- package/dist/db/index.d.ts +9 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +5 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db.d.ts +20 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +35 -0
- package/dist/db.js.map +1 -0
- package/dist/fields/index.d.ts +15 -0
- package/dist/fields/index.d.ts.map +1 -0
- package/dist/fields/index.js +87 -0
- package/dist/fields/index.js.map +1 -0
- package/dist/forms/analytics.d.ts +62 -0
- package/dist/forms/analytics.d.ts.map +1 -0
- package/dist/forms/analytics.js +95 -0
- package/dist/forms/analytics.js.map +1 -0
- package/dist/forms/attribution.d.ts +29 -0
- package/dist/forms/attribution.d.ts.map +1 -0
- package/dist/forms/attribution.js +216 -0
- package/dist/forms/attribution.js.map +1 -0
- package/dist/forms/index.d.ts +5 -0
- package/dist/forms/index.d.ts.map +1 -0
- package/dist/forms/index.js +3 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/graphql/index.d.ts +11 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +58 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/resolvers.d.ts +8 -0
- package/dist/graphql/resolvers.d.ts.map +1 -0
- package/dist/graphql/resolvers.js +93 -0
- package/dist/graphql/resolvers.js.map +1 -0
- package/dist/graphql/schema-builder.d.ts +3 -0
- package/dist/graphql/schema-builder.d.ts.map +1 -0
- package/dist/graphql/schema-builder.js +103 -0
- package/dist/graphql/schema-builder.js.map +1 -0
- package/dist/health/index.d.ts +27 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +43 -0
- package/dist/health/index.js.map +1 -0
- package/dist/i18n/index.d.ts +22 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +37 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/media/index.d.ts +3 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +2 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/optimize.d.ts +40 -0
- package/dist/media/optimize.d.ts.map +1 -0
- package/dist/media/optimize.js +137 -0
- package/dist/media/optimize.js.map +1 -0
- package/dist/middleware.d.ts +7 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +86 -0
- package/dist/middleware.js.map +1 -0
- package/dist/multisite/index.d.ts +20 -0
- package/dist/multisite/index.d.ts.map +1 -0
- package/dist/multisite/index.js +26 -0
- package/dist/multisite/index.js.map +1 -0
- package/dist/next/preview.d.ts +10 -0
- package/dist/next/preview.d.ts.map +1 -0
- package/dist/next/preview.js +17 -0
- package/dist/next/preview.js.map +1 -0
- package/dist/next.d.ts +9 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +35 -0
- package/dist/next.js.map +1 -0
- package/dist/notifications/index.d.ts +20 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +22 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/presence/index.d.ts +24 -0
- package/dist/presence/index.d.ts.map +1 -0
- package/dist/presence/index.js +99 -0
- package/dist/presence/index.js.map +1 -0
- package/dist/preview/index.d.ts +14 -0
- package/dist/preview/index.d.ts.map +1 -0
- package/dist/preview/index.js +45 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/privacy/index.d.ts +33 -0
- package/dist/privacy/index.d.ts.map +1 -0
- package/dist/privacy/index.js +15 -0
- package/dist/privacy/index.js.map +1 -0
- package/dist/relationships/index.d.ts +13 -0
- package/dist/relationships/index.d.ts.map +1 -0
- package/dist/relationships/index.js +12 -0
- package/dist/relationships/index.js.map +1 -0
- package/dist/scheduling/index.d.ts +44 -0
- package/dist/scheduling/index.d.ts.map +1 -0
- package/dist/scheduling/index.js +119 -0
- package/dist/scheduling/index.js.map +1 -0
- package/dist/search/index.d.ts +25 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +168 -0
- package/dist/search/index.js.map +1 -0
- package/dist/security/access.d.ts +26 -0
- package/dist/security/access.d.ts.map +1 -0
- package/dist/security/access.js +92 -0
- package/dist/security/access.js.map +1 -0
- package/dist/security/anomaly-detection.d.ts +17 -0
- package/dist/security/anomaly-detection.d.ts.map +1 -0
- package/dist/security/anomaly-detection.js +17 -0
- package/dist/security/anomaly-detection.js.map +1 -0
- package/dist/security/api-key-enhanced.d.ts +25 -0
- package/dist/security/api-key-enhanced.d.ts.map +1 -0
- package/dist/security/api-key-enhanced.js +25 -0
- package/dist/security/api-key-enhanced.js.map +1 -0
- package/dist/security/audit.d.ts +39 -0
- package/dist/security/audit.d.ts.map +1 -0
- package/dist/security/audit.js +40 -0
- package/dist/security/audit.js.map +1 -0
- package/dist/security/breach-check.d.ts +3 -0
- package/dist/security/breach-check.d.ts.map +1 -0
- package/dist/security/breach-check.js +27 -0
- package/dist/security/breach-check.js.map +1 -0
- package/dist/security/cors.d.ts +11 -0
- package/dist/security/cors.d.ts.map +1 -0
- package/dist/security/cors.js +33 -0
- package/dist/security/cors.js.map +1 -0
- package/dist/security/csp-nonces.d.ts +5 -0
- package/dist/security/csp-nonces.d.ts.map +1 -0
- package/dist/security/csp-nonces.js +24 -0
- package/dist/security/csp-nonces.js.map +1 -0
- package/dist/security/csrf.d.ts +5 -0
- package/dist/security/csrf.d.ts.map +1 -0
- package/dist/security/csrf.js +20 -0
- package/dist/security/csrf.js.map +1 -0
- package/dist/security/encrypted-fields.d.ts +5 -0
- package/dist/security/encrypted-fields.d.ts.map +1 -0
- package/dist/security/encrypted-fields.js +40 -0
- package/dist/security/encrypted-fields.js.map +1 -0
- package/dist/security/headers.d.ts +11 -0
- package/dist/security/headers.d.ts.map +1 -0
- package/dist/security/headers.js +32 -0
- package/dist/security/headers.js.map +1 -0
- package/dist/security/index.d.ts +31 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +20 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/ip-allowlist.d.ts +3 -0
- package/dist/security/ip-allowlist.d.ts.map +1 -0
- package/dist/security/ip-allowlist.js +35 -0
- package/dist/security/ip-allowlist.js.map +1 -0
- package/dist/security/middleware.d.ts +20 -0
- package/dist/security/middleware.d.ts.map +1 -0
- package/dist/security/middleware.js +45 -0
- package/dist/security/middleware.js.map +1 -0
- package/dist/security/rate-limit.d.ts +24 -0
- package/dist/security/rate-limit.d.ts.map +1 -0
- package/dist/security/rate-limit.js +84 -0
- package/dist/security/rate-limit.js.map +1 -0
- package/dist/security/reauth.d.ts +15 -0
- package/dist/security/reauth.d.ts.map +1 -0
- package/dist/security/reauth.js +38 -0
- package/dist/security/reauth.js.map +1 -0
- package/dist/security/sanitize.d.ts +13 -0
- package/dist/security/sanitize.d.ts.map +1 -0
- package/dist/security/sanitize.js +34 -0
- package/dist/security/sanitize.js.map +1 -0
- package/dist/security/security-txt.d.ts +12 -0
- package/dist/security/security-txt.d.ts.map +1 -0
- package/dist/security/security-txt.js +19 -0
- package/dist/security/security-txt.js.map +1 -0
- package/dist/security/session-limits.d.ts +17 -0
- package/dist/security/session-limits.d.ts.map +1 -0
- package/dist/security/session-limits.js +14 -0
- package/dist/security/session-limits.js.map +1 -0
- package/dist/security/upload.d.ts +13 -0
- package/dist/security/upload.d.ts.map +1 -0
- package/dist/security/upload.js +34 -0
- package/dist/security/upload.js.map +1 -0
- package/dist/security/webhook.d.ts +12 -0
- package/dist/security/webhook.d.ts.map +1 -0
- package/dist/security/webhook.js +38 -0
- package/dist/security/webhook.js.map +1 -0
- package/dist/seo/analysis.d.ts +66 -0
- package/dist/seo/analysis.d.ts.map +1 -0
- package/dist/seo/analysis.js +594 -0
- package/dist/seo/analysis.js.map +1 -0
- package/dist/seo/index.d.ts +9 -0
- package/dist/seo/index.d.ts.map +1 -0
- package/dist/seo/index.js +5 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/seo/llms-txt.d.ts +16 -0
- package/dist/seo/llms-txt.d.ts.map +1 -0
- package/dist/seo/llms-txt.js +70 -0
- package/dist/seo/llms-txt.js.map +1 -0
- package/dist/seo/meta-tags.d.ts +33 -0
- package/dist/seo/meta-tags.d.ts.map +1 -0
- package/dist/seo/meta-tags.js +159 -0
- package/dist/seo/meta-tags.js.map +1 -0
- package/dist/seo/title-templates.d.ts +17 -0
- package/dist/seo/title-templates.d.ts.map +1 -0
- package/dist/seo/title-templates.js +28 -0
- package/dist/seo/title-templates.js.map +1 -0
- package/dist/setup/index.d.ts +38 -0
- package/dist/setup/index.d.ts.map +1 -0
- package/dist/setup/index.js +77 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +11 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/templates/index.d.ts +16 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +23 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/upgrade/changelog.d.ts +13 -0
- package/dist/upgrade/changelog.d.ts.map +1 -0
- package/dist/upgrade/changelog.js +54 -0
- package/dist/upgrade/changelog.js.map +1 -0
- package/dist/upgrade/index.d.ts +7 -0
- package/dist/upgrade/index.d.ts.map +1 -0
- package/dist/upgrade/index.js +4 -0
- package/dist/upgrade/index.js.map +1 -0
- package/dist/upgrade/upgrade-pr.d.ts +16 -0
- package/dist/upgrade/upgrade-pr.d.ts.map +1 -0
- package/dist/upgrade/upgrade-pr.js +38 -0
- package/dist/upgrade/upgrade-pr.js.map +1 -0
- package/dist/upgrade/version-check.d.ts +17 -0
- package/dist/upgrade/version-check.d.ts.map +1 -0
- package/dist/upgrade/version-check.js +30 -0
- package/dist/upgrade/version-check.js.map +1 -0
- package/dist/webhooks/index.d.ts +46 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +245 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +56 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflows/index.d.ts +30 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/index.js +14 -0
- package/dist/workflows/index.js.map +1 -0
- package/generated/browser.ts +109 -0
- package/generated/client.ts +133 -0
- package/generated/commonInputTypes.ts +709 -0
- package/generated/enums.ts +125 -0
- package/generated/internal/class.ts +376 -0
- package/generated/internal/prismaNamespace.ts +2617 -0
- package/generated/internal/prismaNamespaceBrowser.ts +611 -0
- package/generated/models/ApiKey.ts +1550 -0
- package/generated/models/AuditLog.ts +1206 -0
- package/generated/models/BackupRecord.ts +1250 -0
- package/generated/models/ContentLock.ts +1472 -0
- package/generated/models/ContentTemplate.ts +1416 -0
- package/generated/models/Document.ts +3005 -0
- package/generated/models/Folder.ts +1904 -0
- package/generated/models/FormSubmission.ts +1200 -0
- package/generated/models/InAppNotification.ts +1457 -0
- package/generated/models/Media.ts +2340 -0
- package/generated/models/MediaUsage.ts +1472 -0
- package/generated/models/OAuthAccount.ts +1463 -0
- package/generated/models/Redirect.ts +1284 -0
- package/generated/models/Session.ts +1492 -0
- package/generated/models/Site.ts +1206 -0
- package/generated/models/User.ts +3513 -0
- package/generated/models/Version.ts +1511 -0
- package/generated/models/WorkflowState.ts +1514 -0
- package/generated/models.ts +29 -0
- package/package.json +83 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { signPayload, verifySignature } from '../../webhooks/index';
|
|
3
|
+
import { validateWebhookUrl } from '../../security/webhook';
|
|
4
|
+
describe('Webhook signing', () => {
|
|
5
|
+
const secret = 'test-webhook-secret-key-12345';
|
|
6
|
+
it('signPayload produces a hex string', async () => {
|
|
7
|
+
const sig = await signPayload('{"event":"test"}', secret);
|
|
8
|
+
expect(sig).toMatch(/^[0-9a-f]{64}$/);
|
|
9
|
+
});
|
|
10
|
+
it('signPayload is deterministic', async () => {
|
|
11
|
+
const body = '{"event":"document.created","data":{}}';
|
|
12
|
+
const sig1 = await signPayload(body, secret);
|
|
13
|
+
const sig2 = await signPayload(body, secret);
|
|
14
|
+
expect(sig1).toBe(sig2);
|
|
15
|
+
});
|
|
16
|
+
it('different payloads produce different signatures', async () => {
|
|
17
|
+
const sig1 = await signPayload('payload-a', secret);
|
|
18
|
+
const sig2 = await signPayload('payload-b', secret);
|
|
19
|
+
expect(sig1).not.toBe(sig2);
|
|
20
|
+
});
|
|
21
|
+
it('different secrets produce different signatures', async () => {
|
|
22
|
+
const body = 'same-payload';
|
|
23
|
+
const sig1 = await signPayload(body, 'secret-1');
|
|
24
|
+
const sig2 = await signPayload(body, 'secret-2');
|
|
25
|
+
expect(sig1).not.toBe(sig2);
|
|
26
|
+
});
|
|
27
|
+
it('verifySignature returns true for valid signature', async () => {
|
|
28
|
+
const body = '{"event":"test","data":{}}';
|
|
29
|
+
const sig = await signPayload(body, secret);
|
|
30
|
+
const valid = await verifySignature(body, sig, secret);
|
|
31
|
+
expect(valid).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it('verifySignature returns false for tampered payload', async () => {
|
|
34
|
+
const sig = await signPayload('original', secret);
|
|
35
|
+
const valid = await verifySignature('tampered', sig, secret);
|
|
36
|
+
expect(valid).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it('verifySignature returns false for wrong secret', async () => {
|
|
39
|
+
const body = 'test-payload';
|
|
40
|
+
const sig = await signPayload(body, secret);
|
|
41
|
+
const valid = await verifySignature(body, sig, 'wrong-secret');
|
|
42
|
+
expect(valid).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it('verifySignature returns false for truncated signature', async () => {
|
|
45
|
+
const body = 'test';
|
|
46
|
+
const sig = await signPayload(body, secret);
|
|
47
|
+
const valid = await verifySignature(body, sig.slice(0, 32), secret);
|
|
48
|
+
expect(valid).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('Webhook URL SSRF validation', () => {
|
|
52
|
+
it('allows valid HTTPS URLs', () => {
|
|
53
|
+
expect(validateWebhookUrl('https://example.com/webhook')).toEqual({ valid: true });
|
|
54
|
+
expect(validateWebhookUrl('https://hooks.slack.com/services/xxx')).toEqual({ valid: true });
|
|
55
|
+
});
|
|
56
|
+
it('allows valid HTTP URLs', () => {
|
|
57
|
+
expect(validateWebhookUrl('http://example.com/webhook')).toEqual({ valid: true });
|
|
58
|
+
});
|
|
59
|
+
it('rejects localhost', () => {
|
|
60
|
+
const result = validateWebhookUrl('http://localhost:3000/hook');
|
|
61
|
+
expect(result.valid).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
it('rejects 0.0.0.0', () => {
|
|
64
|
+
const result = validateWebhookUrl('http://0.0.0.0/hook');
|
|
65
|
+
expect(result.valid).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it('rejects 10.x private range', () => {
|
|
68
|
+
const result = validateWebhookUrl('http://10.0.0.1/hook');
|
|
69
|
+
expect(result.valid).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
it('rejects 192.168.x private range', () => {
|
|
72
|
+
const result = validateWebhookUrl('http://192.168.1.1/hook');
|
|
73
|
+
expect(result.valid).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
it('rejects 172.16-31.x private range', () => {
|
|
76
|
+
expect(validateWebhookUrl('http://172.16.0.1/hook').valid).toBe(false);
|
|
77
|
+
expect(validateWebhookUrl('http://172.31.255.1/hook').valid).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it('rejects 127.x loopback', () => {
|
|
80
|
+
const result = validateWebhookUrl('http://127.0.0.1/hook');
|
|
81
|
+
expect(result.valid).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
it('rejects link-local addresses', () => {
|
|
84
|
+
const result = validateWebhookUrl('http://169.254.1.1/hook');
|
|
85
|
+
expect(result.valid).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it('rejects non-HTTP protocols', () => {
|
|
88
|
+
expect(validateWebhookUrl('ftp://example.com/file').valid).toBe(false);
|
|
89
|
+
expect(validateWebhookUrl('file:///etc/passwd').valid).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
it('rejects invalid URLs', () => {
|
|
92
|
+
expect(validateWebhookUrl('not-a-url').valid).toBe(false);
|
|
93
|
+
expect(validateWebhookUrl('').valid).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=webhooks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.test.js","sourceRoot":"","sources":["../../../src/__tests__/webhooks/webhooks.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type Severity = "error" | "warning" | "info";
|
|
2
|
+
export interface AuditRule {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
severity: Severity;
|
|
7
|
+
check: (html: string) => AuditViolation[];
|
|
8
|
+
}
|
|
9
|
+
export interface AuditViolation {
|
|
10
|
+
ruleId: string;
|
|
11
|
+
severity: Severity;
|
|
12
|
+
message: string;
|
|
13
|
+
selector?: string;
|
|
14
|
+
context?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AuditReport {
|
|
17
|
+
violations: AuditViolation[];
|
|
18
|
+
passes: number;
|
|
19
|
+
timestamp: Date;
|
|
20
|
+
}
|
|
21
|
+
/** Built-in accessibility audit rules for rich text content. */
|
|
22
|
+
export declare const BUILT_IN_RULES: AuditRule[];
|
|
23
|
+
/** Run accessibility audit rules against HTML content. */
|
|
24
|
+
export declare function auditContent(html: string, rules?: AuditRule[]): AuditReport;
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/a11y/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,cAAc,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,gEAAgE;AAChE,eAAO,MAAM,cAAc,EAAE,SAAS,EAqErC,CAAC;AAEF,0DAA0D;AAC1D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,WAAW,CAe3E"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** Built-in accessibility audit rules for rich text content. */
|
|
2
|
+
export const BUILT_IN_RULES = [
|
|
3
|
+
{
|
|
4
|
+
id: "img-alt",
|
|
5
|
+
name: "Image alt text",
|
|
6
|
+
description: "Images must have alt attributes",
|
|
7
|
+
severity: "error",
|
|
8
|
+
check(html) {
|
|
9
|
+
const violations = [];
|
|
10
|
+
const imgRegex = /<img(?![^>]*alt=)[^>]*>/gi;
|
|
11
|
+
let match;
|
|
12
|
+
while ((match = imgRegex.exec(html)) !== null) {
|
|
13
|
+
violations.push({
|
|
14
|
+
ruleId: "img-alt",
|
|
15
|
+
severity: "error",
|
|
16
|
+
message: "Image is missing alt attribute",
|
|
17
|
+
context: match[0].slice(0, 100),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return violations;
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "heading-order",
|
|
25
|
+
name: "Heading order",
|
|
26
|
+
description: "Headings should not skip levels",
|
|
27
|
+
severity: "warning",
|
|
28
|
+
check(html) {
|
|
29
|
+
const violations = [];
|
|
30
|
+
const headingRegex = /<h([1-6])/gi;
|
|
31
|
+
let lastLevel = 0;
|
|
32
|
+
let match;
|
|
33
|
+
while ((match = headingRegex.exec(html)) !== null) {
|
|
34
|
+
const level = parseInt(match[1], 10);
|
|
35
|
+
if (lastLevel > 0 && level > lastLevel + 1) {
|
|
36
|
+
violations.push({
|
|
37
|
+
ruleId: "heading-order",
|
|
38
|
+
severity: "warning",
|
|
39
|
+
message: `Heading level skipped from h${lastLevel} to h${level}`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
lastLevel = level;
|
|
43
|
+
}
|
|
44
|
+
return violations;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "link-text",
|
|
49
|
+
name: "Link text",
|
|
50
|
+
description: "Links should have descriptive text, not 'click here'",
|
|
51
|
+
severity: "warning",
|
|
52
|
+
check(html) {
|
|
53
|
+
const violations = [];
|
|
54
|
+
const linkRegex = /<a[^>]*>(.*?)<\/a>/gi;
|
|
55
|
+
const badTexts = new Set(["click here", "here", "read more", "more", "link"]);
|
|
56
|
+
let match;
|
|
57
|
+
while ((match = linkRegex.exec(html)) !== null) {
|
|
58
|
+
const text = match[1]?.replace(/<[^>]*>/g, "").trim().toLowerCase();
|
|
59
|
+
if (text && badTexts.has(text)) {
|
|
60
|
+
violations.push({
|
|
61
|
+
ruleId: "link-text",
|
|
62
|
+
severity: "warning",
|
|
63
|
+
message: `Link has non-descriptive text: "${text}"`,
|
|
64
|
+
context: match[0].slice(0, 100),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return violations;
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
/** Run accessibility audit rules against HTML content. */
|
|
73
|
+
export function auditContent(html, rules) {
|
|
74
|
+
const activeRules = rules ?? BUILT_IN_RULES;
|
|
75
|
+
const violations = [];
|
|
76
|
+
let passes = 0;
|
|
77
|
+
for (const rule of activeRules) {
|
|
78
|
+
const found = rule.check(html);
|
|
79
|
+
if (found.length > 0) {
|
|
80
|
+
violations.push(...found);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
passes++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { violations, passes, timestamp: new Date() };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/a11y/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface ActionContext {
|
|
2
|
+
userId: string;
|
|
3
|
+
role: string;
|
|
4
|
+
locale?: string;
|
|
5
|
+
/** PrismaClient instance — typed as unknown to avoid hard coupling at config level. */
|
|
6
|
+
db: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface ListOptions {
|
|
9
|
+
collection: string;
|
|
10
|
+
page?: number;
|
|
11
|
+
pageSize?: number;
|
|
12
|
+
sort?: string;
|
|
13
|
+
order?: "asc" | "desc";
|
|
14
|
+
filters?: Record<string, unknown>;
|
|
15
|
+
locale?: string;
|
|
16
|
+
status?: string;
|
|
17
|
+
folderId?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ListResult {
|
|
20
|
+
docs: Record<string, unknown>[];
|
|
21
|
+
total: number;
|
|
22
|
+
page: number;
|
|
23
|
+
pageSize: number;
|
|
24
|
+
totalPages: number;
|
|
25
|
+
}
|
|
26
|
+
/** Create a new document in a collection. */
|
|
27
|
+
export declare function createDocument(collection: string, data: Record<string, unknown>, ctx: ActionContext): Promise<Record<string, unknown>>;
|
|
28
|
+
/** Update an existing document. Merges incoming data with existing to prevent field loss. */
|
|
29
|
+
export declare function updateDocument(collection: string, id: string, data: Record<string, unknown>, ctx: ActionContext): Promise<Record<string, unknown>>;
|
|
30
|
+
/** Soft-delete a document. Creates a version snapshot before deletion. */
|
|
31
|
+
export declare function deleteDocument(collection: string, id: string, ctx: ActionContext): Promise<void>;
|
|
32
|
+
/** Retrieve a single document by ID. */
|
|
33
|
+
export declare function getDocument(collection: string, id: string, ctx: ActionContext): Promise<Record<string, unknown> | null>;
|
|
34
|
+
/** List documents with pagination, sorting, and filtering. */
|
|
35
|
+
export declare function listDocuments(options: ListOptions, ctx: ActionContext): Promise<ListResult>;
|
|
36
|
+
/** Duplicate a document, resetting status to DRAFT and appending "(Copy)" to the title. */
|
|
37
|
+
export declare function duplicateDocument(collection: string, sourceId: string, ctx: ActionContext): Promise<Record<string, unknown>>;
|
|
38
|
+
/** Retrieve a global's data. Globals are stored as single-row collections keyed by slug. */
|
|
39
|
+
export declare function getGlobal(slug: string, ctx: ActionContext): Promise<Record<string, unknown> | null>;
|
|
40
|
+
/** Update a global's data. Creates the document if it doesn't exist (upsert). */
|
|
41
|
+
export declare function updateGlobal(slug: string, data: Record<string, unknown>, ctx: ActionContext): Promise<Record<string, unknown>>;
|
|
42
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,EAAE,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AA8FD,6CAA6C;AAC7C,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA4ClC;AAED,6FAA6F;AAC7F,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA8DlC;AAED,0EAA0E;AAC1E,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED,wCAAwC;AACxC,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAmBzC;AAED,8DAA8D;AAC9D,wBAAsB,aAAa,CACjC,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,UAAU,CAAC,CAiFrB;AAED,2FAA2F;AAC3F,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsDlC;AAED,4FAA4F;AAC5F,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAMzC;AAED,iFAAiF;AACjF,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAyDlC"}
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { extractPlainText, hashContent } from "./content/index";
|
|
2
|
+
import { applyFieldAccess } from "./security/access";
|
|
3
|
+
import { sanitizeHtml } from "./security/sanitize";
|
|
4
|
+
function prisma(ctx) {
|
|
5
|
+
return ctx.db;
|
|
6
|
+
}
|
|
7
|
+
async function fireWebhook(event, data) {
|
|
8
|
+
try {
|
|
9
|
+
const { dispatch } = await import('./webhooks/index');
|
|
10
|
+
dispatch(event, data).catch((err) => {
|
|
11
|
+
console.error(`[actuate] Webhook dispatch failed for ${event}:`, err);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
console.error(`[actuate] Webhook module import failed:`, err);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function firePluginHooks(event, doc, ctx) {
|
|
19
|
+
try {
|
|
20
|
+
const config = globalThis.__actuateConfig;
|
|
21
|
+
const hooks = config?._pluginHooks ?? [];
|
|
22
|
+
const matching = hooks.filter((h) => h.event === event);
|
|
23
|
+
for (const hook of matching) {
|
|
24
|
+
try {
|
|
25
|
+
await hook.handler(doc);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error(`[actuate] Plugin hook "${event}" error:`, err);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (doc.data && typeof doc.data === 'object') {
|
|
32
|
+
const docData = doc.data;
|
|
33
|
+
const hasAiUpdates = docData._aiScore !== undefined || docData._embedding !== undefined;
|
|
34
|
+
if (hasAiUpdates && doc.id) {
|
|
35
|
+
const db = prisma(ctx);
|
|
36
|
+
await db.document.update({
|
|
37
|
+
where: { id: doc.id },
|
|
38
|
+
data: { data: docData },
|
|
39
|
+
}).catch((err) => {
|
|
40
|
+
console.error(`[actuate] Failed to persist plugin hook data:`, err);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.error(`[actuate] Plugin hooks dispatch failed:`, err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function getCollectionFields(collection) {
|
|
50
|
+
const config = globalThis.__actuateConfig?.collections?.[collection];
|
|
51
|
+
return config?.fields ?? null;
|
|
52
|
+
}
|
|
53
|
+
async function checkCollectionAccess(collection, operation, ctx, doc) {
|
|
54
|
+
const collectionConfig = globalThis.__actuateConfig?.collections?.[collection];
|
|
55
|
+
const accessFn = collectionConfig?.access?.[operation];
|
|
56
|
+
if (!accessFn)
|
|
57
|
+
return;
|
|
58
|
+
const user = ctx.userId ? { id: ctx.userId, role: ctx.role } : null;
|
|
59
|
+
const allowed = await accessFn({ user, doc });
|
|
60
|
+
if (!allowed) {
|
|
61
|
+
throw new Error(`Access denied: cannot ${operation} in collection "${collection}"`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function sanitizeDocumentData(data, collection) {
|
|
65
|
+
const config = globalThis.__actuateConfig?.collections?.[collection];
|
|
66
|
+
const fields = config?.fields ?? {};
|
|
67
|
+
const result = { ...data };
|
|
68
|
+
for (const [key, value] of Object.entries(result)) {
|
|
69
|
+
if (typeof value !== 'string')
|
|
70
|
+
continue;
|
|
71
|
+
const fieldDef = fields[key];
|
|
72
|
+
if (fieldDef?.type === 'richText') {
|
|
73
|
+
result[key] = sanitizeHtml(value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
async function computeTextFields(data) {
|
|
79
|
+
const serialized = JSON.stringify(data);
|
|
80
|
+
const plainText = extractPlainText(serialized);
|
|
81
|
+
const contentHash = await hashContent(serialized);
|
|
82
|
+
return { plainText, contentHash };
|
|
83
|
+
}
|
|
84
|
+
/** Create a new document in a collection. */
|
|
85
|
+
export async function createDocument(collection, data, ctx) {
|
|
86
|
+
await checkCollectionAccess(collection, 'create', ctx);
|
|
87
|
+
const db = prisma(ctx);
|
|
88
|
+
const user = { id: ctx.userId, role: ctx.role };
|
|
89
|
+
const fields = getCollectionFields(collection);
|
|
90
|
+
const accessFiltered = fields
|
|
91
|
+
? await applyFieldAccess("write", fields, data, user)
|
|
92
|
+
: data;
|
|
93
|
+
const filteredData = sanitizeDocumentData(accessFiltered, collection);
|
|
94
|
+
const { plainText, contentHash } = await computeTextFields(filteredData);
|
|
95
|
+
return db.$transaction(async (tx) => {
|
|
96
|
+
const doc = await tx.document.create({
|
|
97
|
+
data: {
|
|
98
|
+
collection,
|
|
99
|
+
title: typeof filteredData.title === 'string' ? filteredData.title : null,
|
|
100
|
+
slug: typeof filteredData.slug === 'string' ? filteredData.slug : null,
|
|
101
|
+
data: filteredData,
|
|
102
|
+
status: "DRAFT",
|
|
103
|
+
locale: ctx.locale ?? null,
|
|
104
|
+
createdById: ctx.userId,
|
|
105
|
+
updatedById: ctx.userId,
|
|
106
|
+
plainText,
|
|
107
|
+
contentHash,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
await tx.version.create({
|
|
111
|
+
data: {
|
|
112
|
+
documentId: doc.id,
|
|
113
|
+
data: filteredData,
|
|
114
|
+
changedById: ctx.userId,
|
|
115
|
+
changeType: "CREATE",
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
fireWebhook('document.created', { id: doc.id, collection, title: doc.title, slug: doc.slug });
|
|
119
|
+
firePluginHooks('afterCreate', doc, ctx);
|
|
120
|
+
return doc;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Update an existing document. Merges incoming data with existing to prevent field loss. */
|
|
124
|
+
export async function updateDocument(collection, id, data, ctx) {
|
|
125
|
+
await checkCollectionAccess(collection, 'update', ctx);
|
|
126
|
+
const db = prisma(ctx);
|
|
127
|
+
const user = { id: ctx.userId, role: ctx.role };
|
|
128
|
+
const existing = await db.document.findFirst({
|
|
129
|
+
where: { id, collection, deletedAt: null },
|
|
130
|
+
});
|
|
131
|
+
if (!existing) {
|
|
132
|
+
throw new Error(`Document "${id}" not found in collection "${collection}"`);
|
|
133
|
+
}
|
|
134
|
+
const existingData = (existing.data && typeof existing.data === 'object')
|
|
135
|
+
? existing.data
|
|
136
|
+
: {};
|
|
137
|
+
const mergedData = { ...existingData, ...data };
|
|
138
|
+
const fields = getCollectionFields(collection);
|
|
139
|
+
const accessFiltered = fields
|
|
140
|
+
? await applyFieldAccess("write", fields, mergedData, user)
|
|
141
|
+
: mergedData;
|
|
142
|
+
const filteredData = sanitizeDocumentData(accessFiltered, collection);
|
|
143
|
+
const { plainText, contentHash } = await computeTextFields(filteredData);
|
|
144
|
+
const status = typeof filteredData.status === 'string' ? filteredData.status : undefined;
|
|
145
|
+
return db.$transaction(async (tx) => {
|
|
146
|
+
const updated = await tx.document.update({
|
|
147
|
+
where: { id },
|
|
148
|
+
data: {
|
|
149
|
+
title: typeof filteredData.title === 'string' ? filteredData.title : undefined,
|
|
150
|
+
slug: typeof filteredData.slug === 'string' ? filteredData.slug : undefined,
|
|
151
|
+
data: filteredData,
|
|
152
|
+
updatedById: ctx.userId,
|
|
153
|
+
plainText,
|
|
154
|
+
contentHash,
|
|
155
|
+
...(status ? { status } : {}),
|
|
156
|
+
...(status === 'PUBLISHED' ? { publishedAt: new Date() } : {}),
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
await tx.version.create({
|
|
160
|
+
data: {
|
|
161
|
+
documentId: id,
|
|
162
|
+
data: filteredData,
|
|
163
|
+
changedById: ctx.userId,
|
|
164
|
+
changeType: "UPDATE",
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
const webhookEvent = status === 'PUBLISHED' ? 'document.published' : 'document.updated';
|
|
168
|
+
fireWebhook(webhookEvent, { id, collection, title: updated.title, slug: updated.slug, status: status ?? existing.status });
|
|
169
|
+
firePluginHooks('afterUpdate', updated, ctx);
|
|
170
|
+
return updated;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** Soft-delete a document. Creates a version snapshot before deletion. */
|
|
174
|
+
export async function deleteDocument(collection, id, ctx) {
|
|
175
|
+
await checkCollectionAccess(collection, 'delete', ctx);
|
|
176
|
+
const db = prisma(ctx);
|
|
177
|
+
const existing = await db.document.findFirst({
|
|
178
|
+
where: { id, collection, deletedAt: null },
|
|
179
|
+
});
|
|
180
|
+
if (!existing) {
|
|
181
|
+
throw new Error(`Document "${id}" not found in collection "${collection}"`);
|
|
182
|
+
}
|
|
183
|
+
await db.$transaction(async (tx) => {
|
|
184
|
+
await tx.version.create({
|
|
185
|
+
data: {
|
|
186
|
+
documentId: id,
|
|
187
|
+
data: existing.data ?? {},
|
|
188
|
+
changedById: ctx.userId,
|
|
189
|
+
changeType: 'DELETE',
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
await tx.document.update({
|
|
193
|
+
where: { id },
|
|
194
|
+
data: { deletedAt: new Date() },
|
|
195
|
+
});
|
|
196
|
+
fireWebhook('document.deleted', { id, collection, title: existing.title, slug: existing.slug });
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/** Retrieve a single document by ID. */
|
|
200
|
+
export async function getDocument(collection, id, ctx) {
|
|
201
|
+
const db = prisma(ctx);
|
|
202
|
+
const doc = await db.document.findFirst({
|
|
203
|
+
where: { id, collection, deletedAt: null },
|
|
204
|
+
});
|
|
205
|
+
if (!doc)
|
|
206
|
+
return null;
|
|
207
|
+
const fields = getCollectionFields(collection);
|
|
208
|
+
if (fields && doc.data && typeof doc.data === "object") {
|
|
209
|
+
const user = { id: ctx.userId, role: ctx.role };
|
|
210
|
+
return {
|
|
211
|
+
...doc,
|
|
212
|
+
data: await applyFieldAccess("read", fields, doc.data, user),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return doc;
|
|
216
|
+
}
|
|
217
|
+
/** List documents with pagination, sorting, and filtering. */
|
|
218
|
+
export async function listDocuments(options, ctx) {
|
|
219
|
+
const db = prisma(ctx);
|
|
220
|
+
const page = options.page ?? 1;
|
|
221
|
+
const pageSize = options.pageSize ?? 20;
|
|
222
|
+
const skip = (page - 1) * pageSize;
|
|
223
|
+
const where = {
|
|
224
|
+
collection: options.collection,
|
|
225
|
+
deletedAt: null,
|
|
226
|
+
};
|
|
227
|
+
if (options.status) {
|
|
228
|
+
where.status = options.status;
|
|
229
|
+
}
|
|
230
|
+
if (options.locale) {
|
|
231
|
+
where.locale = options.locale;
|
|
232
|
+
}
|
|
233
|
+
if (options.folderId === 'none') {
|
|
234
|
+
where.folderId = null;
|
|
235
|
+
}
|
|
236
|
+
else if (options.folderId) {
|
|
237
|
+
where.folderId = options.folderId;
|
|
238
|
+
}
|
|
239
|
+
const ALLOWED_FILTERS = new Set([
|
|
240
|
+
'status', 'locale', 'collection', 'siteId', 'templateId', 'folderId',
|
|
241
|
+
]);
|
|
242
|
+
if (options.filters) {
|
|
243
|
+
for (const [key, value] of Object.entries(options.filters)) {
|
|
244
|
+
if (ALLOWED_FILTERS.has(key) && typeof value !== 'object') {
|
|
245
|
+
where[key] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const ALLOWED_SORT = new Set([
|
|
250
|
+
'createdAt', 'updatedAt', 'publishedAt', 'status', 'collection',
|
|
251
|
+
]);
|
|
252
|
+
const orderBy = {};
|
|
253
|
+
const sortField = ALLOWED_SORT.has(options.sort ?? '') ? options.sort : 'createdAt';
|
|
254
|
+
orderBy[sortField] = options.order ?? "desc";
|
|
255
|
+
const [rawDocs, total] = await Promise.all([
|
|
256
|
+
db.document.findMany({
|
|
257
|
+
where,
|
|
258
|
+
skip,
|
|
259
|
+
take: pageSize,
|
|
260
|
+
orderBy,
|
|
261
|
+
}),
|
|
262
|
+
db.document.count({ where }),
|
|
263
|
+
]);
|
|
264
|
+
const fields = getCollectionFields(options.collection);
|
|
265
|
+
let docs = rawDocs;
|
|
266
|
+
if (fields) {
|
|
267
|
+
const user = { id: ctx.userId, role: ctx.role };
|
|
268
|
+
docs = await Promise.all(rawDocs.map(async (doc) => {
|
|
269
|
+
if (doc.data && typeof doc.data === "object") {
|
|
270
|
+
return {
|
|
271
|
+
...doc,
|
|
272
|
+
data: await applyFieldAccess("read", fields, doc.data, user),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return doc;
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
docs,
|
|
280
|
+
total,
|
|
281
|
+
page,
|
|
282
|
+
pageSize,
|
|
283
|
+
totalPages: Math.ceil(total / pageSize),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/** Duplicate a document, resetting status to DRAFT and appending "(Copy)" to the title. */
|
|
287
|
+
export async function duplicateDocument(collection, sourceId, ctx) {
|
|
288
|
+
const db = prisma(ctx);
|
|
289
|
+
const source = await db.document.findFirst({
|
|
290
|
+
where: { id: sourceId, collection, deletedAt: null },
|
|
291
|
+
});
|
|
292
|
+
if (!source) {
|
|
293
|
+
throw new Error(`Source document "${sourceId}" not found in collection "${collection}"`);
|
|
294
|
+
}
|
|
295
|
+
const clonedData = { ...source.data };
|
|
296
|
+
if (typeof clonedData.title === "string") {
|
|
297
|
+
clonedData.title += " (Copy)";
|
|
298
|
+
}
|
|
299
|
+
else if (typeof clonedData.name === "string") {
|
|
300
|
+
clonedData.name += " (Copy)";
|
|
301
|
+
}
|
|
302
|
+
if (typeof clonedData.slug === "string") {
|
|
303
|
+
clonedData.slug += "-copy";
|
|
304
|
+
}
|
|
305
|
+
const { plainText, contentHash } = await computeTextFields(clonedData);
|
|
306
|
+
return db.$transaction(async (tx) => {
|
|
307
|
+
const doc = await tx.document.create({
|
|
308
|
+
data: {
|
|
309
|
+
collection,
|
|
310
|
+
data: clonedData,
|
|
311
|
+
status: "DRAFT",
|
|
312
|
+
locale: source.locale,
|
|
313
|
+
createdById: ctx.userId,
|
|
314
|
+
updatedById: ctx.userId,
|
|
315
|
+
plainText,
|
|
316
|
+
contentHash,
|
|
317
|
+
siteId: source.siteId,
|
|
318
|
+
templateId: source.templateId,
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
await tx.version.create({
|
|
322
|
+
data: {
|
|
323
|
+
documentId: doc.id,
|
|
324
|
+
data: clonedData,
|
|
325
|
+
changedById: ctx.userId,
|
|
326
|
+
changeType: "CREATE",
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
return doc;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/** Retrieve a global's data. Globals are stored as single-row collections keyed by slug. */
|
|
333
|
+
export async function getGlobal(slug, ctx) {
|
|
334
|
+
const db = prisma(ctx);
|
|
335
|
+
return db.document.findFirst({
|
|
336
|
+
where: { collection: slug, deletedAt: null },
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/** Update a global's data. Creates the document if it doesn't exist (upsert). */
|
|
340
|
+
export async function updateGlobal(slug, data, ctx) {
|
|
341
|
+
const db = prisma(ctx);
|
|
342
|
+
const { plainText, contentHash } = await computeTextFields(data);
|
|
343
|
+
const existing = await db.document.findFirst({
|
|
344
|
+
where: { collection: slug, deletedAt: null },
|
|
345
|
+
});
|
|
346
|
+
if (existing) {
|
|
347
|
+
return db.$transaction(async (tx) => {
|
|
348
|
+
const updated = await tx.document.update({
|
|
349
|
+
where: { id: existing.id },
|
|
350
|
+
data: {
|
|
351
|
+
data,
|
|
352
|
+
updatedById: ctx.userId,
|
|
353
|
+
plainText,
|
|
354
|
+
contentHash,
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
await tx.version.create({
|
|
358
|
+
data: {
|
|
359
|
+
documentId: existing.id,
|
|
360
|
+
data,
|
|
361
|
+
changedById: ctx.userId,
|
|
362
|
+
changeType: "UPDATE",
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
return updated;
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return db.$transaction(async (tx) => {
|
|
369
|
+
const created = await tx.document.create({
|
|
370
|
+
data: {
|
|
371
|
+
collection: slug,
|
|
372
|
+
data,
|
|
373
|
+
status: "DRAFT",
|
|
374
|
+
createdById: ctx.userId,
|
|
375
|
+
updatedById: ctx.userId,
|
|
376
|
+
plainText,
|
|
377
|
+
contentHash,
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
await tx.version.create({
|
|
381
|
+
data: {
|
|
382
|
+
documentId: created.id,
|
|
383
|
+
data,
|
|
384
|
+
changedById: ctx.userId,
|
|
385
|
+
changeType: "CREATE",
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
return created;
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
//# sourceMappingURL=actions.js.map
|